├── .gitignore
├── .metadata
├── .vscode
└── launch.json
├── README.md
├── analysis_options.yaml
├── android
├── .gitignore
├── app
│ ├── build.gradle
│ └── src
│ │ ├── debug
│ │ └── AndroidManifest.xml
│ │ ├── main
│ │ ├── AndroidManifest.xml
│ │ ├── kotlin
│ │ │ └── com
│ │ │ │ └── nomtek
│ │ │ │ └── mistral_ai_chat_example_app
│ │ │ │ └── MainActivity.kt
│ │ └── res
│ │ │ ├── drawable-v21
│ │ │ └── launch_background.xml
│ │ │ ├── drawable
│ │ │ └── launch_background.xml
│ │ │ ├── mipmap-hdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-mdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xhdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── values-night
│ │ │ └── styles.xml
│ │ │ └── values
│ │ │ └── styles.xml
│ │ └── profile
│ │ └── AndroidManifest.xml
├── build.gradle
├── gradle.properties
├── gradle
│ └── wrapper
│ │ └── gradle-wrapper.properties
└── settings.gradle
├── assets
├── 20k_leages_under_the_sea_verne.json
├── 20k_leages_under_the_sea_verne.txt
└── fonts
│ ├── Inter-Black.ttf
│ ├── Inter-Bold.ttf
│ ├── Inter-ExtraBold.ttf
│ ├── Inter-ExtraLight.ttf
│ ├── Inter-Light.ttf
│ ├── Inter-Medium.ttf
│ ├── Inter-Regular.ttf
│ ├── Inter-SemiBold.ttf
│ └── Inter-Thin.ttf
├── devtools_options.yaml
├── docs
└── assets
│ ├── app_settings_navigation.png
│ ├── llm_controller_example_demo.gif
│ ├── mistralai_book_search_example.gif
│ ├── mistralai_book_search_example.mp4
│ └── text_summary_example_demo.gif
├── env.env
├── ios
├── .gitignore
├── Flutter
│ ├── AppFrameworkInfo.plist
│ ├── Debug.xcconfig
│ └── Release.xcconfig
├── Podfile
├── Podfile.lock
├── Runner.xcodeproj
│ ├── project.pbxproj
│ ├── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ ├── IDEWorkspaceChecks.plist
│ │ │ └── WorkspaceSettings.xcsettings
│ └── xcshareddata
│ │ └── xcschemes
│ │ └── Runner.xcscheme
├── Runner.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ ├── IDEWorkspaceChecks.plist
│ │ └── WorkspaceSettings.xcsettings
├── Runner
│ ├── AppDelegate.swift
│ ├── Assets.xcassets
│ │ ├── AppIcon.appiconset
│ │ │ ├── Contents.json
│ │ │ ├── Icon-App-1024x1024@1x.png
│ │ │ ├── Icon-App-20x20@1x.png
│ │ │ ├── Icon-App-20x20@2x.png
│ │ │ ├── Icon-App-20x20@3x.png
│ │ │ ├── Icon-App-29x29@1x.png
│ │ │ ├── Icon-App-29x29@2x.png
│ │ │ ├── Icon-App-29x29@3x.png
│ │ │ ├── Icon-App-40x40@1x.png
│ │ │ ├── Icon-App-40x40@2x.png
│ │ │ ├── Icon-App-40x40@3x.png
│ │ │ ├── Icon-App-60x60@2x.png
│ │ │ ├── Icon-App-60x60@3x.png
│ │ │ ├── Icon-App-76x76@1x.png
│ │ │ ├── Icon-App-76x76@2x.png
│ │ │ └── Icon-App-83.5x83.5@2x.png
│ │ └── LaunchImage.imageset
│ │ │ ├── Contents.json
│ │ │ ├── LaunchImage.png
│ │ │ ├── LaunchImage@2x.png
│ │ │ ├── LaunchImage@3x.png
│ │ │ └── README.md
│ ├── Base.lproj
│ │ ├── LaunchScreen.storyboard
│ │ └── Main.storyboard
│ ├── Info.plist
│ └── Runner-Bridging-Header.h
└── RunnerTests
│ └── RunnerTests.swift
├── lib
├── app
│ ├── app.dart
│ ├── app_settings
│ │ ├── app_settings.dart
│ │ └── app_settings_page.dart
│ ├── home_page.dart
│ ├── home_tiles.dart
│ ├── router.dart
│ ├── router.g.dart
│ └── theme.dart
├── main.dart
├── mistral_ai_book_search_example
│ ├── README.md
│ ├── algebra.dart
│ ├── book_search.dart
│ ├── mistralai_book_search_page.dart
│ ├── models.dart
│ ├── models.g.dart
│ └── prepare_data.dart
├── mistral_ai_chat_example
│ ├── README.md
│ ├── chat_model.dart
│ └── mistralai_chat_page.dart
├── mistral_ai_llm_controller_example
│ ├── README.md
│ ├── logger_dialog.dart
│ ├── mistral_ai_llm_controller_page.dart
│ ├── model.dart
│ ├── model.g.dart
│ ├── prompt.dart
│ └── utils.dart
├── mistral_ai_summary_example
│ ├── README.md
│ ├── mistralai_summary_page.dart
│ ├── settings_dialogs.dart
│ ├── settings_model.dart
│ ├── summary_settings_page.dart
│ └── utils.dart
├── mistral_tokenizer
│ ├── merges_binary.dart
│ ├── mistral_tokenizer.dart
│ └── vocab_base64.dart
└── utils
│ ├── error_message.dart
│ └── snackbar_extension.dart
├── linux
├── .gitignore
├── CMakeLists.txt
├── flutter
│ ├── CMakeLists.txt
│ ├── generated_plugin_registrant.cc
│ ├── generated_plugin_registrant.h
│ └── generated_plugins.cmake
├── main.cc
├── my_application.cc
└── my_application.h
├── macos
├── .gitignore
├── Flutter
│ ├── Flutter-Debug.xcconfig
│ ├── Flutter-Release.xcconfig
│ └── GeneratedPluginRegistrant.swift
├── Podfile
├── Podfile.lock
├── Runner.xcodeproj
│ ├── project.pbxproj
│ ├── project.xcworkspace
│ │ └── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
│ └── xcshareddata
│ │ └── xcschemes
│ │ └── Runner.xcscheme
├── Runner.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
├── Runner
│ ├── AppDelegate.swift
│ ├── Assets.xcassets
│ │ └── AppIcon.appiconset
│ │ │ ├── Contents.json
│ │ │ ├── app_icon_1024.png
│ │ │ ├── app_icon_128.png
│ │ │ ├── app_icon_16.png
│ │ │ ├── app_icon_256.png
│ │ │ ├── app_icon_32.png
│ │ │ ├── app_icon_512.png
│ │ │ └── app_icon_64.png
│ ├── Base.lproj
│ │ └── MainMenu.xib
│ ├── Configs
│ │ ├── AppInfo.xcconfig
│ │ ├── Debug.xcconfig
│ │ ├── Release.xcconfig
│ │ └── Warnings.xcconfig
│ ├── DebugProfile.entitlements
│ ├── Info.plist
│ ├── MainFlutterWindow.swift
│ └── Release.entitlements
└── RunnerTests
│ └── RunnerTests.swift
├── pubspec.lock
├── pubspec.yaml
├── test
└── mistral_tokenizer
│ └── mistral_tokenizer_test.dart
├── tool
├── flutter_run.bat
└── flutter_run.sh
├── web
├── favicon.png
├── icons
│ ├── Icon-192.png
│ ├── Icon-512.png
│ ├── Icon-maskable-192.png
│ └── Icon-maskable-512.png
├── index.html
└── manifest.json
└── windows
├── .gitignore
├── CMakeLists.txt
├── flutter
├── CMakeLists.txt
├── generated_plugin_registrant.cc
├── generated_plugin_registrant.h
└── generated_plugins.cmake
└── runner
├── CMakeLists.txt
├── Runner.rc
├── flutter_window.cpp
├── flutter_window.h
├── main.cpp
├── resource.h
├── resources
└── app_icon.ico
├── runner.exe.manifest
├── utils.cpp
├── utils.h
├── win32_window.cpp
└── win32_window.h
/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .buildlog/
9 | .history
10 | .svn/
11 | migrate_working_dir/
12 |
13 | # IntelliJ related
14 | *.iml
15 | *.ipr
16 | *.iws
17 | .idea/
18 |
19 | # The .vscode folder contains launch configuration and tasks you configure in
20 | # VS Code which you may wish to be included in version control, so this line
21 | # is commented out by default.
22 | #.vscode/
23 |
24 | # Flutter/Dart/Pub related
25 | **/doc/api/
26 | **/ios/Flutter/.last_build_id
27 | .dart_tool/
28 | .flutter-plugins
29 | .flutter-plugins-dependencies
30 | .pub-cache/
31 | .pub/
32 | /build/
33 |
34 | # Symbolication related
35 | app.*.symbols
36 |
37 | # Obfuscation related
38 | app.*.map.json
39 |
40 | # Android Studio will place build artifacts here
41 | /android/app/debug
42 | /android/app/profile
43 | /android/app/release
44 |
--------------------------------------------------------------------------------
/.metadata:
--------------------------------------------------------------------------------
1 | # This file tracks properties of this Flutter project.
2 | # Used by Flutter tool to assess capabilities and perform upgrades etc.
3 | #
4 | # This file should be version controlled and should not be manually edited.
5 |
6 | version:
7 | revision: "b0366e0a3f089e15fd89c97604ab402fe26b724c"
8 | channel: "stable"
9 |
10 | project_type: app
11 |
12 | # Tracks metadata for the flutter migrate command
13 | migration:
14 | platforms:
15 | - platform: root
16 | create_revision: b0366e0a3f089e15fd89c97604ab402fe26b724c
17 | base_revision: b0366e0a3f089e15fd89c97604ab402fe26b724c
18 | - platform: android
19 | create_revision: b0366e0a3f089e15fd89c97604ab402fe26b724c
20 | base_revision: b0366e0a3f089e15fd89c97604ab402fe26b724c
21 | - platform: ios
22 | create_revision: b0366e0a3f089e15fd89c97604ab402fe26b724c
23 | base_revision: b0366e0a3f089e15fd89c97604ab402fe26b724c
24 | - platform: linux
25 | create_revision: b0366e0a3f089e15fd89c97604ab402fe26b724c
26 | base_revision: b0366e0a3f089e15fd89c97604ab402fe26b724c
27 | - platform: macos
28 | create_revision: b0366e0a3f089e15fd89c97604ab402fe26b724c
29 | base_revision: b0366e0a3f089e15fd89c97604ab402fe26b724c
30 | - platform: web
31 | create_revision: b0366e0a3f089e15fd89c97604ab402fe26b724c
32 | base_revision: b0366e0a3f089e15fd89c97604ab402fe26b724c
33 | - platform: windows
34 | create_revision: b0366e0a3f089e15fd89c97604ab402fe26b724c
35 | base_revision: b0366e0a3f089e15fd89c97604ab402fe26b724c
36 |
37 | # User provided section
38 |
39 | # List of Local paths (relative to this file) that should be
40 | # ignored by the migrate tool.
41 | #
42 | # Files that are not part of the templates will be ignored by default.
43 | unmanaged_files:
44 | - 'lib/main.dart'
45 | - 'ios/Runner.xcodeproj/project.pbxproj'
46 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": "Run app (debug mode)",
9 | "request": "launch",
10 | "type": "dart",
11 | "toolArgs": [
12 | "--dart-define-from-file",
13 | "env.env",
14 | ]
15 | },
16 | {
17 | "name": "Run app (profile mode)",
18 | "request": "launch",
19 | "type": "dart",
20 | "flutterMode": "profile",
21 | "toolArgs": [
22 | "--dart-define-from-file",
23 | "env.env",
24 | ]
25 | },
26 | {
27 | "name": "Run app (release mode)",
28 | "request": "launch",
29 | "type": "dart",
30 | "flutterMode": "release",
31 | "toolArgs": [
32 | "--dart-define-from-file",
33 | "env.env",
34 | ]
35 | }
36 | ]
37 | }
38 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Flutter AI Examples
2 |
3 | Example app showcasing different usages of AI models.
4 |
5 | ## List of examples
6 |
7 | Examples using [Mistral AI API](https://docs.mistral.ai/) through the [MistralAI Client for Dart](https://pub.dev/packages/mistralai_client_dart) package:
8 |
9 | - [Simple chat](lib/mistral_ai_chat_example/README.md)
10 | - [Text summary](lib/mistral_ai_summary_example/README.md)
11 | - [Book search](lib/mistral_ai_book_search_example/README.md)
12 | - [LLM as a controller](lib/mistral_ai_llm_controller_example/README.md)
13 |
14 | ### Interactive Demo
15 |
16 | Experience the capabilities of our AI examples live with our interactive demo. This demo allows you to explore the functionality of each example in real time through a web-based interface.
17 |
18 | [Launch Interactive Demo](https://flutter-ai-examples.web.app/)
19 |
20 | To access the interactive demo, a Mistral AI API key is necessary. If you haven't acquired one yet, please [sign up for a Mistral AI account](https://mistral.ai/) and obtain your API key.
21 |
22 | ## Getting Started
23 |
24 | ### Install dependencies
25 |
26 | ```shell
27 | flutter pub get
28 | ```
29 |
30 | ### Generate code
31 |
32 | ```shell
33 | dart run build_runner build -d
34 | ```
35 |
36 | ### Setup Mistral AI API key
37 |
38 | #### Using environment variables
39 |
40 | 1. Open the `env.env` file in the root project
41 | 2. Replace `your api key` with your Mistral AI API key.
42 |
43 | :warning: This method is required if you want to change a book in the [book search example](lib/mistral_ai_book_search_example/README.md).
44 |
45 | #### Using the app settings
46 |
47 | It's possible to change the key at runtime in the app settings.
48 |
49 | [](docs/assets/app_settings_navigation.png)
50 |
51 | ### Run the example app
52 |
53 | #### Using the Visual Studio Code
54 |
55 | We've prepared some ready to use [launch configurations](.vscode/launch.json) for VSC.
56 |
57 | In most cases, you should choose the `Run app (debug mode)` configuration.
58 |
59 | #### Using the terminal
60 |
61 | You can either use the `flutter run` command directly
62 |
63 | ```shell
64 | flutter run --dart-define-from-file=env.env
65 | ```
66 |
67 | or use a script that contains the above snippet
68 |
69 | ##### Linux/MacOS
70 |
71 | ```shell
72 | ./tool/flutter_run.sh
73 | ```
74 |
75 | ##### Windows
76 |
77 | ```shell
78 | tool\flutter_run.bat
79 | ```
80 |
81 | You can pass parameters to the script by appending them at the end like this:
82 |
83 | ```shell
84 | // Linux/MacOS
85 | ./tool/flutter_run.sh -d chrome
86 |
87 | // Windows
88 | tool\flutter_run.bat -d chrome
89 | ```
90 |
--------------------------------------------------------------------------------
/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | include: package:very_good_analysis/analysis_options.yaml
2 |
3 | linter:
4 | rules:
5 | public_member_api_docs: false
6 |
7 | analyzer:
8 | exclude:
9 | - '**.g.dart'
10 | plugins:
11 | - custom_lint
12 |
--------------------------------------------------------------------------------
/android/.gitignore:
--------------------------------------------------------------------------------
1 | gradle-wrapper.jar
2 | /.gradle
3 | /captures/
4 | /gradlew
5 | /gradlew.bat
6 | /local.properties
7 | GeneratedPluginRegistrant.java
8 |
9 | # Remember to never publicly share your keystore.
10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
11 | key.properties
12 | **/*.keystore
13 | **/*.jks
14 |
--------------------------------------------------------------------------------
/android/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id "com.android.application"
3 | id "kotlin-android"
4 | id "dev.flutter.flutter-gradle-plugin"
5 | }
6 |
7 | def localProperties = new Properties()
8 | def localPropertiesFile = rootProject.file('local.properties')
9 | if (localPropertiesFile.exists()) {
10 | localPropertiesFile.withReader('UTF-8') { reader ->
11 | localProperties.load(reader)
12 | }
13 | }
14 |
15 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
16 | if (flutterVersionCode == null) {
17 | flutterVersionCode = '1'
18 | }
19 |
20 | def flutterVersionName = localProperties.getProperty('flutter.versionName')
21 | if (flutterVersionName == null) {
22 | flutterVersionName = '1.0'
23 | }
24 |
25 | android {
26 | namespace "com.nomtek.flutter_ai_examples"
27 | compileSdkVersion flutter.compileSdkVersion
28 | ndkVersion flutter.ndkVersion
29 |
30 | compileOptions {
31 | sourceCompatibility JavaVersion.VERSION_1_8
32 | targetCompatibility JavaVersion.VERSION_1_8
33 | }
34 |
35 | kotlinOptions {
36 | jvmTarget = '1.8'
37 | }
38 |
39 | sourceSets {
40 | main.java.srcDirs += 'src/main/kotlin'
41 | }
42 |
43 | defaultConfig {
44 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
45 | applicationId "com.nomtek.flutter_ai_examples"
46 | // You can update the following values to match your application needs.
47 | // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
48 | minSdkVersion flutter.minSdkVersion
49 | targetSdkVersion flutter.targetSdkVersion
50 | versionCode flutterVersionCode.toInteger()
51 | versionName flutterVersionName
52 | }
53 |
54 | buildTypes {
55 | release {
56 | // TODO: Add your own signing config for the release build.
57 | // Signing with the debug keys for now, so `flutter run --release` works.
58 | signingConfig signingConfigs.debug
59 | }
60 | }
61 | }
62 |
63 | flutter {
64 | source '../..'
65 | }
66 |
67 | dependencies {}
68 |
--------------------------------------------------------------------------------
/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
16 |
20 |
24 |
25 |
26 |
27 |
28 |
29 |
31 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/android/app/src/main/kotlin/com/nomtek/mistral_ai_chat_example_app/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.nomtek.flutter_ai_examples
2 |
3 | import io.flutter.embedding.android.FlutterActivity
4 |
5 | class MainActivity: FlutterActivity() {
6 | }
7 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-v21/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nomtek/flutter_ai_examples/4fad00324f284e6003ef6f14cea32e21e9b704ba/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nomtek/flutter_ai_examples/4fad00324f284e6003ef6f14cea32e21e9b704ba/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nomtek/flutter_ai_examples/4fad00324f284e6003ef6f14cea32e21e9b704ba/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nomtek/flutter_ai_examples/4fad00324f284e6003ef6f14cea32e21e9b704ba/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nomtek/flutter_ai_examples/4fad00324f284e6003ef6f14cea32e21e9b704ba/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/values-night/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/android/app/src/profile/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | ext.kotlin_version = '1.7.10'
3 | repositories {
4 | google()
5 | mavenCentral()
6 | }
7 |
8 | dependencies {
9 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
10 | }
11 | }
12 |
13 | allprojects {
14 | repositories {
15 | google()
16 | mavenCentral()
17 | }
18 | }
19 |
20 | rootProject.buildDir = '../build'
21 | subprojects {
22 | project.buildDir = "${rootProject.buildDir}/${project.name}"
23 | }
24 | subprojects {
25 | project.evaluationDependsOn(':app')
26 | }
27 |
28 | tasks.register("clean", Delete) {
29 | delete rootProject.buildDir
30 | }
31 |
--------------------------------------------------------------------------------
/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx4G
2 | android.useAndroidX=true
3 | android.enableJetifier=true
4 |
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | zipStoreBase=GRADLE_USER_HOME
4 | zipStorePath=wrapper/dists
5 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip
6 |
--------------------------------------------------------------------------------
/android/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | def flutterSdkPath = {
3 | def properties = new Properties()
4 | file("local.properties").withInputStream { properties.load(it) }
5 | def flutterSdkPath = properties.getProperty("flutter.sdk")
6 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
7 | return flutterSdkPath
8 | }
9 | settings.ext.flutterSdkPath = flutterSdkPath()
10 |
11 | includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle")
12 |
13 | repositories {
14 | google()
15 | mavenCentral()
16 | gradlePluginPortal()
17 | }
18 |
19 | plugins {
20 | id "dev.flutter.flutter-gradle-plugin" version "1.0.0" apply false
21 | }
22 | }
23 |
24 | plugins {
25 | id "dev.flutter.flutter-plugin-loader" version "1.0.0"
26 | id "com.android.application" version "7.3.0" apply false
27 | }
28 |
29 | include ":app"
30 |
--------------------------------------------------------------------------------
/assets/fonts/Inter-Black.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nomtek/flutter_ai_examples/4fad00324f284e6003ef6f14cea32e21e9b704ba/assets/fonts/Inter-Black.ttf
--------------------------------------------------------------------------------
/assets/fonts/Inter-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nomtek/flutter_ai_examples/4fad00324f284e6003ef6f14cea32e21e9b704ba/assets/fonts/Inter-Bold.ttf
--------------------------------------------------------------------------------
/assets/fonts/Inter-ExtraBold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nomtek/flutter_ai_examples/4fad00324f284e6003ef6f14cea32e21e9b704ba/assets/fonts/Inter-ExtraBold.ttf
--------------------------------------------------------------------------------
/assets/fonts/Inter-ExtraLight.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nomtek/flutter_ai_examples/4fad00324f284e6003ef6f14cea32e21e9b704ba/assets/fonts/Inter-ExtraLight.ttf
--------------------------------------------------------------------------------
/assets/fonts/Inter-Light.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nomtek/flutter_ai_examples/4fad00324f284e6003ef6f14cea32e21e9b704ba/assets/fonts/Inter-Light.ttf
--------------------------------------------------------------------------------
/assets/fonts/Inter-Medium.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nomtek/flutter_ai_examples/4fad00324f284e6003ef6f14cea32e21e9b704ba/assets/fonts/Inter-Medium.ttf
--------------------------------------------------------------------------------
/assets/fonts/Inter-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nomtek/flutter_ai_examples/4fad00324f284e6003ef6f14cea32e21e9b704ba/assets/fonts/Inter-Regular.ttf
--------------------------------------------------------------------------------
/assets/fonts/Inter-SemiBold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nomtek/flutter_ai_examples/4fad00324f284e6003ef6f14cea32e21e9b704ba/assets/fonts/Inter-SemiBold.ttf
--------------------------------------------------------------------------------
/assets/fonts/Inter-Thin.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nomtek/flutter_ai_examples/4fad00324f284e6003ef6f14cea32e21e9b704ba/assets/fonts/Inter-Thin.ttf
--------------------------------------------------------------------------------
/devtools_options.yaml:
--------------------------------------------------------------------------------
1 | extensions:
2 |
--------------------------------------------------------------------------------
/docs/assets/app_settings_navigation.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nomtek/flutter_ai_examples/4fad00324f284e6003ef6f14cea32e21e9b704ba/docs/assets/app_settings_navigation.png
--------------------------------------------------------------------------------
/docs/assets/llm_controller_example_demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nomtek/flutter_ai_examples/4fad00324f284e6003ef6f14cea32e21e9b704ba/docs/assets/llm_controller_example_demo.gif
--------------------------------------------------------------------------------
/docs/assets/mistralai_book_search_example.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nomtek/flutter_ai_examples/4fad00324f284e6003ef6f14cea32e21e9b704ba/docs/assets/mistralai_book_search_example.gif
--------------------------------------------------------------------------------
/docs/assets/mistralai_book_search_example.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nomtek/flutter_ai_examples/4fad00324f284e6003ef6f14cea32e21e9b704ba/docs/assets/mistralai_book_search_example.mp4
--------------------------------------------------------------------------------
/docs/assets/text_summary_example_demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nomtek/flutter_ai_examples/4fad00324f284e6003ef6f14cea32e21e9b704ba/docs/assets/text_summary_example_demo.gif
--------------------------------------------------------------------------------
/env.env:
--------------------------------------------------------------------------------
1 | # inject this file from CI to setup the environment variables
2 | MISTRAL_AI_API_KEY=your api key
3 |
--------------------------------------------------------------------------------
/ios/.gitignore:
--------------------------------------------------------------------------------
1 | **/dgph
2 | *.mode1v3
3 | *.mode2v3
4 | *.moved-aside
5 | *.pbxuser
6 | *.perspectivev3
7 | **/*sync/
8 | .sconsign.dblite
9 | .tags*
10 | **/.vagrant/
11 | **/DerivedData/
12 | Icon?
13 | **/Pods/
14 | **/.symlinks/
15 | profile
16 | xcuserdata
17 | **/.generated/
18 | Flutter/App.framework
19 | Flutter/Flutter.framework
20 | Flutter/Flutter.podspec
21 | Flutter/Generated.xcconfig
22 | Flutter/ephemeral/
23 | Flutter/app.flx
24 | Flutter/app.zip
25 | Flutter/flutter_assets/
26 | Flutter/flutter_export_environment.sh
27 | ServiceDefinitions.json
28 | Runner/GeneratedPluginRegistrant.*
29 |
30 | # Exceptions to above rules.
31 | !default.mode1v3
32 | !default.mode2v3
33 | !default.pbxuser
34 | !default.perspectivev3
35 |
--------------------------------------------------------------------------------
/ios/Flutter/AppFrameworkInfo.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | App
9 | CFBundleIdentifier
10 | io.flutter.flutter.app
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | App
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1.0
23 | MinimumOSVersion
24 | 12.0
25 |
26 |
27 |
--------------------------------------------------------------------------------
/ios/Flutter/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/ios/Flutter/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/ios/Podfile:
--------------------------------------------------------------------------------
1 | # Uncomment this line to define a global platform for your project
2 | # platform :ios, '12.0'
3 |
4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true'
6 |
7 | project 'Runner', {
8 | 'Debug' => :debug,
9 | 'Profile' => :release,
10 | 'Release' => :release,
11 | }
12 |
13 | def flutter_root
14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
15 | unless File.exist?(generated_xcode_build_settings_path)
16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
17 | end
18 |
19 | File.foreach(generated_xcode_build_settings_path) do |line|
20 | matches = line.match(/FLUTTER_ROOT\=(.*)/)
21 | return matches[1].strip if matches
22 | end
23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
24 | end
25 |
26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
27 |
28 | flutter_ios_podfile_setup
29 |
30 | target 'Runner' do
31 | use_frameworks!
32 | use_modular_headers!
33 |
34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
35 | target 'RunnerTests' do
36 | inherit! :search_paths
37 | end
38 | end
39 |
40 | post_install do |installer|
41 | installer.pods_project.targets.each do |target|
42 | flutter_additional_ios_build_settings(target)
43 | end
44 | end
45 |
--------------------------------------------------------------------------------
/ios/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - Flutter (1.0.0)
3 | - path_provider_foundation (0.0.1):
4 | - Flutter
5 | - FlutterMacOS
6 | - shared_preferences_foundation (0.0.1):
7 | - Flutter
8 | - FlutterMacOS
9 |
10 | DEPENDENCIES:
11 | - Flutter (from `Flutter`)
12 | - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
13 | - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
14 |
15 | EXTERNAL SOURCES:
16 | Flutter:
17 | :path: Flutter
18 | path_provider_foundation:
19 | :path: ".symlinks/plugins/path_provider_foundation/darwin"
20 | shared_preferences_foundation:
21 | :path: ".symlinks/plugins/shared_preferences_foundation/darwin"
22 |
23 | SPEC CHECKSUMS:
24 | Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
25 | path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c
26 | shared_preferences_foundation: b4c3b4cddf1c21f02770737f147a3f5da9d39695
27 |
28 | PODFILE CHECKSUM: 819463e6a0290f5a72f145ba7cde16e8b6ef0796
29 |
30 | COCOAPODS: 1.14.2
31 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
37 |
38 |
39 |
40 |
43 |
49 |
50 |
51 |
52 |
53 |
63 |
65 |
71 |
72 |
73 |
74 |
80 |
82 |
88 |
89 |
90 |
91 |
93 |
94 |
97 |
98 |
99 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import Flutter
3 |
4 | @UIApplicationMain
5 | @objc class AppDelegate: FlutterAppDelegate {
6 | override func application(
7 | _ application: UIApplication,
8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
9 | ) -> Bool {
10 | GeneratedPluginRegistrant.register(with: self)
11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "20x20",
5 | "idiom" : "iphone",
6 | "filename" : "Icon-App-20x20@2x.png",
7 | "scale" : "2x"
8 | },
9 | {
10 | "size" : "20x20",
11 | "idiom" : "iphone",
12 | "filename" : "Icon-App-20x20@3x.png",
13 | "scale" : "3x"
14 | },
15 | {
16 | "size" : "29x29",
17 | "idiom" : "iphone",
18 | "filename" : "Icon-App-29x29@1x.png",
19 | "scale" : "1x"
20 | },
21 | {
22 | "size" : "29x29",
23 | "idiom" : "iphone",
24 | "filename" : "Icon-App-29x29@2x.png",
25 | "scale" : "2x"
26 | },
27 | {
28 | "size" : "29x29",
29 | "idiom" : "iphone",
30 | "filename" : "Icon-App-29x29@3x.png",
31 | "scale" : "3x"
32 | },
33 | {
34 | "size" : "40x40",
35 | "idiom" : "iphone",
36 | "filename" : "Icon-App-40x40@2x.png",
37 | "scale" : "2x"
38 | },
39 | {
40 | "size" : "40x40",
41 | "idiom" : "iphone",
42 | "filename" : "Icon-App-40x40@3x.png",
43 | "scale" : "3x"
44 | },
45 | {
46 | "size" : "60x60",
47 | "idiom" : "iphone",
48 | "filename" : "Icon-App-60x60@2x.png",
49 | "scale" : "2x"
50 | },
51 | {
52 | "size" : "60x60",
53 | "idiom" : "iphone",
54 | "filename" : "Icon-App-60x60@3x.png",
55 | "scale" : "3x"
56 | },
57 | {
58 | "size" : "20x20",
59 | "idiom" : "ipad",
60 | "filename" : "Icon-App-20x20@1x.png",
61 | "scale" : "1x"
62 | },
63 | {
64 | "size" : "20x20",
65 | "idiom" : "ipad",
66 | "filename" : "Icon-App-20x20@2x.png",
67 | "scale" : "2x"
68 | },
69 | {
70 | "size" : "29x29",
71 | "idiom" : "ipad",
72 | "filename" : "Icon-App-29x29@1x.png",
73 | "scale" : "1x"
74 | },
75 | {
76 | "size" : "29x29",
77 | "idiom" : "ipad",
78 | "filename" : "Icon-App-29x29@2x.png",
79 | "scale" : "2x"
80 | },
81 | {
82 | "size" : "40x40",
83 | "idiom" : "ipad",
84 | "filename" : "Icon-App-40x40@1x.png",
85 | "scale" : "1x"
86 | },
87 | {
88 | "size" : "40x40",
89 | "idiom" : "ipad",
90 | "filename" : "Icon-App-40x40@2x.png",
91 | "scale" : "2x"
92 | },
93 | {
94 | "size" : "76x76",
95 | "idiom" : "ipad",
96 | "filename" : "Icon-App-76x76@1x.png",
97 | "scale" : "1x"
98 | },
99 | {
100 | "size" : "76x76",
101 | "idiom" : "ipad",
102 | "filename" : "Icon-App-76x76@2x.png",
103 | "scale" : "2x"
104 | },
105 | {
106 | "size" : "83.5x83.5",
107 | "idiom" : "ipad",
108 | "filename" : "Icon-App-83.5x83.5@2x.png",
109 | "scale" : "2x"
110 | },
111 | {
112 | "size" : "1024x1024",
113 | "idiom" : "ios-marketing",
114 | "filename" : "Icon-App-1024x1024@1x.png",
115 | "scale" : "1x"
116 | }
117 | ],
118 | "info" : {
119 | "version" : 1,
120 | "author" : "xcode"
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nomtek/flutter_ai_examples/4fad00324f284e6003ef6f14cea32e21e9b704ba/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nomtek/flutter_ai_examples/4fad00324f284e6003ef6f14cea32e21e9b704ba/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nomtek/flutter_ai_examples/4fad00324f284e6003ef6f14cea32e21e9b704ba/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nomtek/flutter_ai_examples/4fad00324f284e6003ef6f14cea32e21e9b704ba/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nomtek/flutter_ai_examples/4fad00324f284e6003ef6f14cea32e21e9b704ba/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nomtek/flutter_ai_examples/4fad00324f284e6003ef6f14cea32e21e9b704ba/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nomtek/flutter_ai_examples/4fad00324f284e6003ef6f14cea32e21e9b704ba/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nomtek/flutter_ai_examples/4fad00324f284e6003ef6f14cea32e21e9b704ba/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nomtek/flutter_ai_examples/4fad00324f284e6003ef6f14cea32e21e9b704ba/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nomtek/flutter_ai_examples/4fad00324f284e6003ef6f14cea32e21e9b704ba/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nomtek/flutter_ai_examples/4fad00324f284e6003ef6f14cea32e21e9b704ba/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nomtek/flutter_ai_examples/4fad00324f284e6003ef6f14cea32e21e9b704ba/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nomtek/flutter_ai_examples/4fad00324f284e6003ef6f14cea32e21e9b704ba/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nomtek/flutter_ai_examples/4fad00324f284e6003ef6f14cea32e21e9b704ba/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nomtek/flutter_ai_examples/4fad00324f284e6003ef6f14cea32e21e9b704ba/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "LaunchImage.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "LaunchImage@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "LaunchImage@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nomtek/flutter_ai_examples/4fad00324f284e6003ef6f14cea32e21e9b704ba/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nomtek/flutter_ai_examples/4fad00324f284e6003ef6f14cea32e21e9b704ba/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nomtek/flutter_ai_examples/4fad00324f284e6003ef6f14cea32e21e9b704ba/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md:
--------------------------------------------------------------------------------
1 | # Launch Screen Assets
2 |
3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory.
4 |
5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/ios/Runner/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleDisplayName
8 | Mistral Ai Chat Example App
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | flutter_ai_examples
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | $(FLUTTER_BUILD_NAME)
21 | CFBundleSignature
22 | ????
23 | CFBundleVersion
24 | $(FLUTTER_BUILD_NUMBER)
25 | LSRequiresIPhoneOS
26 |
27 | UILaunchStoryboardName
28 | LaunchScreen
29 | UIMainStoryboardFile
30 | Main
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationPortrait
34 | UIInterfaceOrientationLandscapeLeft
35 | UIInterfaceOrientationLandscapeRight
36 |
37 | UISupportedInterfaceOrientations~ipad
38 |
39 | UIInterfaceOrientationPortrait
40 | UIInterfaceOrientationPortraitUpsideDown
41 | UIInterfaceOrientationLandscapeLeft
42 | UIInterfaceOrientationLandscapeRight
43 |
44 | CADisableMinimumFrameDurationOnPhone
45 |
46 | UIApplicationSupportsIndirectInputEvents
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/ios/Runner/Runner-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | #import "GeneratedPluginRegistrant.h"
2 |
--------------------------------------------------------------------------------
/ios/RunnerTests/RunnerTests.swift:
--------------------------------------------------------------------------------
1 | import Flutter
2 | import UIKit
3 | import XCTest
4 |
5 | class RunnerTests: XCTestCase {
6 |
7 | func testExample() {
8 | // If you add code to the Runner application, consider adding tests here.
9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest.
10 | }
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/lib/app/app.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_ai_examples/app/app_settings/app_settings.dart';
3 | import 'package:flutter_ai_examples/app/router.dart';
4 | import 'package:flutter_ai_examples/app/theme.dart';
5 | import 'package:mistralai_client_dart/mistralai_client_dart.dart';
6 | import 'package:provider/provider.dart';
7 | import 'package:shared_preferences/shared_preferences.dart';
8 |
9 | class MyApp extends StatelessWidget {
10 | const MyApp({required this.sharedPreferences, super.key});
11 |
12 | final SharedPreferences sharedPreferences;
13 |
14 | @override
15 | Widget build(BuildContext context) {
16 | return MultiProvider(
17 | providers: [
18 | Provider.value(value: sharedPreferences),
19 | ChangeNotifierProvider(
20 | create: (context) =>
21 | AppSettings(context.read())..init(),
22 | ),
23 | ProxyProvider0(
24 | update: (context, __) => MistralAIClient(
25 | apiKey: context.watch().mistralApiKey,
26 | ),
27 | ),
28 | ],
29 | child: MaterialApp.router(
30 | debugShowCheckedModeBanner: false,
31 | routerConfig: router,
32 | theme: lightTheme(),
33 | ),
34 | );
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/lib/app/app_settings/app_settings.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:shared_preferences/shared_preferences.dart';
3 |
4 | // key for the shared preferences where the API key is stored
5 | const _sharedPrefsMistralApiKey = 'mistralApiKey';
6 |
7 | // environment variable for the Mistral AI API key
8 | const String _mistralAIApiKeyEnvVar =
9 | String.fromEnvironment('MISTRAL_AI_API_KEY');
10 |
11 | class AppSettings extends ValueNotifier {
12 | AppSettings(this._sharedPreferences)
13 | : super(const AppSettingsData(mistralApiKey: ''));
14 |
15 | final SharedPreferences _sharedPreferences;
16 |
17 | String get mistralApiKey => value.mistralApiKey;
18 |
19 | set mistralApiKey(String apiKey) {
20 | value = value.copyWith(apiKey: apiKey);
21 | }
22 |
23 | // initialize the app settings from the shared preferences
24 | void init() {
25 | if (!_sharedPreferences.containsKey(_sharedPrefsMistralApiKey)) {
26 | // if there is no persisted API key,
27 | // try to set the environment variable one as a fallback
28 | _sharedPreferences.setString(
29 | _sharedPrefsMistralApiKey,
30 | _mistralAIApiKeyEnvVar,
31 | );
32 | }
33 | mistralApiKey =
34 | _sharedPreferences.getString(_sharedPrefsMistralApiKey) ?? '';
35 | }
36 | }
37 |
38 | @immutable
39 | class AppSettingsData {
40 | const AppSettingsData({
41 | required this.mistralApiKey,
42 | });
43 |
44 | final String mistralApiKey;
45 |
46 | // for now check if the API key is not empty
47 | bool isMistralApiKeyValid() => mistralApiKey.trim().isNotEmpty;
48 |
49 | @override
50 | bool operator ==(Object other) {
51 | if (identical(this, other)) return true;
52 | return other is AppSettingsData && other.mistralApiKey == mistralApiKey;
53 | }
54 |
55 | @override
56 | int get hashCode => mistralApiKey.hashCode;
57 |
58 | AppSettingsData copyWith({
59 | String? apiKey,
60 | }) {
61 | return AppSettingsData(
62 | mistralApiKey: apiKey ?? mistralApiKey,
63 | );
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/lib/app/app_settings/app_settings_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_ai_examples/app/app_settings/app_settings.dart';
3 | import 'package:flutter_ai_examples/app/theme.dart';
4 | import 'package:material_symbols_icons/material_symbols_icons.dart';
5 | import 'package:provider/provider.dart';
6 |
7 | class AppSettingsPage extends StatelessWidget {
8 | const AppSettingsPage({super.key});
9 |
10 | @override
11 | Widget build(BuildContext context) {
12 | final mistralAIApiKey = context.watch().mistralApiKey;
13 |
14 | return DarkerBackgroundTheme(
15 | child: Scaffold(
16 | appBar: AppBar(
17 | title: const Text('App Settings'),
18 | ),
19 | body: SafeArea(
20 | child: Column(
21 | children: [
22 | ListTile(
23 | leading: const Icon(Symbols.key_vertical),
24 | titleAlignment: ListTileTitleAlignment.titleHeight,
25 | title: const Text('Mistral AI API Key'),
26 | subtitle: Text('Current value: $mistralAIApiKey'),
27 | onTap: () {
28 | showDialog(
29 | context: context,
30 | builder: (context) => MistralAIApiKeyField(
31 | currentApiKey: context.read().mistralApiKey,
32 | ),
33 | );
34 | },
35 | ),
36 | ],
37 | ),
38 | ),
39 | ),
40 | );
41 | }
42 | }
43 |
44 | class MistralAIApiKeyField extends StatefulWidget {
45 | const MistralAIApiKeyField({
46 | required this.currentApiKey,
47 | super.key,
48 | });
49 |
50 | final String currentApiKey;
51 |
52 | @override
53 | State createState() => _MistralAIApiKeyFieldState();
54 | }
55 |
56 | class _MistralAIApiKeyFieldState extends State {
57 | late final TextEditingController inputController;
58 |
59 | @override
60 | void initState() {
61 | inputController = TextEditingController(text: widget.currentApiKey);
62 | super.initState();
63 | }
64 |
65 | @override
66 | void dispose() {
67 | inputController.dispose();
68 | super.dispose();
69 | }
70 |
71 | @override
72 | Widget build(BuildContext context) {
73 | return AlertDialog(
74 | actions: [
75 | TextButton(
76 | onPressed: () => Navigator.pop(context),
77 | child: const Text('Cancel'),
78 | ),
79 | TextButton(
80 | onPressed: () => _save(context),
81 | child: const Text('Update'),
82 | ),
83 | ],
84 | title: const Text('Update Mistral AI API Key'),
85 | content: TextField(
86 | decoration: const InputDecoration(
87 | labelText: 'API Key',
88 | border: OutlineInputBorder(),
89 | ),
90 | controller: inputController,
91 | autofocus: true,
92 | onSubmitted: (_) => _save(context),
93 | ),
94 | );
95 | }
96 |
97 | void _save(BuildContext context) {
98 | context.read().mistralApiKey = inputController.text;
99 | Navigator.pop(context);
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/lib/app/home_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/gestures.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter_ai_examples/app/app_settings/app_settings.dart';
4 | import 'package:flutter_ai_examples/app/home_tiles.dart';
5 | import 'package:flutter_ai_examples/app/router.dart';
6 | import 'package:flutter_ai_examples/utils/snackbar_extension.dart';
7 | import 'package:provider/provider.dart';
8 | import 'package:url_launcher/url_launcher.dart';
9 |
10 | class HomePage extends StatelessWidget {
11 | const HomePage({super.key});
12 | @override
13 | Widget build(BuildContext context) => Scaffold(
14 | appBar: AppBar(
15 | title: const Text('Flutter AI Examples'),
16 | actions: [
17 | IconButton(
18 | onPressed: () => const AppSettingsRoute().go(context),
19 | icon: const Icon(Icons.settings),
20 | ),
21 | ],
22 | ),
23 | body: const SafeArea(
24 | child: SingleChildScrollView(
25 | padding: EdgeInsets.all(20),
26 | child: MistralAIExamples(),
27 | ),
28 | ),
29 | );
30 | }
31 |
32 | class MistralAIExamples extends StatelessWidget {
33 | const MistralAIExamples({
34 | super.key,
35 | });
36 |
37 | @override
38 | Widget build(BuildContext context) {
39 | final isMistralSetUp = context.select(
40 | (appSettings) => appSettings.value.isMistralApiKeyValid(),
41 | );
42 | return Column(
43 | mainAxisSize: MainAxisSize.min,
44 | children: [
45 | const HomeSectionTitle(sectionTitle: 'Mistral AI Examples'),
46 | const SizedBox(height: 16),
47 | if (!isMistralSetUp) const MistralSetupNotFinished(),
48 | const SizedBox(height: 16),
49 | MistralExampleTilesGrid(isEnabled: isMistralSetUp),
50 | ],
51 | );
52 | }
53 | }
54 |
55 | class MistralSetupNotFinished extends StatelessWidget {
56 | const MistralSetupNotFinished({
57 | super.key,
58 | });
59 |
60 | @override
61 | Widget build(BuildContext context) {
62 | return Container(
63 | padding: const EdgeInsets.all(16),
64 | decoration: BoxDecoration(
65 | color: Colors.red[100],
66 | borderRadius: BorderRadius.circular(8),
67 | ),
68 | child: Column(
69 | children: [
70 | Text.rich(
71 | textAlign: TextAlign.center,
72 | TextSpan(
73 | text: 'Please setup Mistral AI API key.\n'
74 | 'Without it, the examples will not work. \n'
75 | 'You can get the API key from the ',
76 | children: [
77 | TextSpan(
78 | text: 'Mistral AI website.',
79 | style: TextStyle(
80 | color: Theme.of(context).primaryColor,
81 | decoration: TextDecoration.underline,
82 | decorationColor: Theme.of(context).primaryColor,
83 | ),
84 | recognizer: TapGestureRecognizer()
85 | ..onTap = () async {
86 | final url = Uri.parse('https://console.mistral.ai/');
87 | if (!await launchUrl(url)) {
88 | if (!context.mounted) return;
89 | context.showMessageSnackBar('Could not launch $url');
90 | }
91 | },
92 | ),
93 | ],
94 | ),
95 | ),
96 | const SizedBox(height: 16),
97 | ElevatedButton(
98 | onPressed: () => const AppSettingsRoute().go(context),
99 | child: const Text('Go to App Settings'),
100 | ),
101 | ],
102 | ),
103 | );
104 | }
105 | }
106 |
107 | class MistralExampleTilesGrid extends StatelessWidget {
108 | const MistralExampleTilesGrid({required this.isEnabled, super.key});
109 |
110 | final bool isEnabled;
111 |
112 | @override
113 | Widget build(BuildContext context) {
114 | final screenWidth = MediaQuery.of(context).size.width;
115 | final columnSize = switch (screenWidth) {
116 | < 600 => 2,
117 | < 900 => 3,
118 | < 1500 => 4,
119 | _ => 8,
120 | };
121 | return GridView.count(
122 | primary: false,
123 | crossAxisSpacing: 10,
124 | mainAxisSpacing: 10,
125 | crossAxisCount: columnSize,
126 | shrinkWrap: true,
127 | childAspectRatio: 1.75,
128 | children: [
129 | ChatExampleTile(isEnabled: isEnabled),
130 | TextSummaryTile(isEnabled: isEnabled),
131 | LllAsControllerTile(isEnabled: isEnabled),
132 | BookSearchTile(isEnabled: isEnabled),
133 | ],
134 | );
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/lib/app/home_tiles.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_ai_examples/app/router.dart';
3 | import 'package:material_symbols_icons/material_symbols_icons.dart';
4 |
5 | class LllAsControllerTile extends StatelessWidget {
6 | const LllAsControllerTile({
7 | required this.isEnabled,
8 | super.key,
9 | });
10 |
11 | final bool isEnabled;
12 |
13 | @override
14 | Widget build(BuildContext context) {
15 | return HomeExampleTile(
16 | title: 'LLM as Controller',
17 | icon: Symbols.home,
18 | gradientColors: const [
19 | Colors.red,
20 | Colors.orangeAccent,
21 | ],
22 | isEnabled: isEnabled,
23 | onTap: () => const MistralAILlmControllerRoute().go(context),
24 | );
25 | }
26 | }
27 |
28 | class TextSummaryTile extends StatelessWidget {
29 | const TextSummaryTile({
30 | required this.isEnabled,
31 | super.key,
32 | });
33 |
34 | final bool isEnabled;
35 |
36 | @override
37 | Widget build(BuildContext context) {
38 | return HomeExampleTile(
39 | title: 'Text Summary',
40 | icon: Symbols.summarize,
41 | gradientColors: const [
42 | Colors.green,
43 | Colors.yellow,
44 | ],
45 | isEnabled: isEnabled,
46 | onTap: () => const MistralAISummaryRoute().go(context),
47 | );
48 | }
49 | }
50 |
51 | class ChatExampleTile extends StatelessWidget {
52 | const ChatExampleTile({
53 | required this.isEnabled,
54 | super.key,
55 | });
56 |
57 | final bool isEnabled;
58 |
59 | @override
60 | Widget build(BuildContext context) {
61 | return HomeExampleTile(
62 | title: 'Chat',
63 | icon: Symbols.forum,
64 | gradientColors: [
65 | Colors.blue[800]!,
66 | Colors.blue[400]!,
67 | ],
68 | isEnabled: isEnabled,
69 | onTap: isEnabled ? () => const MistralAIChatRoute().go(context) : null,
70 | );
71 | }
72 | }
73 |
74 | class BookSearchTile extends StatelessWidget {
75 | const BookSearchTile({
76 | required this.isEnabled,
77 | super.key,
78 | });
79 |
80 | final bool isEnabled;
81 |
82 | @override
83 | Widget build(BuildContext context) {
84 | return HomeExampleTile(
85 | title: 'Book Search',
86 | icon: Symbols.book,
87 | gradientColors: const [
88 | Colors.purple,
89 | Colors.pinkAccent,
90 | ],
91 | isEnabled: isEnabled,
92 | onTap: () => const MistralAIBookSearchRoute().go(context),
93 | );
94 | }
95 | }
96 |
97 | class HomeSectionTitle extends StatelessWidget {
98 | const HomeSectionTitle({required this.sectionTitle, super.key});
99 |
100 | final String sectionTitle;
101 |
102 | @override
103 | Widget build(BuildContext context) {
104 | return Container(
105 | height: 33,
106 | alignment: Alignment.topLeft,
107 | child: Text(
108 | sectionTitle,
109 | style: const TextStyle(
110 | fontSize: 20,
111 | fontWeight: FontWeight.w600,
112 | height: 24.2 / 20,
113 | ),
114 | ),
115 | );
116 | }
117 | }
118 |
119 | class HomeExampleTile extends StatelessWidget {
120 | const HomeExampleTile({
121 | required this.title,
122 | required this.icon,
123 | required this.onTap,
124 | required this.gradientColors,
125 | this.disabledGradientColors = const [Colors.grey, Colors.black54],
126 | this.isEnabled = true,
127 | super.key,
128 | }) : assert(
129 | gradientColors.length == 2,
130 | 'gradient supports only 2 colors (start and end)',
131 | );
132 |
133 | final String title;
134 | final IconData icon;
135 | final VoidCallback? onTap;
136 | final List gradientColors;
137 | final List disabledGradientColors;
138 | final bool isEnabled;
139 |
140 | @override
141 | Widget build(BuildContext context) {
142 | return InkWell(
143 | onTap: isEnabled ? onTap : null,
144 | borderRadius: BorderRadius.circular(8),
145 | child: Opacity(
146 | opacity: isEnabled ? 1 : 0.7,
147 | child: Ink(
148 | padding: const EdgeInsets.all(12),
149 | decoration: BoxDecoration(
150 | borderRadius: BorderRadius.circular(8),
151 | gradient: LinearGradient(
152 | begin: Alignment.bottomLeft,
153 | end: Alignment.topRight,
154 | colors: isEnabled ? gradientColors : disabledGradientColors,
155 | stops: const [0.2806, 0.984],
156 | ),
157 | ),
158 | child: Column(
159 | crossAxisAlignment: CrossAxisAlignment.start,
160 | mainAxisAlignment: MainAxisAlignment.spaceBetween,
161 | children: [
162 | Icon(
163 | icon,
164 | color: Colors.white,
165 | size: 32,
166 | ),
167 | const SizedBox(height: 16),
168 | Flexible(
169 | child: Text(
170 | title,
171 | style: const TextStyle(color: Colors.white),
172 | overflow: TextOverflow.ellipsis,
173 | ),
174 | ),
175 | ],
176 | ),
177 | ),
178 | ),
179 | );
180 | }
181 | }
182 |
--------------------------------------------------------------------------------
/lib/app/router.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/foundation.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter_ai_examples/app/app_settings/app_settings_page.dart';
4 | import 'package:flutter_ai_examples/app/home_page.dart';
5 | import 'package:flutter_ai_examples/mistral_ai_book_search_example/mistralai_book_search_page.dart';
6 | import 'package:flutter_ai_examples/mistral_ai_chat_example/mistralai_chat_page.dart';
7 | import 'package:flutter_ai_examples/mistral_ai_llm_controller_example/mistral_ai_llm_controller_page.dart';
8 | import 'package:flutter_ai_examples/mistral_ai_summary_example/mistralai_summary_page.dart';
9 | import 'package:go_router/go_router.dart';
10 |
11 | part 'router.g.dart';
12 |
13 | final router = GoRouter(routes: $appRoutes);
14 |
15 | @TypedGoRoute(
16 | path: '/',
17 | routes: [
18 | TypedGoRoute(path: 'app-settings'),
19 | TypedGoRoute(path: 'mistralai-chat'),
20 | TypedGoRoute(path: 'mistralai-summary'),
21 | TypedGoRoute(path: 'mistralai-llm-controller'),
22 | TypedGoRoute(path: 'mistralai-book-search'),
23 | ],
24 | )
25 | class HomeRoute extends GoRouteData {
26 | const HomeRoute();
27 |
28 | @override
29 | Widget build(BuildContext context, GoRouterState state) => const HomePage();
30 | }
31 |
32 | class AppSettingsRoute extends GoRouteData {
33 | const AppSettingsRoute();
34 |
35 | @override
36 | Page buildPage(BuildContext context, GoRouterState state) {
37 | return _buildPage(const AppSettingsPage());
38 | }
39 | }
40 |
41 | class MistralAIChatRoute extends GoRouteData {
42 | const MistralAIChatRoute();
43 |
44 | @override
45 | Page buildPage(BuildContext context, GoRouterState state) {
46 | return _buildPage(const MistralAIChatPage());
47 | }
48 | }
49 |
50 | class MistralAISummaryRoute extends GoRouteData {
51 | const MistralAISummaryRoute();
52 |
53 | @override
54 | Page buildPage(BuildContext context, GoRouterState state) {
55 | return _buildPage(const MistralAISummaryPage());
56 | }
57 | }
58 |
59 | class MistralAILlmControllerRoute extends GoRouteData {
60 | const MistralAILlmControllerRoute();
61 |
62 | @override
63 | Page buildPage(BuildContext context, GoRouterState state) {
64 | return _buildPage(const MistralAILlmControllerPage());
65 | }
66 | }
67 |
68 | class MistralAIBookSearchRoute extends GoRouteData {
69 | const MistralAIBookSearchRoute();
70 |
71 | @override
72 | Page buildPage(BuildContext context, GoRouterState state) {
73 | return _buildPage(const MistralAIBookSearchPage());
74 | }
75 | }
76 |
77 | // builds page with no transition for web and material page for other platforms
78 | Page _buildPage(Widget child) {
79 | if (kIsWeb) {
80 | return NoTransitionPage(child: child);
81 | }
82 | return MaterialPage(child: child);
83 | }
84 |
--------------------------------------------------------------------------------
/lib/app/router.g.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 |
3 | part of 'router.dart';
4 |
5 | // **************************************************************************
6 | // GoRouterGenerator
7 | // **************************************************************************
8 |
9 | List get $appRoutes => [
10 | $homeRoute,
11 | ];
12 |
13 | RouteBase get $homeRoute => GoRouteData.$route(
14 | path: '/',
15 | factory: $HomeRouteExtension._fromState,
16 | routes: [
17 | GoRouteData.$route(
18 | path: 'app-settings',
19 | factory: $AppSettingsRouteExtension._fromState,
20 | ),
21 | GoRouteData.$route(
22 | path: 'mistralai-chat',
23 | factory: $MistralAIChatRouteExtension._fromState,
24 | ),
25 | GoRouteData.$route(
26 | path: 'mistralai-summary',
27 | factory: $MistralAISummaryRouteExtension._fromState,
28 | ),
29 | GoRouteData.$route(
30 | path: 'mistralai-llm-controller',
31 | factory: $MistralAILlmControllerRouteExtension._fromState,
32 | ),
33 | GoRouteData.$route(
34 | path: 'mistralai-book-search',
35 | factory: $MistralAIBookSearchRouteExtension._fromState,
36 | ),
37 | ],
38 | );
39 |
40 | extension $HomeRouteExtension on HomeRoute {
41 | static HomeRoute _fromState(GoRouterState state) => const HomeRoute();
42 |
43 | String get location => GoRouteData.$location(
44 | '/',
45 | );
46 |
47 | void go(BuildContext context) => context.go(location);
48 |
49 | Future push(BuildContext context) => context.push(location);
50 |
51 | void pushReplacement(BuildContext context) =>
52 | context.pushReplacement(location);
53 |
54 | void replace(BuildContext context) => context.replace(location);
55 | }
56 |
57 | extension $AppSettingsRouteExtension on AppSettingsRoute {
58 | static AppSettingsRoute _fromState(GoRouterState state) =>
59 | const AppSettingsRoute();
60 |
61 | String get location => GoRouteData.$location(
62 | '/app-settings',
63 | );
64 |
65 | void go(BuildContext context) => context.go(location);
66 |
67 | Future push(BuildContext context) => context.push(location);
68 |
69 | void pushReplacement(BuildContext context) =>
70 | context.pushReplacement(location);
71 |
72 | void replace(BuildContext context) => context.replace(location);
73 | }
74 |
75 | extension $MistralAIChatRouteExtension on MistralAIChatRoute {
76 | static MistralAIChatRoute _fromState(GoRouterState state) =>
77 | const MistralAIChatRoute();
78 |
79 | String get location => GoRouteData.$location(
80 | '/mistralai-chat',
81 | );
82 |
83 | void go(BuildContext context) => context.go(location);
84 |
85 | Future push(BuildContext context) => context.push(location);
86 |
87 | void pushReplacement(BuildContext context) =>
88 | context.pushReplacement(location);
89 |
90 | void replace(BuildContext context) => context.replace(location);
91 | }
92 |
93 | extension $MistralAISummaryRouteExtension on MistralAISummaryRoute {
94 | static MistralAISummaryRoute _fromState(GoRouterState state) =>
95 | const MistralAISummaryRoute();
96 |
97 | String get location => GoRouteData.$location(
98 | '/mistralai-summary',
99 | );
100 |
101 | void go(BuildContext context) => context.go(location);
102 |
103 | Future push(BuildContext context) => context.push(location);
104 |
105 | void pushReplacement(BuildContext context) =>
106 | context.pushReplacement(location);
107 |
108 | void replace(BuildContext context) => context.replace(location);
109 | }
110 |
111 | extension $MistralAILlmControllerRouteExtension on MistralAILlmControllerRoute {
112 | static MistralAILlmControllerRoute _fromState(GoRouterState state) =>
113 | const MistralAILlmControllerRoute();
114 |
115 | String get location => GoRouteData.$location(
116 | '/mistralai-llm-controller',
117 | );
118 |
119 | void go(BuildContext context) => context.go(location);
120 |
121 | Future push(BuildContext context) => context.push(location);
122 |
123 | void pushReplacement(BuildContext context) =>
124 | context.pushReplacement(location);
125 |
126 | void replace(BuildContext context) => context.replace(location);
127 | }
128 |
129 | extension $MistralAIBookSearchRouteExtension on MistralAIBookSearchRoute {
130 | static MistralAIBookSearchRoute _fromState(GoRouterState state) =>
131 | const MistralAIBookSearchRoute();
132 |
133 | String get location => GoRouteData.$location(
134 | '/mistralai-book-search',
135 | );
136 |
137 | void go(BuildContext context) => context.go(location);
138 |
139 | Future push(BuildContext context) => context.push(location);
140 |
141 | void pushReplacement(BuildContext context) =>
142 | context.pushReplacement(location);
143 |
144 | void replace(BuildContext context) => context.replace(location);
145 | }
146 |
--------------------------------------------------------------------------------
/lib/app/theme.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:google_fonts/google_fonts.dart';
3 |
4 | ThemeData lightTheme() {
5 | final baseTheme = ThemeData.light();
6 | return baseTheme.copyWith(
7 | textTheme: GoogleFonts.interTextTheme(baseTheme.textTheme),
8 | sliderTheme: baseTheme.sliderTheme.copyWith(
9 | trackHeight: 10,
10 | thumbShape: CustomThumb(
11 | thumbColor: baseTheme.primaryColor,
12 | spacerColor: baseTheme.colorScheme.surface,
13 | ),
14 | overlayShape: const RoundSliderOverlayShape(overlayRadius: 16),
15 | activeTrackColor: baseTheme.primaryColor,
16 | inactiveTrackColor: baseTheme.primaryColor.withOpacity(0.5),
17 | ),
18 | );
19 | }
20 |
21 | class CustomThumb extends SliderComponentShape {
22 | const CustomThumb({
23 | required this.spacerColor,
24 | required this.thumbColor,
25 | this.thumbWidth = 4,
26 | this.thumbHeight = 44,
27 | this.thumbRadius = 2,
28 | this.space = 6,
29 | });
30 |
31 | final double thumbWidth;
32 | final double thumbHeight;
33 | final double thumbRadius;
34 | final double space;
35 | final Color thumbColor;
36 | final Color spacerColor;
37 |
38 | @override
39 | Size getPreferredSize(bool isEnabled, bool isDiscrete) {
40 | return Size(thumbWidth + space * 2, thumbHeight);
41 | }
42 |
43 | @override
44 | void paint(
45 | PaintingContext context,
46 | Offset center, {
47 | required Animation activationAnimation,
48 | required Animation enableAnimation,
49 | required bool isDiscrete,
50 | required TextPainter labelPainter,
51 | required RenderBox parentBox,
52 | required SliderThemeData sliderTheme,
53 | required TextDirection textDirection,
54 | required double value,
55 | required double textScaleFactor,
56 | required Size sizeWithOverflow,
57 | }) {
58 | final canvas = context.canvas;
59 |
60 | //Prepare the spacer rect
61 | final spacerRect = Rect.fromCenter(
62 | center: center,
63 | width: thumbWidth + space,
64 | height: thumbHeight,
65 | );
66 | final spacerPaint = Paint()
67 | ..color = spacerColor
68 | ..style = PaintingStyle.fill;
69 |
70 | //Prepare the thumb rect
71 | final rect = Rect.fromCenter(
72 | center: center,
73 | width: thumbWidth,
74 | height: thumbHeight,
75 | );
76 | final rrect = RRect.fromRectAndRadius(
77 | rect,
78 | Radius.circular(thumbRadius),
79 | );
80 | final paint = Paint()
81 | ..color = thumbColor
82 | ..style = PaintingStyle.fill;
83 |
84 | //Draw the spacer and the thumb on the canvas
85 | canvas
86 | ..drawRect(spacerRect, spacerPaint)
87 | ..drawRRect(rrect, paint);
88 | }
89 | }
90 |
91 | /// Theme with a darker background color.
92 | //
93 | // This theme is not fully complete for all the components.
94 | class DarkerBackgroundTheme extends StatelessWidget {
95 | const DarkerBackgroundTheme({required this.child, super.key});
96 |
97 | final Widget child;
98 |
99 | @override
100 | Widget build(BuildContext context) {
101 | // This theme is not fully complete.
102 | // There are updates to the components that are used
103 | // in the current examples.
104 | // If any component is not looking good then update it here
105 | final baseTheme = Theme.of(context);
106 | final darkBackgroundThemeData = ThemeData.from(
107 | colorScheme: baseTheme.colorScheme.copyWith(
108 | // change the background color to a darker color for all widgets
109 | // using this color
110 | background: baseTheme.colorScheme.surfaceVariant,
111 | //change the surface tint for alert dialogs
112 | surfaceTint: baseTheme.colorScheme.surfaceVariant,
113 | ),
114 | textTheme: baseTheme.textTheme,
115 | ).copyWith(
116 | // updates to the components theme to better work with the new
117 | // background color
118 | appBarTheme: baseTheme.appBarTheme.copyWith(
119 | backgroundColor: baseTheme.colorScheme.surfaceVariant,
120 | ),
121 | listTileTheme: baseTheme.listTileTheme.copyWith(
122 | tileColor: baseTheme.colorScheme.surface,
123 | ),
124 | sliderTheme: baseTheme.sliderTheme.copyWith(
125 | trackHeight: 10,
126 | thumbShape: CustomThumb(
127 | thumbColor: baseTheme.primaryColor,
128 | spacerColor: baseTheme.colorScheme.surface,
129 | ),
130 | ),
131 | );
132 | return Theme(data: darkBackgroundThemeData, child: child);
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/lib/main.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/foundation.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter/services.dart';
4 | import 'package:flutter_ai_examples/app/app.dart';
5 | import 'package:shared_preferences/shared_preferences.dart';
6 |
7 | void main() async {
8 | WidgetsFlutterBinding.ensureInitialized();
9 | LicenseRegistry.addLicense(() async* {
10 | // register the license for the Google Fonts
11 | final license = await rootBundle.loadString('google_fonts/OFL.txt');
12 | yield LicenseEntryWithLineBreaks(['google_fonts'], license);
13 | });
14 |
15 | final sharedPreferences = await SharedPreferences.getInstance();
16 |
17 | runApp(
18 | MyApp(sharedPreferences: sharedPreferences),
19 | );
20 | }
21 |
--------------------------------------------------------------------------------
/lib/mistral_ai_book_search_example/algebra.dart:
--------------------------------------------------------------------------------
1 | import 'dart:math';
2 |
3 | // use it find the most similar text in the book based on embeddings
4 | // of the question and the book fragment
5 | double calculateCosineSimilarity(List vectorA, List vectorB) {
6 | final dotProduct = calculateDotProduct(vectorA, vectorB);
7 | final magnitudeA = calculateMagnitude(vectorA);
8 | final magnitudeB = calculateMagnitude(vectorB);
9 | return dotProduct / (magnitudeA * magnitudeB);
10 | }
11 |
12 | double calculateDotProduct(List vectorA, List vectorB) {
13 | assert(vectorA.length == vectorB.length, 'Vectors must be of same length');
14 | var dotProduct = 0.0;
15 | for (var i = 0; i < vectorA.length; i++) {
16 | dotProduct += vectorA[i] * vectorB[i];
17 | }
18 | return dotProduct;
19 | }
20 |
21 | double calculateMagnitude(List vector) => sqrt(
22 | vector.fold(
23 | 0,
24 | (previousValue, element) => previousValue + element * element,
25 | ),
26 | );
27 |
--------------------------------------------------------------------------------
/lib/mistral_ai_book_search_example/book_search.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 |
3 | import 'package:flutter/foundation.dart';
4 | import 'package:flutter/services.dart';
5 | import 'package:flutter_ai_examples/mistral_ai_book_search_example/algebra.dart';
6 | import 'package:flutter_ai_examples/mistral_ai_book_search_example/models.dart';
7 | import 'package:flutter_ai_examples/mistral_tokenizer/mistral_tokenizer.dart';
8 | import 'package:mistralai_client_dart/mistralai_client_dart.dart';
9 |
10 | // loads and keeps information about the book that is used for searching
11 | // and provides methods to find answers to questions
12 | class BookSearch {
13 | BookSearch({
14 | required this.mistralAIClient,
15 | required this.tokenizer,
16 | this.bookTitle = 'Twenty Thousands Leagues Under the Sea',
17 | this.bookSearchDataAssetPath = 'assets/20k_leages_under_the_sea_verne.json',
18 | });
19 |
20 | final MistralAIClient mistralAIClient;
21 | final MistralTokenizer tokenizer;
22 | late SearchData _searchData;
23 | bool _init = false;
24 |
25 | // change this to change the book name
26 | final String bookTitle;
27 | // change this to change the book data (generated with prepare_data.dart)
28 | final String bookSearchDataAssetPath;
29 |
30 | Future init({bool force = false}) async {
31 | if (_init && !force) {
32 | debugPrint('BookSearch already initialized');
33 | return;
34 | }
35 | debugPrint('Initializing BookSearch');
36 | final fileContent = await rootBundle.loadString(bookSearchDataAssetPath);
37 | final json = jsonDecode(fileContent) as Map;
38 | _searchData = SearchData.fromJson(json);
39 | _init = true;
40 | debugPrint('Finished initializing BookSearch');
41 | }
42 |
43 | Future> _createKeywordsFromQuestion(String userQuestion) async {
44 | const assistantRole = '''
45 | You are a helpful assistant designed to create keywords from a question.
46 | The keywords are used to search for the answer in the book.
47 | Return the keywords in a list of strings: ["keyword1", "keyword2", ...]
48 | Do not return anything else than list.
49 | Do not explain anything.
50 | ''';
51 | final response = await mistralAIClient.chat(
52 | ChatParams(
53 | model: 'mistral-tiny',
54 | messages: [
55 | const ChatMessage(role: 'system', content: assistantRole),
56 | ChatMessage(role: 'user', content: userQuestion),
57 | ],
58 | // temperature is set to 0.1 and not 0
59 | // because Mistral API does not allow 0
60 | // when topP is not 1 (default value)
61 | temperature: 0.1, // less randomness
62 | topP: 0.1, // choose from top 10% of the most likely tokens
63 | ),
64 | );
65 | final content = response.choices.first.message.content;
66 |
67 | // extract the list of keywords from the response
68 | // (ai can return more text than a list)
69 | final regex = RegExp(r'\[.*?\]');
70 | final match = regex.firstMatch(content);
71 | final listAsString = match?.group(0);
72 | if (listAsString == null) {
73 | return [];
74 | }
75 | return List.from(jsonDecode(listAsString) as List);
76 | }
77 |
78 | Future _getAnswerToQuestion({
79 | required String userQuestion,
80 | required List keywordsFromQuestion,
81 | required List fragments,
82 | }) async {
83 | final answerRole = '''
84 | You are a helpful assistant designed to answer questions from a book.
85 | You are given list of book fragments related to the question.
86 | Use knowledge only from given fragments to answer the question.
87 | Here are some keywords related to question: ${keywordsFromQuestion.join(', ')}.
88 | Return the answer as a plain text.
89 | ''';
90 | final response = await mistralAIClient.chat(
91 | ChatParams(
92 | model: 'mistral-medium',
93 | messages: [
94 | ChatMessage(role: 'system', content: answerRole),
95 | ...fragments.map((e) => ChatMessage(role: 'system', content: e)),
96 | ChatMessage(role: 'user', content: userQuestion),
97 | ],
98 | // temperature is set to 0.1 and not 0
99 | // because Mistral API does not allow 0
100 | // when topP is not 1 (default value)
101 | temperature: 0.1, // less randomness
102 | topP: 0.5, // choose from top 50% of the most likely tokens
103 | ),
104 | );
105 | return response.choices.first.message.content;
106 | }
107 |
108 | // step 1: Take question about book from user
109 | // and ask AI to create keywords from it
110 | // step 2: Search for fragments with keywords
111 | // step 3: Take fragments and ask AI to answer original question
112 | Future findAnswer(
113 | String question, {
114 | // max number of fragments to use for answer
115 | int resultCount = 5,
116 | }) async {
117 | final keywords = await _createKeywordsFromQuestion(question);
118 | final keywordsString = keywords.join(' ');
119 | debugPrint('keywords: $keywordsString');
120 | // Get embedding for question + keywords.
121 | // Keywords are added to the question to help AI
122 | // find most relevant fragments. To make a stronger connection
123 | // between question and keywords we add them to the question.
124 | //
125 | // This will be used to find the most similar fragments
126 | // to the question
127 | // (we use the same embedding model as for the book fragments).
128 | //
129 | // Later on we will use found fragments to answer the question using AI.
130 | final questionEmbedding = await _getEmbedding('$question $keywordsString');
131 | final fragmentToQuestionSimilarities = [];
132 | for (var i = 0; i < _searchData.fragments.length; i++) {
133 | final fragmentEmbedding = _searchData.fragmentEmbeddings[i];
134 | final similarity = calculateCosineSimilarity(
135 | questionEmbedding,
136 | fragmentEmbedding,
137 | );
138 | fragmentToQuestionSimilarities.add(
139 | FragmentSimilarity(i, _searchData.fragments[i], similarity),
140 | );
141 | }
142 | // sort by similarity descending
143 | final sortedSimilarities = fragmentToQuestionSimilarities
144 | ..sort((a, b) => b.similarity.compareTo(a.similarity));
145 | final mostRelevantSimilarities =
146 | sortedSimilarities.take(resultCount).toList();
147 | final mostRelevantFragments =
148 | mostRelevantSimilarities.map((e) => e.text).toList();
149 | final answer = await _getAnswerToQuestion(
150 | userQuestion: question,
151 | keywordsFromQuestion: keywords,
152 | fragments: mostRelevantFragments,
153 | );
154 |
155 | return Answer(
156 | text: answer,
157 | question: question,
158 | keywords: keywords,
159 | fragmentSimilarities: mostRelevantSimilarities,
160 | );
161 | }
162 |
163 | Future> _getEmbedding(String text) async {
164 | final queryEmbedding = await mistralAIClient
165 | .embeddings(EmbeddingParams(model: 'mistral-embed', input: [text]));
166 | if (queryEmbedding.data.isEmpty) {
167 | throw Exception('No embedding found for text: $text');
168 | }
169 | return queryEmbedding.data.first.embedding;
170 | }
171 |
172 | List fragments() => _searchData.fragments;
173 |
174 | List> fragmentsTokens() => _searchData.fragmentTokens;
175 | }
176 |
--------------------------------------------------------------------------------
/lib/mistral_ai_book_search_example/models.dart:
--------------------------------------------------------------------------------
1 | import 'package:json_annotation/json_annotation.dart';
2 |
3 | part 'models.g.dart';
4 |
5 | // answer to the question about the book
6 | class Answer {
7 | Answer({
8 | required this.text,
9 | required this.question,
10 | required this.keywords,
11 | required this.fragmentSimilarities,
12 | });
13 |
14 | final String text;
15 | final String question;
16 | final List keywords;
17 | final List fragmentSimilarities;
18 | }
19 |
20 | class FragmentSimilarity {
21 | FragmentSimilarity(this.fragmentIndex, this.text, this.similarity);
22 |
23 | final int fragmentIndex;
24 | final String text;
25 | final double similarity;
26 |
27 | @override
28 | String toString() =>
29 | 'FragmentSimilarity(text: $text, similarity: $similarity)';
30 | }
31 |
32 | // keeps information about the book that is used for searching
33 | @JsonSerializable()
34 | class SearchData {
35 | SearchData(this.fragments, this.fragmentTokens, this.fragmentEmbeddings);
36 |
37 | factory SearchData.fromJson(Map json) =>
38 | _$SearchDataFromJson(json);
39 |
40 | Map toJson() => _$SearchDataToJson(this);
41 |
42 | final List fragments;
43 | final List> fragmentTokens;
44 | final List> fragmentEmbeddings;
45 | }
46 |
--------------------------------------------------------------------------------
/lib/mistral_ai_book_search_example/models.g.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 |
3 | part of 'models.dart';
4 |
5 | // **************************************************************************
6 | // JsonSerializableGenerator
7 | // **************************************************************************
8 |
9 | SearchData _$SearchDataFromJson(Map json) => SearchData(
10 | (json['fragments'] as List).map((e) => e as String).toList(),
11 | (json['fragmentTokens'] as List)
12 | .map((e) => (e as List).map((e) => e as int).toList())
13 | .toList(),
14 | (json['fragmentEmbeddings'] as List)
15 | .map((e) =>
16 | (e as List).map((e) => (e as num).toDouble()).toList())
17 | .toList(),
18 | );
19 |
20 | Map _$SearchDataToJson(SearchData instance) =>
21 | {
22 | 'fragments': instance.fragments,
23 | 'fragmentTokens': instance.fragmentTokens,
24 | 'fragmentEmbeddings': instance.fragmentEmbeddings,
25 | };
26 |
--------------------------------------------------------------------------------
/lib/mistral_ai_book_search_example/prepare_data.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 | import 'dart:io';
3 |
4 | import 'package:flutter_ai_examples/mistral_ai_book_search_example/models.dart';
5 | import 'package:flutter_ai_examples/mistral_tokenizer/mistral_tokenizer.dart';
6 | import 'package:langchain/langchain.dart';
7 | import 'package:mistralai_client_dart/mistralai_client_dart.dart';
8 |
9 | // tokenizer and client are here to be sure the script
10 | // can be used as a standalone script
11 |
12 | final mistralTokenizer = MistralTokenizer.fromBase64();
13 |
14 | const String mistralAIApiKey = String.fromEnvironment('MISTRAL_AI_API_KEY');
15 |
16 | final mistralAIClient = MistralAIClient(apiKey: mistralAIApiKey);
17 |
18 | // This file is supposed to be run from the root of the project
19 | // to generate data for book search example.
20 | //
21 | // Do not use this file in the app.
22 | //
23 | // Use this command to run this file from the root of the project:
24 | // dart run --define=MISTRAL_AI_API_KEY=YourAPIKey lib/mistral_ai_book_search_example/prepare_data.dart
25 | //
26 | // To generate data for different book, change the fileName variable
27 | // and put the text file in assets folder.
28 | void main() async {
29 | final mainStopWatch = Stopwatch()..start();
30 | const fileName = '20k_leages_under_the_sea_verne';
31 | final textFile = File('assets/$fileName.txt');
32 | _log('=== Preparing search data ===');
33 | _log('text file: ${textFile.absolute.path}');
34 | _log('=======');
35 |
36 | final searchData = await prepareSearchData(textFile);
37 |
38 | _log('=== Search data generated ===');
39 | _log('fragments length: ${searchData.fragments.length}');
40 | _log('fragments tokens length: ${searchData.fragmentTokens.length}');
41 | _log('embeddings length: ${searchData.fragmentEmbeddings.length}');
42 | _log('=======');
43 | final jsonFile = File('assets/$fileName.json')
44 | ..writeAsStringSync(jsonEncode(searchData.toJson()));
45 | _log('Results saved to ${jsonFile.absolute.path}');
46 |
47 | mainStopWatch.stop();
48 | _log('Finished in ${mainStopWatch.elapsed}');
49 | }
50 |
51 | int _lastEmbeddingRequestTimeMs = 0;
52 |
53 | // get embeddings with a delay between requests to not exceed the
54 | // MistralAI API rate limit of 2 requests per second
55 | Future>> getEmbedding(List texts) async {
56 | final now = DateTime.now().millisecondsSinceEpoch;
57 | // 500ms is the minimum time between requests
58 | final delay = 500 - (now - _lastEmbeddingRequestTimeMs);
59 | if (delay > 0) {
60 | await Future.delayed(Duration(milliseconds: delay), () {});
61 | }
62 |
63 | final embeddingsResult = await mistralAIClient
64 | .embeddings(EmbeddingParams(model: 'mistral-embed', input: texts));
65 | _lastEmbeddingRequestTimeMs = DateTime.now().millisecondsSinceEpoch;
66 | if (embeddingsResult.data.isEmpty) {
67 | throw Exception('No embedding found for query');
68 | }
69 | return embeddingsResult.data.map((e) => e.embedding).toList();
70 | }
71 |
72 | // prepare data for search
73 | // takes a text file and returns a SearchData object
74 | // SearchData contains the text fragments, their tokens and embeddings
75 | Future prepareSearchData(
76 | File textFile, {
77 | // process only first n fragments - for testing
78 | int? processFirstNFragments,
79 | }) async {
80 | assert(
81 | processFirstNFragments == null || processFirstNFragments > 0,
82 | 'processFirstNFragments must be null or > 0',
83 | );
84 | if (!textFile.existsSync()) {
85 | throw Exception(
86 | 'File does not exist: ${textFile.path}. Check for typos. '
87 | 'Current directory: ${Directory.current.path}',
88 | );
89 | }
90 | // initialize space for fragments, tokens and embeddings
91 | final fragmentsTokens = >[];
92 | final embeddings = >[];
93 | var fragments = [];
94 |
95 | await timedOperation('Load text file and split to fragments', () async {
96 | final bookText = await textFile.readAsString();
97 | final splitter = TextToFragmentSplitter();
98 | final splittedText = splitter.split(bookText);
99 | if (processFirstNFragments != null) {
100 | fragments = splittedText.take(processFirstNFragments).toList();
101 | } else {
102 | fragments = splittedText;
103 | }
104 | });
105 |
106 | // tokenize fragments
107 | timedOperationSync('Tokenization', () {
108 | final splitLength = fragments.length;
109 | for (var i = 0; i < splitLength; i++) {
110 | final fragment = fragments[i];
111 | // encode fragment to tokens and add to list
112 | final tokenizedFragment = mistralTokenizer.encode(fragment);
113 | fragmentsTokens.add(tokenizedFragment);
114 | final number = i + 1;
115 | _log('tokenized fragment $number/$splitLength');
116 | }
117 | });
118 |
119 | // get embeddings for fragments
120 | //
121 | // batch size value from error message of MistralAI API
122 | final fragmentsBatches = >[];
123 | timedOperationSync('Batches for embeddings creation', () {
124 | // max token size handled by API is 16384
125 | // but we use a smaller value to have a margin in case
126 | // our tokenizer returns different number of tokens for the same text
127 | const batchSize = 15000;
128 | var batchCurrentSize = 0;
129 | var batch = [];
130 | var i = 0;
131 | while (i < fragmentsTokens.length) {
132 | final fragmentTokens = fragmentsTokens[i];
133 | final tokensLength = fragmentTokens.length;
134 | if (batchCurrentSize + tokensLength > batchSize) {
135 | // batch if full - add to batches
136 | // and create new batch
137 | fragmentsBatches.add(batch);
138 | batch = [];
139 | batchCurrentSize = 0;
140 | } else {
141 | batch.add(fragments[i]);
142 | batchCurrentSize += tokensLength;
143 | i++;
144 | }
145 | }
146 | // add last batch
147 | if (batch.isNotEmpty) {
148 | fragmentsBatches.add(batch);
149 | }
150 | });
151 |
152 | // get embeddings for batches
153 | await timedOperation('Embeddings for all batches creation', () async {
154 | for (var i = 0; i < fragmentsBatches.length; i++) {
155 | final batch = fragmentsBatches[i];
156 | final batchEmbeddings = await getEmbedding(batch);
157 | embeddings.addAll(batchEmbeddings);
158 | _log('Embeddings for batch ${i + 1}/${fragmentsBatches.length} created');
159 | }
160 | });
161 |
162 | checkLengths([fragments, fragmentsTokens, embeddings]);
163 | return SearchData(fragments, fragmentsTokens, embeddings);
164 | }
165 |
166 | void _log(String message) {
167 | // ignore: avoid_print
168 | print(message);
169 | }
170 |
171 | Future timedOperation(
172 | String operationName,
173 | Future Function() operation,
174 | ) async {
175 | final timer = Stopwatch()..start();
176 | await operation();
177 | timer.stop();
178 | _log('$operationName completed in ${timer.elapsedMilliseconds}ms');
179 | }
180 |
181 | void timedOperationSync(
182 | String operationName,
183 | void Function() operation,
184 | ) {
185 | final timer = Stopwatch()..start();
186 | operation();
187 | timer.stop();
188 | _log('$operationName completed in ${timer.elapsedMilliseconds}ms');
189 | }
190 |
191 | void checkLengths(List> lists) {
192 | final length = lists.first.length;
193 | for (final list in lists) {
194 | if (list.length != length) {
195 | throw Exception('Lists must have the same length');
196 | }
197 | }
198 | }
199 |
200 | /// splits text into smaller chunks
201 | class TextToFragmentSplitter {
202 | List split(
203 | String text, {
204 | int chunkSize = 1000,
205 | int chunkOverlap = 100,
206 | }) {
207 | final textSplitter = RecursiveCharacterTextSplitter(
208 | chunkSize: chunkSize,
209 | chunkOverlap: chunkOverlap,
210 | );
211 | final splitText = textSplitter.splitText(text);
212 | return splitText;
213 | }
214 | }
215 |
--------------------------------------------------------------------------------
/lib/mistral_ai_chat_example/README.md:
--------------------------------------------------------------------------------
1 | # Mistral AI Chat example
2 |
3 | This is an example of AI chat similar to OpenAI's ChatGPT but using Mistral AI model.
4 |
5 | Example is using [mistralai_client_dart](https://pub.dev/packages/mistralai_client_dart) to access Mistral model using REST API.
6 |
7 |
8 |
9 | ## Features
10 |
11 | 1. Remembers the chat history
12 | 2. Option to change between streaming and full response mode
13 |
14 | ## How to open example
15 |
16 | Follow instruction on how to run example app from [projects README.md](../../README.md).
17 | When app is running go to `MistralAI Chat example` page.
18 |
19 | ## How it works?
20 |
21 | Using `MistralAIClient` we request chat responses from API.
22 |
23 | The API takes a list of messages with a role for each message.
24 |
25 | User messages have a role of `user` and chat responses have `assistant` role.
26 |
27 | So for AI to have a context of the chat we are keeping the history of the chat (all messages) in memory and send it back to AI with new user message each time.
28 |
29 | For example:
30 |
31 | This is what will be send when user sends first message.
32 |
33 | ```dart
34 | final params = ChatParams(
35 | model: 'mistral-small',
36 | messages: [
37 | ChatMessage(role: 'user', content: 'Hi chat!'),
38 | ],
39 | );
40 | mistralAIClient.chat(params);
41 | ```
42 |
43 | Then we get a response like (omitting the exact code and model for chat response):
44 |
45 | ```text
46 | Hey how can I help you today?
47 | ```
48 |
49 | So next time user sends a message we will send now a chat like this:
50 |
51 | ```dart
52 | final params = ChatParams(
53 | model: 'mistral-small',
54 | messages: [
55 | ChatMessage(role: 'user', content: 'Hi chat!'),
56 | ChatMessage(role: 'assistant', content: 'Hey how can I help you today?'),
57 | ChatMessage(role: 'user', content: 'Can you help me write an Flutter example app about AI using Mistral API?'),
58 | ],
59 | );
60 | mistralAIClient.chat(params);
61 | ```
62 |
63 | Basically every time we are doing back and forth with response and answer send back to the API. The whole conversation context is kept. This is required to be able for AI to answer question about previous questions or answers that happened during chat.
64 |
65 | ## Room for improvements
66 |
67 | The example is a simple example and showcase how chat can be built. There are few things that can be improved to make it more real world.
68 |
69 | 1. Make sure that maximum count of tokens in a chat is not exceeded. Mistral can process max 32k tokens at a time so the longer the history is then we are closer and closer to this limit. There are few strategies to do that:
70 | - To save space we could generate a summary of the older messages in chat and use this as context instead of sending the exact chat history.
71 | - We could send only last X messages instead of the whole conversation
72 | - Mix of above
73 | 2. Save chat history to database instead of keeping it in memory
74 | 3. Try to cache answers and maybe reuse (answer with cached response to the same question) them to lower the cost of API usage.
75 |
--------------------------------------------------------------------------------
/lib/mistral_ai_chat_example/chat_model.dart:
--------------------------------------------------------------------------------
1 | part of 'mistralai_chat_page.dart';
2 |
3 | /// Keeps state of the chat
4 | class _ChatModel extends ChangeNotifier {
5 | _ChatModel(this._mistralAIClient);
6 |
7 | final MistralAIClient _mistralAIClient;
8 |
9 | // internal queue to store chat history items
10 | final _chatHistory = Queue<_ChatHistoryItem>();
11 |
12 | // make sure to not allow modification on the chat history items
13 | List<_ChatHistoryItem> get chatHistory => List.unmodifiable(
14 | _chatHistory.toList(),
15 | );
16 |
17 | // stream subscription to cancel it on dispose
18 | StreamSubscription? _streamSub;
19 |
20 | bool _isGenerationInProgress = false;
21 |
22 | bool get waitingForResponse => _isGenerationInProgress;
23 |
24 | // keep track of the error to show it on the screen
25 | String? error;
26 |
27 | // keep track of the streaming state setting
28 | bool _streaming = true;
29 | bool get streaming => _streaming;
30 | set streaming(bool value) {
31 | _streaming = value;
32 | notifyListeners();
33 | }
34 |
35 | void add(_ChatHistoryItem item) {
36 | if (_isGenerationInProgress) {
37 | // skip adding new message if there is a stream subscription
38 | return;
39 | }
40 | _isGenerationInProgress = true;
41 | error = null;
42 | _chatHistory.addLast(item);
43 | // notify ui about added user's message and updating loading state
44 | notifyListeners();
45 |
46 | // TODO(lgawron): consider using tokenizer to count tokens
47 | final params = ChatParams(
48 | model: 'mistral-small',
49 | messages: [
50 | // TODO(lgawron): consider taking only subset of the chat history
51 | // to reduce the payload size, improve performance and reduce cost
52 |
53 | // convert chat history items to chat messages
54 | // to provide them to the ai chat
55 | // this is needed to keep pass the context to the ai chat
56 | ..._chatHistory.map(_historyItemToChatMessage),
57 | ],
58 | );
59 |
60 | if (streaming) {
61 | _sendToMistralStreaming(params);
62 | } else {
63 | _sendToMistral(params);
64 | }
65 | }
66 |
67 | void _sendToMistral(ChatParams params) {
68 | _mistralAIClient.chat(params).then(
69 | (value) {
70 | final botMessage = value.choices[0].message.content;
71 | _chatHistory.addLast(_ChatHistoryItem.botMessage(botMessage));
72 | _generationDone();
73 | },
74 | onError: _handleError,
75 | );
76 | }
77 |
78 | void _sendToMistralStreaming(ChatParams params) {
79 | _streamSub = _mistralAIClient
80 | .chatStream(params)
81 |
82 | /// delay the response to make it look like a real chat
83 | .asyncMap(
84 | (event) =>
85 | Future.delayed(const Duration(milliseconds: 150), () => event),
86 | )
87 | .listen(
88 | (event) {
89 | // append bot message to the last user message
90 | //
91 | // if there is user message, just add bot message
92 | //
93 | // if there is no user message,
94 | // append bot message to the last bot message
95 | final botMessage = event.choices[0].delta?.content;
96 | if (botMessage != null) {
97 | if (_chatHistory.last.isUserMessage) {
98 | _chatHistory.addLast(_ChatHistoryItem.botMessage(botMessage));
99 | } else {
100 | final historyItem = _chatHistory.removeLast();
101 | _chatHistory.addLast(
102 | _ChatHistoryItem.botMessage('${historyItem.message}$botMessage'),
103 | );
104 | }
105 | // notify about new message added/updated
106 | notifyListeners();
107 | }
108 | },
109 | onDone: _generationDone,
110 | onError: _handleError,
111 | );
112 | }
113 |
114 | void _handleError(dynamic error) {
115 | final errorText = error.toString();
116 | debugPrint('Error: $errorText');
117 | _setError(getNiceErrorMessage(error));
118 | }
119 |
120 | void _generationDone() {
121 | // cleanup after generation is done
122 | _isGenerationInProgress = false;
123 | _streamSub = null;
124 | notifyListeners();
125 | }
126 |
127 | void _setError(String error) {
128 | this.error = error;
129 | notifyListeners();
130 | }
131 |
132 | @override
133 | void dispose() {
134 | unawaited(_streamSub?.cancel());
135 | super.dispose();
136 | }
137 |
138 | ChatMessage _historyItemToChatMessage(_ChatHistoryItem item) {
139 | return ChatMessage(
140 | role: item.isUserMessage ? 'user' : 'assistant',
141 | content: item.message,
142 | );
143 | }
144 | }
145 |
146 | class _ChatHistoryItem {
147 | _ChatHistoryItem({
148 | required this.message,
149 | required this.isUserMessage,
150 | });
151 |
152 | factory _ChatHistoryItem.userMessage(String message) => _ChatHistoryItem(
153 | message: message,
154 | isUserMessage: true,
155 | );
156 |
157 | factory _ChatHistoryItem.botMessage(String message) => _ChatHistoryItem(
158 | message: message,
159 | isUserMessage: false,
160 | );
161 |
162 | String message;
163 | final bool isUserMessage;
164 | }
165 |
--------------------------------------------------------------------------------
/lib/mistral_ai_llm_controller_example/README.md:
--------------------------------------------------------------------------------
1 | # LLM as a controller example
2 |
3 | This example showcase how to use [mistral_ai_client_dart](https://pub.dev/packages/mistralai_client_dart) to control the app.
4 |
5 | The "LLM as a Controller" example showcases a practical implementation of using Large Language Models (LLMs) to control various aspects of a mobile application. This innovative approach utilizes the capabilities of LLMs to interpret user commands, process natural language inputs, and execute actions within the app. This particular example focuses on simulating smart home behavior.
6 |
7 | Features:
8 |
9 | - set color of light using text instruction
10 | - set volume level using text instruction
11 | - set temperature using text instruction
12 | - turn on/off TV using text instruction
13 |
14 | ## Demo
15 |
16 | [](../../docs/assets/llm_controller_example_demo.gif)
17 |
18 | ## Prompt and AI chat construction
19 |
20 | The data sent to the LLM is divided into three messages.
21 |
22 | - System message with general controller description
23 | - System message with specific controller context (available functions, current state)
24 | - User message with instruction for controller
25 |
26 | ```dart
27 | final response = await mistralAIClient.chat(
28 | ChatParams(
29 | model: 'mistral-medium',
30 | messages: [
31 | ChatMessage(role: 'system', content: controllerDescription),
32 | ChatMessage(
33 | role: 'system',
34 | content: controllerContext(
35 | availableFunctions,
36 | controllerSettings,
37 | ),
38 | ),
39 | ChatMessage(role: 'user', content: command),
40 | ],
41 | ),
42 | );
43 |
44 | ```
45 |
46 | ### Controller description
47 |
48 | It defines the general purpose of the controller. It says what the controller can do and what it should return.
49 |
50 | ```text
51 | You are a helpful assistant designed to output only JSON.
52 | Interpret commands based on their intent and map them to the appropriate function.
53 | For direct commands, choose the corresponding function.
54 | For indirect commands, infer the intent and select the most relevant function.
55 | If a command is unrecognized, return the following JSON: { "name": "unknown", "parameters": "" }
56 | You can perform one function at a time.
57 | Do not return anything else than JSON.
58 | Do not explain anything.
59 | ```
60 |
61 | Let's break it down:
62 |
63 | ```text
64 | You are a helpful assistant designed to output only JSON.
65 | ```
66 |
67 | It is important to tell AI chatbot that we are looking for JSON output.
68 |
69 | ```text
70 | Interpret commands based on their intent and map them to the appropriate function.
71 | ```
72 |
73 | It is the main instruction for AI chatbot.
74 |
75 | ```text
76 | For direct commands, choose the corresponding function.
77 | For indirect commands, infer the intent and select the most relevant function.
78 | If a command is unrecognized, return the following JSON: { "name": "unknown", "parameters": "" }
79 | ```
80 |
81 | This is used to tell the AI chatbot how to interpret commands and what to do if the command is not recognized. It is important to return an unknown function name when the command is not recognized without that, AI will use one of the available functions.
82 |
83 | ```text
84 | You can perform one function at a time.
85 | ```
86 |
87 | This is an extension of the instructions for AI chatbot that specifies how the output should look like.
88 |
89 | ```text
90 | Do not return anything else than JSON.
91 | Do not explain anything.
92 | ```
93 |
94 | This is used to tell AI chatbot that we are looking for JSON output and that we are not looking for any explanations. Based on our experience, sometimes AI chatbot is trying to explain the answer instead of giving the answer.
95 |
96 | ### Controller context
97 |
98 | It defines the current state of the controller and contains the available functions and examples of commands and expected responses.
99 |
100 | ```text
101 | #####Current settings:
102 | Settings { temperature: 20.0, volume: 50, color: Color(0xff000000)}, isTVOn: false
103 | #####Set of functions:
104 | setTemperature (parameters:temperature:number)
105 | setVolume (parameters:volume:number)
106 | setColorOfLight (parameters:color:hex ) returns color in hex format only (#RRGGBB)
107 | turnOnTV (parameters:bool)
108 | #####Instructions:
109 | Respond by returning a function names with parameters and only with valid JSON in format: { "name": "value", "parameters": "" }
110 | #####Examples
111 | INPUT: "Set temperature to 17 degrees"
112 | RESULT: { "name": "setTemperature", "parameters": "17" }
113 |
114 | INPUT: "Set volume to 10"
115 | RESULT: { "name": "setVolume", "parameters": "10" }
116 |
117 | INPUT: "Set color of light to orange"
118 | RESULT: { "name": "setColorOfLight", "parameters": "#FFA500" }
119 |
120 | INPUT: "Turn on TV"
121 | RESULT: { "name": "turnOnTV", "parameters": "true" }
122 | ```
123 |
124 | ## Problems and difficulties
125 |
126 | An LLM does very well for direct commands but has problems with indirect commands. For example, if we ask the LLM to "turn on the TV", it will do what we expect, but when we tell the LLM "I am bored, play something", it will not know that turning on the TV might help. A possible solution is to use a more example-based approach, where we would have to provide more examples of indirect commands. The downside of this approach is that we would increase the size of the prompt which would increase the cost of the request.
127 |
--------------------------------------------------------------------------------
/lib/mistral_ai_llm_controller_example/logger_dialog.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class LoggerDialog extends StatelessWidget {
4 | const LoggerDialog({
5 | required this.logger,
6 | required this.errorMessage,
7 | super.key,
8 | });
9 |
10 | final String logger;
11 | final String errorMessage;
12 |
13 | @override
14 | Widget build(BuildContext context) {
15 | return Dialog(
16 | child: Padding(
17 | padding: const EdgeInsets.all(16),
18 | child: Column(
19 | children: [
20 | Expanded(
21 | child: ListView(
22 | children: [
23 | if (logger.isNotEmpty)
24 | SelectableText(
25 | 'Logger: $logger',
26 | style: const TextStyle(color: Colors.green),
27 | ),
28 | if (errorMessage.isNotEmpty)
29 | SelectableText(
30 | 'Error message: $errorMessage',
31 | style: const TextStyle(color: Colors.red),
32 | ),
33 | ],
34 | ),
35 | ),
36 | Row(
37 | mainAxisAlignment: MainAxisAlignment.end,
38 | children: [
39 | TextButton(
40 | onPressed: () {
41 | Navigator.pop(context);
42 | },
43 | child: const Text('Close'),
44 | ),
45 | ],
46 | ),
47 | ],
48 | ),
49 | ),
50 | );
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/lib/mistral_ai_llm_controller_example/model.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:json_annotation/json_annotation.dart';
3 |
4 | part 'model.g.dart';
5 |
6 | @JsonSerializable()
7 | class ControllerResponse {
8 | const ControllerResponse({required this.name, required this.parameters});
9 |
10 | factory ControllerResponse.fromJson(Map json) =>
11 | _$ControllerResponseFromJson(json);
12 |
13 | final String name;
14 | final String parameters;
15 |
16 | Map toJson() => _$ControllerResponseToJson(this);
17 | }
18 |
19 | class ControllerSettings {
20 | ControllerSettings({
21 | this.temperature = 20.0,
22 | this.volume = 50,
23 | this.color = Colors.black,
24 | this.isTVOn = false,
25 | });
26 |
27 | double temperature;
28 | int volume;
29 | Color color;
30 | bool isTVOn = false;
31 |
32 | @override
33 | String toString() {
34 | return 'Settings { '
35 | 'temperature: $temperature, volume: $volume, color: $color}, '
36 | 'isTVOn: $isTVOn';
37 | }
38 | }
39 |
40 | sealed class ControllerFunction {
41 | const ControllerFunction({
42 | required this.name,
43 | required this.type,
44 | required this.example,
45 | this.additionalInfo = '',
46 | });
47 |
48 | factory ControllerFunction.fromName(String name) => switch (name) {
49 | SetTemperatureFunction.signature => const SetTemperatureFunction(),
50 | SetVolumeFunction.signature => const SetVolumeFunction(),
51 | SetColorOfLightFunction.signature => const SetColorOfLightFunction(),
52 | TurnOnTVFunction.signature => const TurnOnTVFunction(),
53 | _ => const UnknownFunction()
54 | };
55 |
56 | String get definition => '$name (parameters:$type) $additionalInfo'.trim();
57 |
58 | final String name;
59 | final String type;
60 | final String additionalInfo;
61 | final String example;
62 | }
63 |
64 | class SetTemperatureFunction extends ControllerFunction {
65 | const SetTemperatureFunction()
66 | : super(
67 | name: signature,
68 | type: 'temperature:number',
69 | example: '''
70 | INPUT: "Set temperature to 17 degrees"
71 | RESULT: { "name": "$signature", "parameters": "17" }
72 | ''',
73 | );
74 | static const String signature = 'setTemperature';
75 | }
76 |
77 | class SetVolumeFunction extends ControllerFunction {
78 | const SetVolumeFunction()
79 | : super(
80 | name: signature,
81 | type: 'volume:number',
82 | example: '''
83 | INPUT: "Set volume to 10"
84 | RESULT: { "name": "$signature", "parameters": "10" }
85 | ''',
86 | );
87 | static const String signature = 'setVolume';
88 | }
89 |
90 | class SetColorOfLightFunction extends ControllerFunction {
91 | const SetColorOfLightFunction()
92 | : super(
93 | name: signature,
94 | type: 'color:hex ',
95 | additionalInfo: 'returns color in hex format only (#RRGGBB)',
96 | example: '''
97 | INPUT: "Set color of light to orange"
98 | RESULT: { "name": "$signature", "parameters": "#FFA500" }
99 | ''',
100 | );
101 | static const String signature = 'setColorOfLight';
102 | }
103 |
104 | class TurnOnTVFunction extends ControllerFunction {
105 | const TurnOnTVFunction()
106 | : super(
107 | name: signature,
108 | type: 'bool',
109 | example: '''
110 | INPUT: "Turn on TV"
111 | RESULT: { "name": "$signature", "parameters": "true" }
112 | ''',
113 | );
114 | static const String signature = 'turnOnTV';
115 | }
116 |
117 | class UnknownFunction extends ControllerFunction {
118 | const UnknownFunction()
119 | : super(
120 | name: 'unknown',
121 | type: '',
122 | example: '',
123 | );
124 | }
125 |
126 | const List availableFunctions = [
127 | SetTemperatureFunction(),
128 | SetVolumeFunction(),
129 | SetColorOfLightFunction(),
130 | TurnOnTVFunction(),
131 | ];
132 |
--------------------------------------------------------------------------------
/lib/mistral_ai_llm_controller_example/model.g.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 |
3 | part of 'model.dart';
4 |
5 | // **************************************************************************
6 | // JsonSerializableGenerator
7 | // **************************************************************************
8 |
9 | ControllerResponse _$ControllerResponseFromJson(Map json) =>
10 | ControllerResponse(
11 | name: json['name'] as String,
12 | parameters: json['parameters'] as String,
13 | );
14 |
15 | Map _$ControllerResponseToJson(ControllerResponse instance) =>
16 | {
17 | 'name': instance.name,
18 | 'parameters': instance.parameters,
19 | };
20 |
--------------------------------------------------------------------------------
/lib/mistral_ai_llm_controller_example/prompt.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_ai_examples/mistral_ai_llm_controller_example/model.dart';
2 |
3 | String controllerDescription = '''
4 | You are a helpful assistant designed to output only JSON.
5 | Interpret commands based on their intent and map them to the appropriate function.
6 | For direct commands, choose the corresponding function.
7 | For indirect commands, infer the intent and select the most relevant function.
8 | If a command is unrecognized, return the following JSON: { "name": "unknown", "parameters": "" }
9 | You can perform one function at a time.
10 | Do not return anything else than JSON.
11 | Do not explain anything. ''';
12 |
13 | String controllerContext(
14 | List controllerFunctions,
15 | ControllerSettings currentSettings,
16 | ) =>
17 | '''
18 | #####Current settings:
19 | $currentSettings
20 | #####Set of functions:
21 | ${controllerFunctions.map((e) => e.definition).join('\n')}
22 | #####Instructions:
23 | Respond by returning a function names with parameters and only with valid JSON in format: { "name": "value", "parameters": "" }
24 | #####Examples
25 | ${controllerFunctions.map((e) => e.example).join('\n')}''';
26 |
--------------------------------------------------------------------------------
/lib/mistral_ai_llm_controller_example/utils.dart:
--------------------------------------------------------------------------------
1 | import 'dart:core';
2 | import 'dart:ui';
3 |
4 | import 'package:flutter_ai_examples/mistral_ai_llm_controller_example/model.dart';
5 |
6 | String? extractJson(String input) {
7 | final regExp = RegExp(r'\{[^}]*\}');
8 | final Match? match = regExp.firstMatch(input);
9 |
10 | return match?.group(0);
11 | }
12 |
13 | /// Returns a `Color` object corresponding to the hexadecimal color string,
14 | ///
15 | /// Supported formats:
16 | /// 0XAARRGGBB
17 | /// 0XRRGGBB
18 | /// AARRGGBB
19 | /// RRGGBB
20 | /// #AARRGGBB
21 | /// #RRGGBB
22 | ///
23 | /// Throws an exception if the input string is not a valid color representation.
24 | Color getColorFromHex(String hexColor) {
25 | try {
26 | var colorString = hexColor.toUpperCase().replaceAll('#', '');
27 |
28 | if (colorString.length == 10 && colorString.startsWith('0X')) {
29 | colorString = colorString.substring(2);
30 | } else if (colorString.length == 8 && colorString.startsWith('0X')) {
31 | colorString = colorString.substring(2);
32 | colorString = 'FF$colorString';
33 | } else if (colorString.length == 6) {
34 | colorString = 'FF$colorString';
35 | }
36 | return Color(int.parse('0x$colorString'));
37 | } catch (e) {
38 | throw Exception('Invalid color: $hexColor');
39 | }
40 | }
41 |
42 | String commandMessage(
43 | String command,
44 | ControllerSettings controllerSettings,
45 | ) =>
46 | '\nCURRENT SETTINGS:\n$controllerSettings\nCOMMAND:\n$command';
47 |
48 | String responseMessage(String response) => '\nRESPONSE:\n$response';
49 |
50 | String invalidResponseMessage(String response) => 'Invalid response: $response';
51 |
--------------------------------------------------------------------------------
/lib/mistral_ai_summary_example/README.md:
--------------------------------------------------------------------------------
1 | # Mistral AI Summary Example
2 |
3 | This example showcase how to use [mistral_ai_client_dart](https://pub.dev/packages/mistralai_client_dart) summarize text.
4 |
5 | ## Features
6 |
7 | - Summarize example text
8 | - Summarize custom text
9 | - Change summary settings
10 |
11 | ## Demo
12 |
13 | [](../../docs/assets/text_summary_example_demo.gif)
14 |
15 | ## Prompt and AI chat construction
16 |
17 | The usage of the Mistral AI API is very simple. It is just one message from the user and the AI will respond with the summary.
18 |
19 | ```dart
20 | final response = await mistralAIClient.chat(
21 | ChatParams(
22 | model: 'mistral-small',
23 | messages: [
24 | ChatMessage(role: 'user', content: text),
25 | ],
26 | ),
27 | );
28 | ```
29 |
30 | The text that is sent to the AI is a little bit more complex. The text that is going to be summarized is wrapped in a prompt that tells the AI what to do.
31 |
32 | ```dart
33 | String getSummaryPromptForText(String text) {
34 | return '''
35 | ###
36 | $text
37 | ###
38 | Please provide a concise summary of the text:
39 | - Highlight the most important entities.
40 | - Should contain 4-5 sentences.
41 |
42 | Guidelines for generating summaries:
43 | - Each sentence should be:
44 | - Relevant to the main story.
45 | - Avoid repetitive and uninformative phrases.
46 | - Truly present in the article.
47 | - Summaries should be easily understood without referencing the article.
48 | ''';
49 | }
50 | ```
51 |
52 | Let's break it down:
53 |
54 | ```text
55 | Please provide a concise summary of the text:
56 | - Highlight the most important entities.
57 | - Should contain 4-5 sentences.
58 | ```
59 |
60 | It is important to specify what the AI should do. Especially in terms of the length of the summary from our experience, it tends to be a little bit too long
61 |
62 | ```text
63 | Guidelines for generating summaries, each sentence should be:
64 | - Relevant to the main story.
65 | - Avoid repetitive and uninformative phrases.
66 | - Truly present in the article.
67 | - Summaries should be easily understood without referencing the article.
68 | ```
69 |
70 | Those are more detailed instructions for the AI. Telling it may help the AI to generate a better summary with fewer hallucinations.
71 |
72 | ## Problems and difficulties
73 |
74 | Despite detailed instructions, the AI sometimes generates summaries that are too long specialty for the small models. Possible solution is to improve the prompt and the instructions for the AI.
75 |
--------------------------------------------------------------------------------
/lib/mistral_ai_summary_example/settings_model.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class SummarySettings {
4 | const SummarySettings({
5 | this.model = MistralAIModel.mistralSmall,
6 | this.temperature = 0.7,
7 | this.topP = 1,
8 | this.maxTokens,
9 | this.safePrompt = false,
10 | this.randomSeed,
11 | });
12 | final MistralAIModel model;
13 | final double temperature;
14 | final double topP;
15 | final int? maxTokens;
16 | final bool safePrompt;
17 | final int? randomSeed;
18 | }
19 |
20 | enum MistralAIModel {
21 | mistralMedium('mistral-medium'),
22 | mistralSmall('mistral-small'),
23 | mistralTiny('mistral-tiny'),
24 | mistralEmbed('mistral-embed');
25 |
26 | const MistralAIModel(this.name);
27 | final String name;
28 | }
29 |
30 | class SummarySettingsModel extends ChangeNotifier {
31 | SummarySettingsModel();
32 |
33 | MistralAIModel _model = MistralAIModel.mistralSmall;
34 | double _temperature = 0.7;
35 | double _topP = 1;
36 | int? _maxTokens;
37 | bool _safePrompt = false;
38 | int? _randomSeed;
39 |
40 | SummarySettings get settings => SummarySettings(
41 | model: _model,
42 | temperature: _temperature,
43 | topP: _topP,
44 | maxTokens: _maxTokens,
45 | safePrompt: _safePrompt,
46 | randomSeed: _randomSeed,
47 | );
48 |
49 | void setModel(MistralAIModel model) {
50 | _model = model;
51 | notifyListeners();
52 | }
53 |
54 | void setTemperature(double temperature) {
55 | _temperature = temperature;
56 | notifyListeners();
57 | }
58 |
59 | void setTopP(double topP) {
60 | _topP = topP;
61 | notifyListeners();
62 | }
63 |
64 | void setMaxTokens(int? maxTokens) {
65 | _maxTokens = maxTokens;
66 | notifyListeners();
67 | }
68 |
69 | void setSafePrompt({required bool safePrompt}) {
70 | _safePrompt = safePrompt;
71 | notifyListeners();
72 | }
73 |
74 | void setRandomSeed(int? randomSeed) {
75 | _randomSeed = randomSeed;
76 | notifyListeners();
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/lib/mistral_ai_summary_example/summary_settings_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_ai_examples/app/theme.dart';
3 |
4 | import 'package:flutter_ai_examples/mistral_ai_summary_example/settings_dialogs.dart';
5 | import 'package:flutter_ai_examples/mistral_ai_summary_example/settings_model.dart';
6 | import 'package:provider/provider.dart';
7 |
8 | class SettingsWidget extends StatelessWidget {
9 | const SettingsWidget({
10 | super.key,
11 | });
12 |
13 | @override
14 | Widget build(BuildContext context) {
15 | return DarkerBackgroundTheme(
16 | child: Scaffold(
17 | appBar: AppBar(title: const Text('Settings')),
18 | body: Column(
19 | children: [
20 | Expanded(
21 | child: ListView(
22 | children: const [
23 | ModelSettingWidget(),
24 | SettingsListSpacer(),
25 | TemperatureAndTopPSettingWidget(),
26 | SettingsListSpacer(),
27 | MaxTokensSettingWidget(),
28 | SettingsListSpacer(),
29 | RandomSeedSettingWidget(),
30 | SettingsListSpacer(),
31 | SafePromptSettingWidget(),
32 | ],
33 | ),
34 | ),
35 | ],
36 | ),
37 | ),
38 | );
39 | }
40 | }
41 |
42 | class ModelSettingWidget extends StatefulWidget {
43 | const ModelSettingWidget({
44 | super.key,
45 | });
46 |
47 | @override
48 | State createState() => _ModelSettingWidgetState();
49 | }
50 |
51 | class _ModelSettingWidgetState extends State {
52 | MistralAIModel selectedModel = MistralAIModel.mistralMedium;
53 |
54 | @override
55 | Widget build(BuildContext context) {
56 | return ClickableSettingsItem(
57 | settingName: 'Model',
58 | settingValue: context.watch().settings.model.name,
59 | onTap: () {
60 | final summarySettingsModel = context.read();
61 | showDialog(
62 | context: context,
63 | builder: (_) => ChangeNotifierProvider.value(
64 | value: summarySettingsModel,
65 | child: const ModelSettingsDialog(),
66 | ),
67 | );
68 | },
69 | );
70 | }
71 | }
72 |
73 | class TemperatureAndTopPSettingWidget extends StatelessWidget {
74 | const TemperatureAndTopPSettingWidget({
75 | super.key,
76 | });
77 |
78 | @override
79 | Widget build(BuildContext context) {
80 | final temperature =
81 | context.watch().settings.temperature;
82 |
83 | // to long to put in one line
84 | final temperatureStringValue = temperature.toStringAsFixed(2);
85 |
86 | final topP = context.watch().settings.topP;
87 |
88 | return ColoredBox(
89 | color: Theme.of(context).colorScheme.surface,
90 | child: Padding(
91 | padding: const EdgeInsets.symmetric(
92 | horizontal: 28,
93 | vertical: 16,
94 | ),
95 | child: Column(
96 | crossAxisAlignment: CrossAxisAlignment.start,
97 | children: [
98 | Column(
99 | crossAxisAlignment: CrossAxisAlignment.start,
100 | children: [
101 | Text(
102 | 'Temperature: $temperatureStringValue',
103 | ),
104 | Slider(
105 | value: temperature,
106 | label: temperature.toStringAsFixed(2),
107 | onChanged: (value) => context
108 | .read()
109 | .setTemperature(value),
110 | ),
111 | ],
112 | ),
113 | Column(
114 | crossAxisAlignment: CrossAxisAlignment.start,
115 | children: [
116 | Text(
117 | 'Top P: ${topP.toStringAsFixed(2)}',
118 | ),
119 | Slider(
120 | value: topP,
121 | label: topP.toStringAsFixed(2),
122 | onChanged: (value) =>
123 | context.read().setTopP(value),
124 | ),
125 | ],
126 | ),
127 | const Text(
128 | 'We recommend altering only one, not both',
129 | style: TextStyle(
130 | fontSize: 12,
131 | color: Colors.grey,
132 | ),
133 | ),
134 | ],
135 | ),
136 | ),
137 | );
138 | }
139 | }
140 |
141 | class MaxTokensSettingWidget extends StatelessWidget {
142 | const MaxTokensSettingWidget({
143 | super.key,
144 | });
145 |
146 | @override
147 | Widget build(BuildContext context) {
148 | return ClickableSettingsItem(
149 | settingName: 'Max tokens',
150 | settingValue: context
151 | .watch()
152 | .settings
153 | .maxTokens
154 | ?.toString() ??
155 | 'Unlimited',
156 | onTap: () {
157 | final summarySettingsModel = context.read();
158 | showDialog(
159 | context: context,
160 | builder: (_) => ChangeNotifierProvider.value(
161 | value: summarySettingsModel,
162 | child: const MaxTokensDialog(),
163 | ),
164 | );
165 | },
166 | );
167 | }
168 | }
169 |
170 | class RandomSeedSettingWidget extends StatelessWidget {
171 | const RandomSeedSettingWidget({
172 | super.key,
173 | });
174 |
175 | @override
176 | Widget build(BuildContext context) {
177 | return ClickableSettingsItem(
178 | settingName: 'Random seed',
179 | settingValue: context
180 | .watch()
181 | .settings
182 | .randomSeed
183 | ?.toString() ??
184 | 'Unset',
185 | onTap: () {
186 | final summarySettingsModel = context.read();
187 | showDialog(
188 | context: context,
189 | builder: (_) => ChangeNotifierProvider.value(
190 | value: summarySettingsModel,
191 | child: const RandomSeedDialog(),
192 | ),
193 | );
194 | },
195 | );
196 | }
197 | }
198 |
199 | class SafePromptSettingWidget extends StatelessWidget {
200 | const SafePromptSettingWidget({
201 | super.key,
202 | });
203 |
204 | @override
205 | Widget build(BuildContext context) {
206 | final settingValue =
207 | context.watch().settings.safePrompt;
208 | return SwitchListTile(
209 | contentPadding: const EdgeInsets.symmetric(
210 | horizontal: 29,
211 | vertical: 10,
212 | ),
213 | title: Text('Safe prompt: $settingValue '),
214 | value: settingValue,
215 | onChanged: (value) {
216 | context.read().setSafePrompt(safePrompt: value);
217 | },
218 | );
219 | }
220 | }
221 |
222 | class SettingsListSpacer extends StatelessWidget {
223 | const SettingsListSpacer({
224 | super.key,
225 | });
226 |
227 | @override
228 | Widget build(BuildContext context) {
229 | return const SizedBox(height: 24);
230 | }
231 | }
232 |
233 | class ClickableSettingsItem extends StatelessWidget {
234 | const ClickableSettingsItem({
235 | required this.settingName,
236 | required this.settingValue,
237 | required this.onTap,
238 | super.key,
239 | });
240 |
241 | final String settingName;
242 | final String settingValue;
243 | final VoidCallback onTap;
244 |
245 | @override
246 | Widget build(BuildContext context) {
247 | return Material(
248 | color: Theme.of(context).colorScheme.surface,
249 | child: ListTile(
250 | contentPadding: const EdgeInsets.symmetric(
251 | horizontal: 29,
252 | vertical: 8,
253 | ),
254 | title: Text(settingName),
255 | subtitle: Text(settingValue),
256 | onTap: onTap,
257 | ),
258 | );
259 | }
260 | }
261 |
--------------------------------------------------------------------------------
/lib/utils/error_message.dart:
--------------------------------------------------------------------------------
1 | import 'package:mistralai_client_dart/mistralai_client_dart.dart';
2 |
3 | String getNiceErrorMessage(dynamic error) {
4 | final errorText = error.toString();
5 | if (error is MistralAIClientException) {
6 | final isUnauthorized = errorText.contains('401');
7 | return isUnauthorized
8 | ? 'Unauthorized. Please check your API key.'
9 | : 'Something went wrong when sending a request to Mistral API. '
10 | 'Please try again.\n($errorText)';
11 | }
12 | return 'Something went wrong. Please try again.\n($errorText)';
13 | }
14 |
--------------------------------------------------------------------------------
/lib/utils/snackbar_extension.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | extension SnackBarExtension on BuildContext {
4 | void showMessageSnackBar(String message) {
5 | final context = this;
6 | ScaffoldMessenger.of(context).showSnackBar(
7 | SnackBar(
8 | // limit the width of the snackbar to 500 if the screen is wider
9 | width: MediaQuery.of(context).size.width > 500 ? 460 : null,
10 | behavior: SnackBarBehavior.floating,
11 | content: Text(message),
12 | ),
13 | );
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/linux/.gitignore:
--------------------------------------------------------------------------------
1 | flutter/ephemeral
2 |
--------------------------------------------------------------------------------
/linux/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | # Project-level configuration.
2 | cmake_minimum_required(VERSION 3.10)
3 | project(runner LANGUAGES CXX)
4 |
5 | # The name of the executable created for the application. Change this to change
6 | # the on-disk name of your application.
7 | set(BINARY_NAME "flutter_ai_examples")
8 | # The unique GTK application identifier for this application. See:
9 | # https://wiki.gnome.org/HowDoI/ChooseApplicationID
10 | set(APPLICATION_ID "com.nomtek.flutter_ai_examples")
11 |
12 | # Explicitly opt in to modern CMake behaviors to avoid warnings with recent
13 | # versions of CMake.
14 | cmake_policy(SET CMP0063 NEW)
15 |
16 | # Load bundled libraries from the lib/ directory relative to the binary.
17 | set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
18 |
19 | # Root filesystem for cross-building.
20 | if(FLUTTER_TARGET_PLATFORM_SYSROOT)
21 | set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT})
22 | set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT})
23 | set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
24 | set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
25 | set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
26 | set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
27 | endif()
28 |
29 | # Define build configuration options.
30 | if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
31 | set(CMAKE_BUILD_TYPE "Debug" CACHE
32 | STRING "Flutter build mode" FORCE)
33 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
34 | "Debug" "Profile" "Release")
35 | endif()
36 |
37 | # Compilation settings that should be applied to most targets.
38 | #
39 | # Be cautious about adding new options here, as plugins use this function by
40 | # default. In most cases, you should add new options to specific targets instead
41 | # of modifying this function.
42 | function(APPLY_STANDARD_SETTINGS TARGET)
43 | target_compile_features(${TARGET} PUBLIC cxx_std_14)
44 | target_compile_options(${TARGET} PRIVATE -Wall -Werror)
45 | target_compile_options(${TARGET} PRIVATE "$<$>:-O3>")
46 | target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>")
47 | endfunction()
48 |
49 | # Flutter library and tool build rules.
50 | set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
51 | add_subdirectory(${FLUTTER_MANAGED_DIR})
52 |
53 | # System-level dependencies.
54 | find_package(PkgConfig REQUIRED)
55 | pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
56 |
57 | add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}")
58 |
59 | # Define the application target. To change its name, change BINARY_NAME above,
60 | # not the value here, or `flutter run` will no longer work.
61 | #
62 | # Any new source files that you add to the application should be added here.
63 | add_executable(${BINARY_NAME}
64 | "main.cc"
65 | "my_application.cc"
66 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
67 | )
68 |
69 | # Apply the standard set of build settings. This can be removed for applications
70 | # that need different build settings.
71 | apply_standard_settings(${BINARY_NAME})
72 |
73 | # Add dependency libraries. Add any application-specific dependencies here.
74 | target_link_libraries(${BINARY_NAME} PRIVATE flutter)
75 | target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK)
76 |
77 | # Run the Flutter tool portions of the build. This must not be removed.
78 | add_dependencies(${BINARY_NAME} flutter_assemble)
79 |
80 | # Only the install-generated bundle's copy of the executable will launch
81 | # correctly, since the resources must in the right relative locations. To avoid
82 | # people trying to run the unbundled copy, put it in a subdirectory instead of
83 | # the default top-level location.
84 | set_target_properties(${BINARY_NAME}
85 | PROPERTIES
86 | RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run"
87 | )
88 |
89 |
90 | # Generated plugin build rules, which manage building the plugins and adding
91 | # them to the application.
92 | include(flutter/generated_plugins.cmake)
93 |
94 |
95 | # === Installation ===
96 | # By default, "installing" just makes a relocatable bundle in the build
97 | # directory.
98 | set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle")
99 | if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
100 | set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
101 | endif()
102 |
103 | # Start with a clean build bundle directory every time.
104 | install(CODE "
105 | file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\")
106 | " COMPONENT Runtime)
107 |
108 | set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
109 | set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib")
110 |
111 | install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
112 | COMPONENT Runtime)
113 |
114 | install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
115 | COMPONENT Runtime)
116 |
117 | install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
118 | COMPONENT Runtime)
119 |
120 | foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES})
121 | install(FILES "${bundled_library}"
122 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
123 | COMPONENT Runtime)
124 | endforeach(bundled_library)
125 |
126 | # Copy the native assets provided by the build.dart from all packages.
127 | set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/")
128 | install(DIRECTORY "${NATIVE_ASSETS_DIR}"
129 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
130 | COMPONENT Runtime)
131 |
132 | # Fully re-copy the assets directory on each build to avoid having stale files
133 | # from a previous install.
134 | set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
135 | install(CODE "
136 | file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
137 | " COMPONENT Runtime)
138 | install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
139 | DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
140 |
141 | # Install the AOT library on non-Debug builds only.
142 | if(NOT CMAKE_BUILD_TYPE MATCHES "Debug")
143 | install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
144 | COMPONENT Runtime)
145 | endif()
146 |
--------------------------------------------------------------------------------
/linux/flutter/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | # This file controls Flutter-level build steps. It should not be edited.
2 | cmake_minimum_required(VERSION 3.10)
3 |
4 | set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
5 |
6 | # Configuration provided via flutter tool.
7 | include(${EPHEMERAL_DIR}/generated_config.cmake)
8 |
9 | # TODO: Move the rest of this into files in ephemeral. See
10 | # https://github.com/flutter/flutter/issues/57146.
11 |
12 | # Serves the same purpose as list(TRANSFORM ... PREPEND ...),
13 | # which isn't available in 3.10.
14 | function(list_prepend LIST_NAME PREFIX)
15 | set(NEW_LIST "")
16 | foreach(element ${${LIST_NAME}})
17 | list(APPEND NEW_LIST "${PREFIX}${element}")
18 | endforeach(element)
19 | set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE)
20 | endfunction()
21 |
22 | # === Flutter Library ===
23 | # System-level dependencies.
24 | find_package(PkgConfig REQUIRED)
25 | pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
26 | pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0)
27 | pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0)
28 |
29 | set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so")
30 |
31 | # Published to parent scope for install step.
32 | set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
33 | set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
34 | set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
35 | set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE)
36 |
37 | list(APPEND FLUTTER_LIBRARY_HEADERS
38 | "fl_basic_message_channel.h"
39 | "fl_binary_codec.h"
40 | "fl_binary_messenger.h"
41 | "fl_dart_project.h"
42 | "fl_engine.h"
43 | "fl_json_message_codec.h"
44 | "fl_json_method_codec.h"
45 | "fl_message_codec.h"
46 | "fl_method_call.h"
47 | "fl_method_channel.h"
48 | "fl_method_codec.h"
49 | "fl_method_response.h"
50 | "fl_plugin_registrar.h"
51 | "fl_plugin_registry.h"
52 | "fl_standard_message_codec.h"
53 | "fl_standard_method_codec.h"
54 | "fl_string_codec.h"
55 | "fl_value.h"
56 | "fl_view.h"
57 | "flutter_linux.h"
58 | )
59 | list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/")
60 | add_library(flutter INTERFACE)
61 | target_include_directories(flutter INTERFACE
62 | "${EPHEMERAL_DIR}"
63 | )
64 | target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}")
65 | target_link_libraries(flutter INTERFACE
66 | PkgConfig::GTK
67 | PkgConfig::GLIB
68 | PkgConfig::GIO
69 | )
70 | add_dependencies(flutter flutter_assemble)
71 |
72 | # === Flutter tool backend ===
73 | # _phony_ is a non-existent file to force this command to run every time,
74 | # since currently there's no way to get a full input/output list from the
75 | # flutter tool.
76 | add_custom_command(
77 | OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
78 | ${CMAKE_CURRENT_BINARY_DIR}/_phony_
79 | COMMAND ${CMAKE_COMMAND} -E env
80 | ${FLUTTER_TOOL_ENVIRONMENT}
81 | "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh"
82 | ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE}
83 | VERBATIM
84 | )
85 | add_custom_target(flutter_assemble DEPENDS
86 | "${FLUTTER_LIBRARY}"
87 | ${FLUTTER_LIBRARY_HEADERS}
88 | )
89 |
--------------------------------------------------------------------------------
/linux/flutter/generated_plugin_registrant.cc:
--------------------------------------------------------------------------------
1 | //
2 | // Generated file. Do not edit.
3 | //
4 |
5 | // clang-format off
6 |
7 | #include "generated_plugin_registrant.h"
8 |
9 | #include
10 |
11 | void fl_register_plugins(FlPluginRegistry* registry) {
12 | g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
13 | fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
14 | url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
15 | }
16 |
--------------------------------------------------------------------------------
/linux/flutter/generated_plugin_registrant.h:
--------------------------------------------------------------------------------
1 | //
2 | // Generated file. Do not edit.
3 | //
4 |
5 | // clang-format off
6 |
7 | #ifndef GENERATED_PLUGIN_REGISTRANT_
8 | #define GENERATED_PLUGIN_REGISTRANT_
9 |
10 | #include
11 |
12 | // Registers Flutter plugins.
13 | void fl_register_plugins(FlPluginRegistry* registry);
14 |
15 | #endif // GENERATED_PLUGIN_REGISTRANT_
16 |
--------------------------------------------------------------------------------
/linux/flutter/generated_plugins.cmake:
--------------------------------------------------------------------------------
1 | #
2 | # Generated file, do not edit.
3 | #
4 |
5 | list(APPEND FLUTTER_PLUGIN_LIST
6 | url_launcher_linux
7 | )
8 |
9 | list(APPEND FLUTTER_FFI_PLUGIN_LIST
10 | )
11 |
12 | set(PLUGIN_BUNDLED_LIBRARIES)
13 |
14 | foreach(plugin ${FLUTTER_PLUGIN_LIST})
15 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin})
16 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
17 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $)
18 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
19 | endforeach(plugin)
20 |
21 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
22 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin})
23 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
24 | endforeach(ffi_plugin)
25 |
--------------------------------------------------------------------------------
/linux/main.cc:
--------------------------------------------------------------------------------
1 | #include "my_application.h"
2 |
3 | int main(int argc, char** argv) {
4 | g_autoptr(MyApplication) app = my_application_new();
5 | return g_application_run(G_APPLICATION(app), argc, argv);
6 | }
7 |
--------------------------------------------------------------------------------
/linux/my_application.cc:
--------------------------------------------------------------------------------
1 | #include "my_application.h"
2 |
3 | #include
4 | #ifdef GDK_WINDOWING_X11
5 | #include
6 | #endif
7 |
8 | #include "flutter/generated_plugin_registrant.h"
9 |
10 | struct _MyApplication {
11 | GtkApplication parent_instance;
12 | char** dart_entrypoint_arguments;
13 | };
14 |
15 | G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION)
16 |
17 | // Implements GApplication::activate.
18 | static void my_application_activate(GApplication* application) {
19 | MyApplication* self = MY_APPLICATION(application);
20 | GtkWindow* window =
21 | GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application)));
22 |
23 | // Use a header bar when running in GNOME as this is the common style used
24 | // by applications and is the setup most users will be using (e.g. Ubuntu
25 | // desktop).
26 | // If running on X and not using GNOME then just use a traditional title bar
27 | // in case the window manager does more exotic layout, e.g. tiling.
28 | // If running on Wayland assume the header bar will work (may need changing
29 | // if future cases occur).
30 | gboolean use_header_bar = TRUE;
31 | #ifdef GDK_WINDOWING_X11
32 | GdkScreen* screen = gtk_window_get_screen(window);
33 | if (GDK_IS_X11_SCREEN(screen)) {
34 | const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen);
35 | if (g_strcmp0(wm_name, "GNOME Shell") != 0) {
36 | use_header_bar = FALSE;
37 | }
38 | }
39 | #endif
40 | if (use_header_bar) {
41 | GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
42 | gtk_widget_show(GTK_WIDGET(header_bar));
43 | gtk_header_bar_set_title(header_bar, "flutter_ai_examples");
44 | gtk_header_bar_set_show_close_button(header_bar, TRUE);
45 | gtk_window_set_titlebar(window, GTK_WIDGET(header_bar));
46 | } else {
47 | gtk_window_set_title(window, "flutter_ai_examples");
48 | }
49 |
50 | gtk_window_set_default_size(window, 1280, 720);
51 | gtk_widget_show(GTK_WIDGET(window));
52 |
53 | g_autoptr(FlDartProject) project = fl_dart_project_new();
54 | fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments);
55 |
56 | FlView* view = fl_view_new(project);
57 | gtk_widget_show(GTK_WIDGET(view));
58 | gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view));
59 |
60 | fl_register_plugins(FL_PLUGIN_REGISTRY(view));
61 |
62 | gtk_widget_grab_focus(GTK_WIDGET(view));
63 | }
64 |
65 | // Implements GApplication::local_command_line.
66 | static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) {
67 | MyApplication* self = MY_APPLICATION(application);
68 | // Strip out the first argument as it is the binary name.
69 | self->dart_entrypoint_arguments = g_strdupv(*arguments + 1);
70 |
71 | g_autoptr(GError) error = nullptr;
72 | if (!g_application_register(application, nullptr, &error)) {
73 | g_warning("Failed to register: %s", error->message);
74 | *exit_status = 1;
75 | return TRUE;
76 | }
77 |
78 | g_application_activate(application);
79 | *exit_status = 0;
80 |
81 | return TRUE;
82 | }
83 |
84 | // Implements GObject::dispose.
85 | static void my_application_dispose(GObject* object) {
86 | MyApplication* self = MY_APPLICATION(object);
87 | g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev);
88 | G_OBJECT_CLASS(my_application_parent_class)->dispose(object);
89 | }
90 |
91 | static void my_application_class_init(MyApplicationClass* klass) {
92 | G_APPLICATION_CLASS(klass)->activate = my_application_activate;
93 | G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line;
94 | G_OBJECT_CLASS(klass)->dispose = my_application_dispose;
95 | }
96 |
97 | static void my_application_init(MyApplication* self) {}
98 |
99 | MyApplication* my_application_new() {
100 | return MY_APPLICATION(g_object_new(my_application_get_type(),
101 | "application-id", APPLICATION_ID,
102 | "flags", G_APPLICATION_NON_UNIQUE,
103 | nullptr));
104 | }
105 |
--------------------------------------------------------------------------------
/linux/my_application.h:
--------------------------------------------------------------------------------
1 | #ifndef FLUTTER_MY_APPLICATION_H_
2 | #define FLUTTER_MY_APPLICATION_H_
3 |
4 | #include
5 |
6 | G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION,
7 | GtkApplication)
8 |
9 | /**
10 | * my_application_new:
11 | *
12 | * Creates a new Flutter-based application.
13 | *
14 | * Returns: a new #MyApplication.
15 | */
16 | MyApplication* my_application_new();
17 |
18 | #endif // FLUTTER_MY_APPLICATION_H_
19 |
--------------------------------------------------------------------------------
/macos/.gitignore:
--------------------------------------------------------------------------------
1 | # Flutter-related
2 | **/Flutter/ephemeral/
3 | **/Pods/
4 |
5 | # Xcode-related
6 | **/dgph
7 | **/xcuserdata/
8 |
--------------------------------------------------------------------------------
/macos/Flutter/Flutter-Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
2 | #include "ephemeral/Flutter-Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/macos/Flutter/Flutter-Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
2 | #include "ephemeral/Flutter-Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/macos/Flutter/GeneratedPluginRegistrant.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Generated file. Do not edit.
3 | //
4 |
5 | import FlutterMacOS
6 | import Foundation
7 |
8 | import path_provider_foundation
9 | import shared_preferences_foundation
10 | import url_launcher_macos
11 |
12 | func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
13 | PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
14 | SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
15 | UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
16 | }
17 |
--------------------------------------------------------------------------------
/macos/Podfile:
--------------------------------------------------------------------------------
1 | platform :osx, '10.14'
2 |
3 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
4 | ENV['COCOAPODS_DISABLE_STATS'] = 'true'
5 |
6 | project 'Runner', {
7 | 'Debug' => :debug,
8 | 'Profile' => :release,
9 | 'Release' => :release,
10 | }
11 |
12 | def flutter_root
13 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__)
14 | unless File.exist?(generated_xcode_build_settings_path)
15 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first"
16 | end
17 |
18 | File.foreach(generated_xcode_build_settings_path) do |line|
19 | matches = line.match(/FLUTTER_ROOT\=(.*)/)
20 | return matches[1].strip if matches
21 | end
22 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\""
23 | end
24 |
25 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
26 |
27 | flutter_macos_podfile_setup
28 |
29 | target 'Runner' do
30 | use_frameworks!
31 | use_modular_headers!
32 |
33 | flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__))
34 | target 'RunnerTests' do
35 | inherit! :search_paths
36 | end
37 | end
38 |
39 | post_install do |installer|
40 | installer.pods_project.targets.each do |target|
41 | flutter_additional_macos_build_settings(target)
42 | end
43 | end
44 |
--------------------------------------------------------------------------------
/macos/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - FlutterMacOS (1.0.0)
3 | - path_provider_foundation (0.0.1):
4 | - Flutter
5 | - FlutterMacOS
6 | - shared_preferences_foundation (0.0.1):
7 | - Flutter
8 | - FlutterMacOS
9 | - url_launcher_macos (0.0.1):
10 | - FlutterMacOS
11 |
12 | DEPENDENCIES:
13 | - FlutterMacOS (from `Flutter/ephemeral`)
14 | - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
15 | - shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`)
16 | - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
17 |
18 | EXTERNAL SOURCES:
19 | FlutterMacOS:
20 | :path: Flutter/ephemeral
21 | path_provider_foundation:
22 | :path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin
23 | shared_preferences_foundation:
24 | :path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin
25 | url_launcher_macos:
26 | :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos
27 |
28 | SPEC CHECKSUMS:
29 | FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
30 | path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c
31 | shared_preferences_foundation: b4c3b4cddf1c21f02770737f147a3f5da9d39695
32 | url_launcher_macos: d2691c7dd33ed713bf3544850a623080ec693d95
33 |
34 | PODFILE CHECKSUM: 236401fc2c932af29a9fcf0e97baeeb2d750d367
35 |
36 | COCOAPODS: 1.11.3
37 |
--------------------------------------------------------------------------------
/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
37 |
38 |
39 |
40 |
43 |
49 |
50 |
51 |
52 |
53 |
63 |
65 |
71 |
72 |
73 |
74 |
80 |
82 |
88 |
89 |
90 |
91 |
93 |
94 |
97 |
98 |
99 |
--------------------------------------------------------------------------------
/macos/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/macos/Runner/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import Cocoa
2 | import FlutterMacOS
3 |
4 | @NSApplicationMain
5 | class AppDelegate: FlutterAppDelegate {
6 | override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
7 | return true
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "16x16",
5 | "idiom" : "mac",
6 | "filename" : "app_icon_16.png",
7 | "scale" : "1x"
8 | },
9 | {
10 | "size" : "16x16",
11 | "idiom" : "mac",
12 | "filename" : "app_icon_32.png",
13 | "scale" : "2x"
14 | },
15 | {
16 | "size" : "32x32",
17 | "idiom" : "mac",
18 | "filename" : "app_icon_32.png",
19 | "scale" : "1x"
20 | },
21 | {
22 | "size" : "32x32",
23 | "idiom" : "mac",
24 | "filename" : "app_icon_64.png",
25 | "scale" : "2x"
26 | },
27 | {
28 | "size" : "128x128",
29 | "idiom" : "mac",
30 | "filename" : "app_icon_128.png",
31 | "scale" : "1x"
32 | },
33 | {
34 | "size" : "128x128",
35 | "idiom" : "mac",
36 | "filename" : "app_icon_256.png",
37 | "scale" : "2x"
38 | },
39 | {
40 | "size" : "256x256",
41 | "idiom" : "mac",
42 | "filename" : "app_icon_256.png",
43 | "scale" : "1x"
44 | },
45 | {
46 | "size" : "256x256",
47 | "idiom" : "mac",
48 | "filename" : "app_icon_512.png",
49 | "scale" : "2x"
50 | },
51 | {
52 | "size" : "512x512",
53 | "idiom" : "mac",
54 | "filename" : "app_icon_512.png",
55 | "scale" : "1x"
56 | },
57 | {
58 | "size" : "512x512",
59 | "idiom" : "mac",
60 | "filename" : "app_icon_1024.png",
61 | "scale" : "2x"
62 | }
63 | ],
64 | "info" : {
65 | "version" : 1,
66 | "author" : "xcode"
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nomtek/flutter_ai_examples/4fad00324f284e6003ef6f14cea32e21e9b704ba/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nomtek/flutter_ai_examples/4fad00324f284e6003ef6f14cea32e21e9b704ba/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nomtek/flutter_ai_examples/4fad00324f284e6003ef6f14cea32e21e9b704ba/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nomtek/flutter_ai_examples/4fad00324f284e6003ef6f14cea32e21e9b704ba/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nomtek/flutter_ai_examples/4fad00324f284e6003ef6f14cea32e21e9b704ba/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nomtek/flutter_ai_examples/4fad00324f284e6003ef6f14cea32e21e9b704ba/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nomtek/flutter_ai_examples/4fad00324f284e6003ef6f14cea32e21e9b704ba/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png
--------------------------------------------------------------------------------
/macos/Runner/Configs/AppInfo.xcconfig:
--------------------------------------------------------------------------------
1 | // Application-level settings for the Runner target.
2 | //
3 | // This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the
4 | // future. If not, the values below would default to using the project name when this becomes a
5 | // 'flutter create' template.
6 |
7 | // The application's name. By default this is also the title of the Flutter window.
8 | PRODUCT_NAME = flutter_ai_examples
9 |
10 | // The application's bundle identifier
11 | PRODUCT_BUNDLE_IDENTIFIER = com.example.mistralAiChatExampleApp
12 |
13 | // The copyright displayed in application information
14 | PRODUCT_COPYRIGHT = Copyright © 2024 com.example. All rights reserved.
15 |
--------------------------------------------------------------------------------
/macos/Runner/Configs/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include "../../Flutter/Flutter-Debug.xcconfig"
2 | #include "Warnings.xcconfig"
3 |
--------------------------------------------------------------------------------
/macos/Runner/Configs/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include "../../Flutter/Flutter-Release.xcconfig"
2 | #include "Warnings.xcconfig"
3 |
--------------------------------------------------------------------------------
/macos/Runner/Configs/Warnings.xcconfig:
--------------------------------------------------------------------------------
1 | WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings
2 | GCC_WARN_UNDECLARED_SELECTOR = YES
3 | CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES
4 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE
5 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES
6 | CLANG_WARN_PRAGMA_PACK = YES
7 | CLANG_WARN_STRICT_PROTOTYPES = YES
8 | CLANG_WARN_COMMA = YES
9 | GCC_WARN_STRICT_SELECTOR_MATCH = YES
10 | CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES
11 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES
12 | GCC_WARN_SHADOW = YES
13 | CLANG_WARN_UNREACHABLE_CODE = YES
14 |
--------------------------------------------------------------------------------
/macos/Runner/DebugProfile.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.cs.allow-jit
8 |
9 | com.apple.security.network.server
10 |
11 | com.apple.security.network.client
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/macos/Runner/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIconFile
10 |
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | $(FLUTTER_BUILD_NAME)
21 | CFBundleVersion
22 | $(FLUTTER_BUILD_NUMBER)
23 | LSMinimumSystemVersion
24 | $(MACOSX_DEPLOYMENT_TARGET)
25 | NSHumanReadableCopyright
26 | $(PRODUCT_COPYRIGHT)
27 | NSMainNibFile
28 | MainMenu
29 | NSPrincipalClass
30 | NSApplication
31 |
32 |
33 |
--------------------------------------------------------------------------------
/macos/Runner/MainFlutterWindow.swift:
--------------------------------------------------------------------------------
1 | import Cocoa
2 | import FlutterMacOS
3 |
4 | class MainFlutterWindow: NSWindow {
5 | override func awakeFromNib() {
6 | let flutterViewController = FlutterViewController()
7 | let windowFrame = self.frame
8 | self.contentViewController = flutterViewController
9 | self.setFrame(windowFrame, display: true)
10 |
11 | RegisterGeneratedPlugins(registry: flutterViewController)
12 |
13 | super.awakeFromNib()
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/macos/Runner/Release.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.network.client
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/macos/RunnerTests/RunnerTests.swift:
--------------------------------------------------------------------------------
1 | import FlutterMacOS
2 | import Cocoa
3 | import XCTest
4 |
5 | class RunnerTests: XCTestCase {
6 |
7 | func testExample() {
8 | // If you add code to the Runner application, consider adding tests here.
9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest.
10 | }
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: flutter_ai_examples
2 | description: "Example app showcasing the Mistral AI chat client app."
3 | publish_to: "none"
4 |
5 | version: 0.0.1+1
6 |
7 | environment:
8 | sdk: ">=3.2.3 <4.0.0"
9 | flutter: ^3.16.0
10 |
11 | dependencies:
12 | characters: ^1.3.0
13 | collection: ^1.18.0
14 | flutter:
15 | sdk: flutter
16 | go_router: ^13.0.1
17 | google_fonts: ^6.1.0
18 | json_annotation: ^4.8.1
19 | langchain: ^0.4.0
20 | material_symbols_icons: ^4.2718.0
21 | mistralai_client_dart: 0.0.2
22 | provider: ^6.1.1
23 | shared_preferences: ^2.2.2
24 | url_launcher: ^6.2.4
25 |
26 | dev_dependencies:
27 | build_runner: ^2.4.8
28 | flutter_test:
29 | sdk: flutter
30 | go_router_builder: ^2.4.1
31 | json_serializable: ^6.7.1
32 | test: ^1.24.9
33 | very_good_analysis: ^5.1.0
34 |
35 | flutter:
36 | generate: true
37 | uses-material-design: true
38 |
39 | assets:
40 | - assets/
41 | - assets/fonts/
42 |
--------------------------------------------------------------------------------
/test/mistral_tokenizer/mistral_tokenizer_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_ai_examples/mistral_tokenizer/mistral_tokenizer.dart';
2 | import 'package:test/test.dart';
3 |
4 | void main() {
5 | final testData = [
6 | // Simple test case
7 | ('grabbed', [1, 13300]),
8 |
9 | // Naive implementation produces inconsistent tokenization for " grabbed",
10 | // making this a good test case
11 | (' grabbed', [1, 28705, 13300]),
12 |
13 | // Naive implementation uses incorrect merge order for multiple
14 | // consecutive space merges, making this a good test case
15 | (' grabbed', [1, 17422, 13300]),
16 |
17 | // Line breaks and tabs are handled as fallback to byte tokens
18 | ('\n', [1, 28705, 13]),
19 | (' \n', [1, 259, 13]),
20 | (' tabs out here', [1, 28705, 12, 24856, 12, 12, 12, 12, 406, 1236]),
21 |
22 | // Equal priority merges are performed left-to-right (fixed in 1.1.1)
23 | ('ax\n####\nboo', [1, 6056, 13, 2000, 13, 1798, 28709]),
24 |
25 | // UTF-8 multipoint character that should be found in vocabulary
26 | ('镇', [1, 28705, 29780]),
27 |
28 | // UTF-8 multipoint character that should NOT be found in vocabulary,
29 | // fallback to MULTIPLE byte tokens
30 | ('🦙', [1, 28705, 243, 162, 169, 156]),
31 |
32 | // Consecutive UTF-8 multipoint characters that are NOT found
33 | // in a vocabulary and use DIFFERENT number of bytes
34 | ('🦙Ꙋ', [1, 28705, 243, 162, 169, 156, 237, 156, 141]),
35 | ('Ꙋ🦙', [1, 28705, 237, 156, 141, 243, 162, 169, 156]),
36 |
37 | // Larger text input with various special characters sprinkled in
38 | (
39 | 'The llama (/ˈlɑːmə/; 🦙Spanish pronunciation: [ˈʎama]) (Lama glama) is a domesticated South American camelid, widely used as a meat and pack animal by Andean cultures since the Pre-Columbian era. Llamas are social animals and live with others as a herd. Their wool is soft and contains only a small amount of lanolin.[2] Llamas can learn simple tasks after a few repetitions. When using a pack, they can carry about 25 to 30% of their body weight for 8 to 13 km (5–8 miles).[3] The name llama (in the past also spelled "lama" or "glama") was adopted by European settlers from native Peruvians.[4] The ancestors of llamas are thought to have originated from the Great Plains of North America about 40 million years ago, and subsequently migrated to South America about three million years ago during the Great American Interchange. By the end of the last ice age (10,000–12,000 years ago), camelids were extinct in North America.[3] As of 2007, there were over seven million llamas and alpacas in South America and over 158,000 llamas and 100,000Ꙋ🦙 alpacas, descended from progenitors imported late in the 20th century, in the United States and Canada.[5] In Aymara mythology, llamas are important beings. The Heavenly Llama is said to drink water from the ocean and urinates as it rains.[6] According to Aymara eschatology, llamas will return to the water springs and lagoons where they come from at the end of time.[6]',
40 | [
41 | 1, 415, 8814, 2786, 325, 28748, 29097, 28714, 29813, // pre-format
42 | 29240,
43 | 28719, 29108, 28748, 28745, 28705, 243, 162, 169, 156, 13116,
44 | 789, 12704, 14281, 352, 28747, 733, 29097, 205, 145, 2786, 2803,
45 | 325, 28758, 2786, 1272, 2786, 28731, 349, 264, 2853, 374, 6899,
46 | 3658, 2556, 3730, 301, 313, 28725, 12575, 1307, 390, 264, 10228,
47 | 304, 2163, 8527, 486, 1015, 28706, 276, 19826, 1854, 272, 4258,
48 | 28733, 1577, 2915, 753, 4204, 28723, 393, 5989, 293, 460, 2809,
49 | 8222, 304, 2943, 395, 2663, 390, 264, 559, 28715, 28723, 6723,
50 | 25943, 349, 2664, 304, 5876, 865, 264, 1741, 3558, 302, 26573,
51 | 27545, 20011, 28750, 28793, 393, 5989, 293, 541, 2822, 3588, 9796,
52 | 1024, 264, 1664, 21435, 2065, 28723, 1684, 1413, 264, 2163, 28725,
53 | 590, 541, 7096, 684, 28705, 28750, 28782, 298, 28705, 28770, 28734,
54 | 28823, 302, 652, 2187, 4336, 354, 28705, 28783, 298, 28705, 28740,
55 | 28770, 3535, 325, 28782, 28816, 28783, 6052, 609, 28792, 28770, 28793,
56 | 415, 1141, 8814, 2786, 325, 262, 272, 2609, 835, 668, 6099, 345, 28714,
57 | 2786, 28739, 442, 345, 1727, 2786, 1243, 403, 13424, 486, 6392, 4641,
58 | 8531, 477, 8271, 2744, 5388, 3693, 20011, 28781, 28793, 415, 25427, 302,
59 | 17620, 293, 460, 1654, 298, 506, 5016, 601, 477, 272, 6043, 1641, 1606,
60 | 302, 3964, 4352, 684, 28705, 28781, 28734, 3841, 1267, 3584, 28725, 304,
61 | 18410, 11205, 601, 298, 3658, 4352, 684, 1712, 3841, 1267, 3584, 1938,
62 | 272, 6043, 2556, 4287, 4078, 28723, 2463, 272, 948, 302, 272, 1432,
63 | 7515,
64 | 3595, 325, 28740, 28734, 28725, 28734, 28734, 28734, 28816, 28740,
65 | 28750,
66 | 28725, 28734, 28734, 28734, 1267, 3584, 557, 3730, 301, 2298, 654, 1568,
67 | 5654, 297, 3964, 4352, 20011, 28770, 28793, 1136, 302, 28705, 28750,
68 | 28734, 28734, 28787, 28725, 736, 654, 754, 6671, 3841, 17620, 293, 304,
69 | 389, 28720, 323, 293, 297, 3658, 4352, 304, 754, 28705, 28740, 28782,
70 | 28783, 28725, 28734, 28734, 28734, 17620, 293, 304, 28705, 28740, 28734,
71 | 28734, 28725, 28734, 28734, 28734, 237, 156, 141, 243, 162, 169, 156,
72 | 389, 28720, 323, 293, 28725, 2283, 2508, 477, 430, 2383, 9058, 26659,
73 | 3909, 297, 272, 28705, 28750, 28734, 362, 5445, 28725, 297, 272, 2969,
74 | 3543, 304, 6082, 20011, 28782, 28793, 560, 330, 1082, 2923, 13345,
75 | 2161, 28725, 17620, 293, 460, 2278, 16905, 28723, 415, 22830, 346, 393,
76 | 28714, 2786, 349, 773, 298, 4663, 2130, 477, 272, 13993, 304, 4273,
77 | 10218, 390, 378, 408, 1606, 20011, 28784, 28793, 6586, 298, 330, 1082,
78 | 2923, 1037, 12932, 2161, 28725, 17620, 293, 622, 604, 298, 272, 2130,
79 | 7474, 28713, 304, 305, 4567, 1053, 970, 590, 1567, 477, 438, 272, 948,
80 | 302, 727, 20011, 28784, 28793,
81 | ]
82 | ),
83 | ];
84 |
85 | final tokenizer = MistralTokenizer.fromBase64();
86 |
87 | var index = 0;
88 | for (final input in testData) {
89 | index++;
90 | final inputString = input.$1;
91 | final expectedTokens = input.$2;
92 | test('$index. Tokenizer encodes/decodes input correctly', () {
93 | final actualTokens = tokenizer.encode(inputString);
94 | expect(actualTokens, expectedTokens, reason: 'Input: $inputString');
95 |
96 | final actualString = tokenizer.decode(actualTokens);
97 | expect(actualString, inputString, reason: 'Input: $inputString');
98 | });
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/tool/flutter_run.bat:
--------------------------------------------------------------------------------
1 | @echo off
2 | REM script to run flutter app with environment variables
3 | REM it is recommended to use this script instead of flutter run for development
4 | REM
5 | REM run 'tool\flutter_run.bat -h' for help
6 | REM run 'tool\flutter_run.bat -d chrome' for web
7 |
8 | REM "%*" allows you to pass arguments to the script
9 | REM e.g. tool\flutter_run.bat -d chrome
10 | flutter run --dart-define-from-file=env.env %*
11 |
--------------------------------------------------------------------------------
/tool/flutter_run.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # script to run flutter app with environment variables
4 | # it is recommended to use this script instead of flutter run for development
5 | #
6 | # run './tool/flutter_run.sh -h' for help
7 | # run './tool/flutter_run.sh -d' chrome for web
8 |
9 | # "$@" allows you to pass arguments to the script
10 | # e.g. ./tool/flutter_run.sh -d chrome
11 | flutter run --dart-define-from-file=env.env "$@"
12 |
--------------------------------------------------------------------------------
/web/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nomtek/flutter_ai_examples/4fad00324f284e6003ef6f14cea32e21e9b704ba/web/favicon.png
--------------------------------------------------------------------------------
/web/icons/Icon-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nomtek/flutter_ai_examples/4fad00324f284e6003ef6f14cea32e21e9b704ba/web/icons/Icon-192.png
--------------------------------------------------------------------------------
/web/icons/Icon-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nomtek/flutter_ai_examples/4fad00324f284e6003ef6f14cea32e21e9b704ba/web/icons/Icon-512.png
--------------------------------------------------------------------------------
/web/icons/Icon-maskable-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nomtek/flutter_ai_examples/4fad00324f284e6003ef6f14cea32e21e9b704ba/web/icons/Icon-maskable-192.png
--------------------------------------------------------------------------------
/web/icons/Icon-maskable-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nomtek/flutter_ai_examples/4fad00324f284e6003ef6f14cea32e21e9b704ba/web/icons/Icon-maskable-512.png
--------------------------------------------------------------------------------
/web/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | flutter_ai_examples
33 |
34 |
35 |
39 |
40 |
41 |
42 |
43 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/web/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "flutter_ai_examples",
3 | "short_name": "flutter_ai_examples",
4 | "start_url": ".",
5 | "display": "standalone",
6 | "background_color": "#0175C2",
7 | "theme_color": "#0175C2",
8 | "description": "A new Flutter project.",
9 | "orientation": "portrait-primary",
10 | "prefer_related_applications": false,
11 | "icons": [
12 | {
13 | "src": "icons/Icon-192.png",
14 | "sizes": "192x192",
15 | "type": "image/png"
16 | },
17 | {
18 | "src": "icons/Icon-512.png",
19 | "sizes": "512x512",
20 | "type": "image/png"
21 | },
22 | {
23 | "src": "icons/Icon-maskable-192.png",
24 | "sizes": "192x192",
25 | "type": "image/png",
26 | "purpose": "maskable"
27 | },
28 | {
29 | "src": "icons/Icon-maskable-512.png",
30 | "sizes": "512x512",
31 | "type": "image/png",
32 | "purpose": "maskable"
33 | }
34 | ]
35 | }
36 |
--------------------------------------------------------------------------------
/windows/.gitignore:
--------------------------------------------------------------------------------
1 | flutter/ephemeral/
2 |
3 | # Visual Studio user-specific files.
4 | *.suo
5 | *.user
6 | *.userosscache
7 | *.sln.docstates
8 |
9 | # Visual Studio build-related files.
10 | x64/
11 | x86/
12 |
13 | # Visual Studio cache files
14 | # files ending in .cache can be ignored
15 | *.[Cc]ache
16 | # but keep track of directories ending in .cache
17 | !*.[Cc]ache/
18 |
--------------------------------------------------------------------------------
/windows/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | # Project-level configuration.
2 | cmake_minimum_required(VERSION 3.14)
3 | project(flutter_ai_examples LANGUAGES CXX)
4 |
5 | # The name of the executable created for the application. Change this to change
6 | # the on-disk name of your application.
7 | set(BINARY_NAME "flutter_ai_examples")
8 |
9 | # Explicitly opt in to modern CMake behaviors to avoid warnings with recent
10 | # versions of CMake.
11 | cmake_policy(VERSION 3.14...3.25)
12 |
13 | # Define build configuration option.
14 | get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
15 | if(IS_MULTICONFIG)
16 | set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release"
17 | CACHE STRING "" FORCE)
18 | else()
19 | if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
20 | set(CMAKE_BUILD_TYPE "Debug" CACHE
21 | STRING "Flutter build mode" FORCE)
22 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
23 | "Debug" "Profile" "Release")
24 | endif()
25 | endif()
26 | # Define settings for the Profile build mode.
27 | set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}")
28 | set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}")
29 | set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}")
30 | set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}")
31 |
32 | # Use Unicode for all projects.
33 | add_definitions(-DUNICODE -D_UNICODE)
34 |
35 | # Compilation settings that should be applied to most targets.
36 | #
37 | # Be cautious about adding new options here, as plugins use this function by
38 | # default. In most cases, you should add new options to specific targets instead
39 | # of modifying this function.
40 | function(APPLY_STANDARD_SETTINGS TARGET)
41 | target_compile_features(${TARGET} PUBLIC cxx_std_17)
42 | target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100")
43 | target_compile_options(${TARGET} PRIVATE /EHsc)
44 | target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0")
45 | target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>")
46 | endfunction()
47 |
48 | # Flutter library and tool build rules.
49 | set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
50 | add_subdirectory(${FLUTTER_MANAGED_DIR})
51 |
52 | # Application build; see runner/CMakeLists.txt.
53 | add_subdirectory("runner")
54 |
55 |
56 | # Generated plugin build rules, which manage building the plugins and adding
57 | # them to the application.
58 | include(flutter/generated_plugins.cmake)
59 |
60 |
61 | # === Installation ===
62 | # Support files are copied into place next to the executable, so that it can
63 | # run in place. This is done instead of making a separate bundle (as on Linux)
64 | # so that building and running from within Visual Studio will work.
65 | set(BUILD_BUNDLE_DIR "$")
66 | # Make the "install" step default, as it's required to run.
67 | set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1)
68 | if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
69 | set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
70 | endif()
71 |
72 | set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
73 | set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}")
74 |
75 | install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
76 | COMPONENT Runtime)
77 |
78 | install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
79 | COMPONENT Runtime)
80 |
81 | install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
82 | COMPONENT Runtime)
83 |
84 | if(PLUGIN_BUNDLED_LIBRARIES)
85 | install(FILES "${PLUGIN_BUNDLED_LIBRARIES}"
86 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
87 | COMPONENT Runtime)
88 | endif()
89 |
90 | # Copy the native assets provided by the build.dart from all packages.
91 | set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/")
92 | install(DIRECTORY "${NATIVE_ASSETS_DIR}"
93 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
94 | COMPONENT Runtime)
95 |
96 | # Fully re-copy the assets directory on each build to avoid having stale files
97 | # from a previous install.
98 | set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
99 | install(CODE "
100 | file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
101 | " COMPONENT Runtime)
102 | install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
103 | DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
104 |
105 | # Install the AOT library on non-Debug builds only.
106 | install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
107 | CONFIGURATIONS Profile;Release
108 | COMPONENT Runtime)
109 |
--------------------------------------------------------------------------------
/windows/flutter/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | # This file controls Flutter-level build steps. It should not be edited.
2 | cmake_minimum_required(VERSION 3.14)
3 |
4 | set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
5 |
6 | # Configuration provided via flutter tool.
7 | include(${EPHEMERAL_DIR}/generated_config.cmake)
8 |
9 | # TODO: Move the rest of this into files in ephemeral. See
10 | # https://github.com/flutter/flutter/issues/57146.
11 | set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper")
12 |
13 | # Set fallback configurations for older versions of the flutter tool.
14 | if (NOT DEFINED FLUTTER_TARGET_PLATFORM)
15 | set(FLUTTER_TARGET_PLATFORM "windows-x64")
16 | endif()
17 |
18 | # === Flutter Library ===
19 | set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll")
20 |
21 | # Published to parent scope for install step.
22 | set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
23 | set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
24 | set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
25 | set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE)
26 |
27 | list(APPEND FLUTTER_LIBRARY_HEADERS
28 | "flutter_export.h"
29 | "flutter_windows.h"
30 | "flutter_messenger.h"
31 | "flutter_plugin_registrar.h"
32 | "flutter_texture_registrar.h"
33 | )
34 | list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/")
35 | add_library(flutter INTERFACE)
36 | target_include_directories(flutter INTERFACE
37 | "${EPHEMERAL_DIR}"
38 | )
39 | target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib")
40 | add_dependencies(flutter flutter_assemble)
41 |
42 | # === Wrapper ===
43 | list(APPEND CPP_WRAPPER_SOURCES_CORE
44 | "core_implementations.cc"
45 | "standard_codec.cc"
46 | )
47 | list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/")
48 | list(APPEND CPP_WRAPPER_SOURCES_PLUGIN
49 | "plugin_registrar.cc"
50 | )
51 | list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/")
52 | list(APPEND CPP_WRAPPER_SOURCES_APP
53 | "flutter_engine.cc"
54 | "flutter_view_controller.cc"
55 | )
56 | list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/")
57 |
58 | # Wrapper sources needed for a plugin.
59 | add_library(flutter_wrapper_plugin STATIC
60 | ${CPP_WRAPPER_SOURCES_CORE}
61 | ${CPP_WRAPPER_SOURCES_PLUGIN}
62 | )
63 | apply_standard_settings(flutter_wrapper_plugin)
64 | set_target_properties(flutter_wrapper_plugin PROPERTIES
65 | POSITION_INDEPENDENT_CODE ON)
66 | set_target_properties(flutter_wrapper_plugin PROPERTIES
67 | CXX_VISIBILITY_PRESET hidden)
68 | target_link_libraries(flutter_wrapper_plugin PUBLIC flutter)
69 | target_include_directories(flutter_wrapper_plugin PUBLIC
70 | "${WRAPPER_ROOT}/include"
71 | )
72 | add_dependencies(flutter_wrapper_plugin flutter_assemble)
73 |
74 | # Wrapper sources needed for the runner.
75 | add_library(flutter_wrapper_app STATIC
76 | ${CPP_WRAPPER_SOURCES_CORE}
77 | ${CPP_WRAPPER_SOURCES_APP}
78 | )
79 | apply_standard_settings(flutter_wrapper_app)
80 | target_link_libraries(flutter_wrapper_app PUBLIC flutter)
81 | target_include_directories(flutter_wrapper_app PUBLIC
82 | "${WRAPPER_ROOT}/include"
83 | )
84 | add_dependencies(flutter_wrapper_app flutter_assemble)
85 |
86 | # === Flutter tool backend ===
87 | # _phony_ is a non-existent file to force this command to run every time,
88 | # since currently there's no way to get a full input/output list from the
89 | # flutter tool.
90 | set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_")
91 | set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE)
92 | add_custom_command(
93 | OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
94 | ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN}
95 | ${CPP_WRAPPER_SOURCES_APP}
96 | ${PHONY_OUTPUT}
97 | COMMAND ${CMAKE_COMMAND} -E env
98 | ${FLUTTER_TOOL_ENVIRONMENT}
99 | "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat"
100 | ${FLUTTER_TARGET_PLATFORM} $
101 | VERBATIM
102 | )
103 | add_custom_target(flutter_assemble DEPENDS
104 | "${FLUTTER_LIBRARY}"
105 | ${FLUTTER_LIBRARY_HEADERS}
106 | ${CPP_WRAPPER_SOURCES_CORE}
107 | ${CPP_WRAPPER_SOURCES_PLUGIN}
108 | ${CPP_WRAPPER_SOURCES_APP}
109 | )
110 |
--------------------------------------------------------------------------------
/windows/flutter/generated_plugin_registrant.cc:
--------------------------------------------------------------------------------
1 | //
2 | // Generated file. Do not edit.
3 | //
4 |
5 | // clang-format off
6 |
7 | #include "generated_plugin_registrant.h"
8 |
9 | #include
10 |
11 | void RegisterPlugins(flutter::PluginRegistry* registry) {
12 | UrlLauncherWindowsRegisterWithRegistrar(
13 | registry->GetRegistrarForPlugin("UrlLauncherWindows"));
14 | }
15 |
--------------------------------------------------------------------------------
/windows/flutter/generated_plugin_registrant.h:
--------------------------------------------------------------------------------
1 | //
2 | // Generated file. Do not edit.
3 | //
4 |
5 | // clang-format off
6 |
7 | #ifndef GENERATED_PLUGIN_REGISTRANT_
8 | #define GENERATED_PLUGIN_REGISTRANT_
9 |
10 | #include
11 |
12 | // Registers Flutter plugins.
13 | void RegisterPlugins(flutter::PluginRegistry* registry);
14 |
15 | #endif // GENERATED_PLUGIN_REGISTRANT_
16 |
--------------------------------------------------------------------------------
/windows/flutter/generated_plugins.cmake:
--------------------------------------------------------------------------------
1 | #
2 | # Generated file, do not edit.
3 | #
4 |
5 | list(APPEND FLUTTER_PLUGIN_LIST
6 | url_launcher_windows
7 | )
8 |
9 | list(APPEND FLUTTER_FFI_PLUGIN_LIST
10 | )
11 |
12 | set(PLUGIN_BUNDLED_LIBRARIES)
13 |
14 | foreach(plugin ${FLUTTER_PLUGIN_LIST})
15 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin})
16 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
17 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $)
18 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
19 | endforeach(plugin)
20 |
21 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
22 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin})
23 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
24 | endforeach(ffi_plugin)
25 |
--------------------------------------------------------------------------------
/windows/runner/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.14)
2 | project(runner LANGUAGES CXX)
3 |
4 | # Define the application target. To change its name, change BINARY_NAME in the
5 | # top-level CMakeLists.txt, not the value here, or `flutter run` will no longer
6 | # work.
7 | #
8 | # Any new source files that you add to the application should be added here.
9 | add_executable(${BINARY_NAME} WIN32
10 | "flutter_window.cpp"
11 | "main.cpp"
12 | "utils.cpp"
13 | "win32_window.cpp"
14 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
15 | "Runner.rc"
16 | "runner.exe.manifest"
17 | )
18 |
19 | # Apply the standard set of build settings. This can be removed for applications
20 | # that need different build settings.
21 | apply_standard_settings(${BINARY_NAME})
22 |
23 | # Add preprocessor definitions for the build version.
24 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"")
25 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}")
26 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}")
27 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}")
28 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}")
29 |
30 | # Disable Windows macros that collide with C++ standard library functions.
31 | target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX")
32 |
33 | # Add dependency libraries and include directories. Add any application-specific
34 | # dependencies here.
35 | target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app)
36 | target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib")
37 | target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")
38 |
39 | # Run the Flutter tool portions of the build. This must not be removed.
40 | add_dependencies(${BINARY_NAME} flutter_assemble)
41 |
--------------------------------------------------------------------------------
/windows/runner/Runner.rc:
--------------------------------------------------------------------------------
1 | // Microsoft Visual C++ generated resource script.
2 | //
3 | #pragma code_page(65001)
4 | #include "resource.h"
5 |
6 | #define APSTUDIO_READONLY_SYMBOLS
7 | /////////////////////////////////////////////////////////////////////////////
8 | //
9 | // Generated from the TEXTINCLUDE 2 resource.
10 | //
11 | #include "winres.h"
12 |
13 | /////////////////////////////////////////////////////////////////////////////
14 | #undef APSTUDIO_READONLY_SYMBOLS
15 |
16 | /////////////////////////////////////////////////////////////////////////////
17 | // English (United States) resources
18 |
19 | #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
20 | LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
21 |
22 | #ifdef APSTUDIO_INVOKED
23 | /////////////////////////////////////////////////////////////////////////////
24 | //
25 | // TEXTINCLUDE
26 | //
27 |
28 | 1 TEXTINCLUDE
29 | BEGIN
30 | "resource.h\0"
31 | END
32 |
33 | 2 TEXTINCLUDE
34 | BEGIN
35 | "#include ""winres.h""\r\n"
36 | "\0"
37 | END
38 |
39 | 3 TEXTINCLUDE
40 | BEGIN
41 | "\r\n"
42 | "\0"
43 | END
44 |
45 | #endif // APSTUDIO_INVOKED
46 |
47 |
48 | /////////////////////////////////////////////////////////////////////////////
49 | //
50 | // Icon
51 | //
52 |
53 | // Icon with lowest ID value placed first to ensure application icon
54 | // remains consistent on all systems.
55 | IDI_APP_ICON ICON "resources\\app_icon.ico"
56 |
57 |
58 | /////////////////////////////////////////////////////////////////////////////
59 | //
60 | // Version
61 | //
62 |
63 | #if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD)
64 | #define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD
65 | #else
66 | #define VERSION_AS_NUMBER 1,0,0,0
67 | #endif
68 |
69 | #if defined(FLUTTER_VERSION)
70 | #define VERSION_AS_STRING FLUTTER_VERSION
71 | #else
72 | #define VERSION_AS_STRING "1.0.0"
73 | #endif
74 |
75 | VS_VERSION_INFO VERSIONINFO
76 | FILEVERSION VERSION_AS_NUMBER
77 | PRODUCTVERSION VERSION_AS_NUMBER
78 | FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
79 | #ifdef _DEBUG
80 | FILEFLAGS VS_FF_DEBUG
81 | #else
82 | FILEFLAGS 0x0L
83 | #endif
84 | FILEOS VOS__WINDOWS32
85 | FILETYPE VFT_APP
86 | FILESUBTYPE 0x0L
87 | BEGIN
88 | BLOCK "StringFileInfo"
89 | BEGIN
90 | BLOCK "040904e4"
91 | BEGIN
92 | VALUE "CompanyName", "com.example" "\0"
93 | VALUE "FileDescription", "flutter_ai_examples" "\0"
94 | VALUE "FileVersion", VERSION_AS_STRING "\0"
95 | VALUE "InternalName", "flutter_ai_examples" "\0"
96 | VALUE "LegalCopyright", "Copyright (C) 2024 com.example. All rights reserved." "\0"
97 | VALUE "OriginalFilename", "flutter_ai_examples.exe" "\0"
98 | VALUE "ProductName", "flutter_ai_examples" "\0"
99 | VALUE "ProductVersion", VERSION_AS_STRING "\0"
100 | END
101 | END
102 | BLOCK "VarFileInfo"
103 | BEGIN
104 | VALUE "Translation", 0x409, 1252
105 | END
106 | END
107 |
108 | #endif // English (United States) resources
109 | /////////////////////////////////////////////////////////////////////////////
110 |
111 |
112 |
113 | #ifndef APSTUDIO_INVOKED
114 | /////////////////////////////////////////////////////////////////////////////
115 | //
116 | // Generated from the TEXTINCLUDE 3 resource.
117 | //
118 |
119 |
120 | /////////////////////////////////////////////////////////////////////////////
121 | #endif // not APSTUDIO_INVOKED
122 |
--------------------------------------------------------------------------------
/windows/runner/flutter_window.cpp:
--------------------------------------------------------------------------------
1 | #include "flutter_window.h"
2 |
3 | #include
4 |
5 | #include "flutter/generated_plugin_registrant.h"
6 |
7 | FlutterWindow::FlutterWindow(const flutter::DartProject& project)
8 | : project_(project) {}
9 |
10 | FlutterWindow::~FlutterWindow() {}
11 |
12 | bool FlutterWindow::OnCreate() {
13 | if (!Win32Window::OnCreate()) {
14 | return false;
15 | }
16 |
17 | RECT frame = GetClientArea();
18 |
19 | // The size here must match the window dimensions to avoid unnecessary surface
20 | // creation / destruction in the startup path.
21 | flutter_controller_ = std::make_unique(
22 | frame.right - frame.left, frame.bottom - frame.top, project_);
23 | // Ensure that basic setup of the controller was successful.
24 | if (!flutter_controller_->engine() || !flutter_controller_->view()) {
25 | return false;
26 | }
27 | RegisterPlugins(flutter_controller_->engine());
28 | SetChildContent(flutter_controller_->view()->GetNativeWindow());
29 |
30 | flutter_controller_->engine()->SetNextFrameCallback([&]() {
31 | this->Show();
32 | });
33 |
34 | // Flutter can complete the first frame before the "show window" callback is
35 | // registered. The following call ensures a frame is pending to ensure the
36 | // window is shown. It is a no-op if the first frame hasn't completed yet.
37 | flutter_controller_->ForceRedraw();
38 |
39 | return true;
40 | }
41 |
42 | void FlutterWindow::OnDestroy() {
43 | if (flutter_controller_) {
44 | flutter_controller_ = nullptr;
45 | }
46 |
47 | Win32Window::OnDestroy();
48 | }
49 |
50 | LRESULT
51 | FlutterWindow::MessageHandler(HWND hwnd, UINT const message,
52 | WPARAM const wparam,
53 | LPARAM const lparam) noexcept {
54 | // Give Flutter, including plugins, an opportunity to handle window messages.
55 | if (flutter_controller_) {
56 | std::optional result =
57 | flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam,
58 | lparam);
59 | if (result) {
60 | return *result;
61 | }
62 | }
63 |
64 | switch (message) {
65 | case WM_FONTCHANGE:
66 | flutter_controller_->engine()->ReloadSystemFonts();
67 | break;
68 | }
69 |
70 | return Win32Window::MessageHandler(hwnd, message, wparam, lparam);
71 | }
72 |
--------------------------------------------------------------------------------
/windows/runner/flutter_window.h:
--------------------------------------------------------------------------------
1 | #ifndef RUNNER_FLUTTER_WINDOW_H_
2 | #define RUNNER_FLUTTER_WINDOW_H_
3 |
4 | #include
5 | #include
6 |
7 | #include
8 |
9 | #include "win32_window.h"
10 |
11 | // A window that does nothing but host a Flutter view.
12 | class FlutterWindow : public Win32Window {
13 | public:
14 | // Creates a new FlutterWindow hosting a Flutter view running |project|.
15 | explicit FlutterWindow(const flutter::DartProject& project);
16 | virtual ~FlutterWindow();
17 |
18 | protected:
19 | // Win32Window:
20 | bool OnCreate() override;
21 | void OnDestroy() override;
22 | LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam,
23 | LPARAM const lparam) noexcept override;
24 |
25 | private:
26 | // The project to run.
27 | flutter::DartProject project_;
28 |
29 | // The Flutter instance hosted by this window.
30 | std::unique_ptr flutter_controller_;
31 | };
32 |
33 | #endif // RUNNER_FLUTTER_WINDOW_H_
34 |
--------------------------------------------------------------------------------
/windows/runner/main.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 |
5 | #include "flutter_window.h"
6 | #include "utils.h"
7 |
8 | int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
9 | _In_ wchar_t *command_line, _In_ int show_command) {
10 | // Attach to console when present (e.g., 'flutter run') or create a
11 | // new console when running with a debugger.
12 | if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) {
13 | CreateAndAttachConsole();
14 | }
15 |
16 | // Initialize COM, so that it is available for use in the library and/or
17 | // plugins.
18 | ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
19 |
20 | flutter::DartProject project(L"data");
21 |
22 | std::vector command_line_arguments =
23 | GetCommandLineArguments();
24 |
25 | project.set_dart_entrypoint_arguments(std::move(command_line_arguments));
26 |
27 | FlutterWindow window(project);
28 | Win32Window::Point origin(10, 10);
29 | Win32Window::Size size(1280, 720);
30 | if (!window.Create(L"flutter_ai_examples", origin, size)) {
31 | return EXIT_FAILURE;
32 | }
33 | window.SetQuitOnClose(true);
34 |
35 | ::MSG msg;
36 | while (::GetMessage(&msg, nullptr, 0, 0)) {
37 | ::TranslateMessage(&msg);
38 | ::DispatchMessage(&msg);
39 | }
40 |
41 | ::CoUninitialize();
42 | return EXIT_SUCCESS;
43 | }
44 |
--------------------------------------------------------------------------------
/windows/runner/resource.h:
--------------------------------------------------------------------------------
1 | //{{NO_DEPENDENCIES}}
2 | // Microsoft Visual C++ generated include file.
3 | // Used by Runner.rc
4 | //
5 | #define IDI_APP_ICON 101
6 |
7 | // Next default values for new objects
8 | //
9 | #ifdef APSTUDIO_INVOKED
10 | #ifndef APSTUDIO_READONLY_SYMBOLS
11 | #define _APS_NEXT_RESOURCE_VALUE 102
12 | #define _APS_NEXT_COMMAND_VALUE 40001
13 | #define _APS_NEXT_CONTROL_VALUE 1001
14 | #define _APS_NEXT_SYMED_VALUE 101
15 | #endif
16 | #endif
17 |
--------------------------------------------------------------------------------
/windows/runner/resources/app_icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nomtek/flutter_ai_examples/4fad00324f284e6003ef6f14cea32e21e9b704ba/windows/runner/resources/app_icon.ico
--------------------------------------------------------------------------------
/windows/runner/runner.exe.manifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PerMonitorV2
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/windows/runner/utils.cpp:
--------------------------------------------------------------------------------
1 | #include "utils.h"
2 |
3 | #include
4 | #include
5 | #include
6 | #include
7 |
8 | #include
9 |
10 | void CreateAndAttachConsole() {
11 | if (::AllocConsole()) {
12 | FILE *unused;
13 | if (freopen_s(&unused, "CONOUT$", "w", stdout)) {
14 | _dup2(_fileno(stdout), 1);
15 | }
16 | if (freopen_s(&unused, "CONOUT$", "w", stderr)) {
17 | _dup2(_fileno(stdout), 2);
18 | }
19 | std::ios::sync_with_stdio();
20 | FlutterDesktopResyncOutputStreams();
21 | }
22 | }
23 |
24 | std::vector GetCommandLineArguments() {
25 | // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use.
26 | int argc;
27 | wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc);
28 | if (argv == nullptr) {
29 | return std::vector();
30 | }
31 |
32 | std::vector command_line_arguments;
33 |
34 | // Skip the first argument as it's the binary name.
35 | for (int i = 1; i < argc; i++) {
36 | command_line_arguments.push_back(Utf8FromUtf16(argv[i]));
37 | }
38 |
39 | ::LocalFree(argv);
40 |
41 | return command_line_arguments;
42 | }
43 |
44 | std::string Utf8FromUtf16(const wchar_t* utf16_string) {
45 | if (utf16_string == nullptr) {
46 | return std::string();
47 | }
48 | int target_length = ::WideCharToMultiByte(
49 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string,
50 | -1, nullptr, 0, nullptr, nullptr)
51 | -1; // remove the trailing null character
52 | int input_length = (int)wcslen(utf16_string);
53 | std::string utf8_string;
54 | if (target_length <= 0 || target_length > utf8_string.max_size()) {
55 | return utf8_string;
56 | }
57 | utf8_string.resize(target_length);
58 | int converted_length = ::WideCharToMultiByte(
59 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string,
60 | input_length, utf8_string.data(), target_length, nullptr, nullptr);
61 | if (converted_length == 0) {
62 | return std::string();
63 | }
64 | return utf8_string;
65 | }
66 |
--------------------------------------------------------------------------------
/windows/runner/utils.h:
--------------------------------------------------------------------------------
1 | #ifndef RUNNER_UTILS_H_
2 | #define RUNNER_UTILS_H_
3 |
4 | #include
5 | #include
6 |
7 | // Creates a console for the process, and redirects stdout and stderr to
8 | // it for both the runner and the Flutter library.
9 | void CreateAndAttachConsole();
10 |
11 | // Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string
12 | // encoded in UTF-8. Returns an empty std::string on failure.
13 | std::string Utf8FromUtf16(const wchar_t* utf16_string);
14 |
15 | // Gets the command line arguments passed in as a std::vector,
16 | // encoded in UTF-8. Returns an empty std::vector on failure.
17 | std::vector GetCommandLineArguments();
18 |
19 | #endif // RUNNER_UTILS_H_
20 |
--------------------------------------------------------------------------------
/windows/runner/win32_window.h:
--------------------------------------------------------------------------------
1 | #ifndef RUNNER_WIN32_WINDOW_H_
2 | #define RUNNER_WIN32_WINDOW_H_
3 |
4 | #include
5 |
6 | #include
7 | #include
8 | #include
9 |
10 | // A class abstraction for a high DPI-aware Win32 Window. Intended to be
11 | // inherited from by classes that wish to specialize with custom
12 | // rendering and input handling
13 | class Win32Window {
14 | public:
15 | struct Point {
16 | unsigned int x;
17 | unsigned int y;
18 | Point(unsigned int x, unsigned int y) : x(x), y(y) {}
19 | };
20 |
21 | struct Size {
22 | unsigned int width;
23 | unsigned int height;
24 | Size(unsigned int width, unsigned int height)
25 | : width(width), height(height) {}
26 | };
27 |
28 | Win32Window();
29 | virtual ~Win32Window();
30 |
31 | // Creates a win32 window with |title| that is positioned and sized using
32 | // |origin| and |size|. New windows are created on the default monitor. Window
33 | // sizes are specified to the OS in physical pixels, hence to ensure a
34 | // consistent size this function will scale the inputted width and height as
35 | // as appropriate for the default monitor. The window is invisible until
36 | // |Show| is called. Returns true if the window was created successfully.
37 | bool Create(const std::wstring& title, const Point& origin, const Size& size);
38 |
39 | // Show the current window. Returns true if the window was successfully shown.
40 | bool Show();
41 |
42 | // Release OS resources associated with window.
43 | void Destroy();
44 |
45 | // Inserts |content| into the window tree.
46 | void SetChildContent(HWND content);
47 |
48 | // Returns the backing Window handle to enable clients to set icon and other
49 | // window properties. Returns nullptr if the window has been destroyed.
50 | HWND GetHandle();
51 |
52 | // If true, closing this window will quit the application.
53 | void SetQuitOnClose(bool quit_on_close);
54 |
55 | // Return a RECT representing the bounds of the current client area.
56 | RECT GetClientArea();
57 |
58 | protected:
59 | // Processes and route salient window messages for mouse handling,
60 | // size change and DPI. Delegates handling of these to member overloads that
61 | // inheriting classes can handle.
62 | virtual LRESULT MessageHandler(HWND window,
63 | UINT const message,
64 | WPARAM const wparam,
65 | LPARAM const lparam) noexcept;
66 |
67 | // Called when CreateAndShow is called, allowing subclass window-related
68 | // setup. Subclasses should return false if setup fails.
69 | virtual bool OnCreate();
70 |
71 | // Called when Destroy is called.
72 | virtual void OnDestroy();
73 |
74 | private:
75 | friend class WindowClassRegistrar;
76 |
77 | // OS callback called by message pump. Handles the WM_NCCREATE message which
78 | // is passed when the non-client area is being created and enables automatic
79 | // non-client DPI scaling so that the non-client area automatically
80 | // responds to changes in DPI. All other messages are handled by
81 | // MessageHandler.
82 | static LRESULT CALLBACK WndProc(HWND const window,
83 | UINT const message,
84 | WPARAM const wparam,
85 | LPARAM const lparam) noexcept;
86 |
87 | // Retrieves a class instance pointer for |window|
88 | static Win32Window* GetThisFromHandle(HWND const window) noexcept;
89 |
90 | // Update the window frame's theme to match the system theme.
91 | static void UpdateTheme(HWND const window);
92 |
93 | bool quit_on_close_ = false;
94 |
95 | // window handle for top level window.
96 | HWND window_handle_ = nullptr;
97 |
98 | // window handle for hosted content.
99 | HWND child_content_ = nullptr;
100 | };
101 |
102 | #endif // RUNNER_WIN32_WINDOW_H_
103 |
--------------------------------------------------------------------------------