├── .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 | [![App settings](docs/assets/app_settings_navigation.png)](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 | [![Demo](../../docs/assets/llm_controller_example_demo.gif)](../../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 | [![Demo](../../docs/assets/text_summary_example_demo.gif)](../../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 | --------------------------------------------------------------------------------