diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index dc8fb824..88c60750 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -5,7 +5,7 @@ permissions: read-all on: pull_request: - branches: [ main ] + branches: [main] push: branches: [main, ffi-wrapper, ffi-wrapper-text-pkg] schedule: diff --git a/Makefile b/Makefile index c6c007c5..82b80978 100644 --- a/Makefile +++ b/Makefile @@ -57,4 +57,17 @@ test_text: cd packages/mediapipe-task-text/example && flutter test example_text: - cd packages/mediapipe-task-text/example && flutter run -d macos \ No newline at end of file + cd packages/mediapipe-task-text/example && flutter run -d macos + +# GenAI --- +generate_genai: + cd packages/mediapipe-task-genai && dart --enable-experiment=native-assets run ffigen --config=ffigen.yaml + +# Example genai invocation. +# Note that `GEMMA_4B_CPU_URI` can either be a local path or web URL. Similar values exist for +# 8B and GPU variants. +# +# For desktop development, standard environment variables like this work great. +# $ GEMMA_4B_CPU_URI=/path/to/gemma-2b-it-cpu-int4.bin flutter run -d [macos, windows, linux] +# For emulator or attached device testing, use `--dart-define` for the same values. +# $ flutter run -d [] --dart-define=GEMMA_4B_CPU_URI=https://url/to.com/gemma-2b-it-cpu-int4.bin diff --git a/packages/mediapipe-core/lib/src/interface/task_options.dart b/packages/mediapipe-core/lib/src/interface/task_options.dart index f39e5bde..9550a25d 100644 --- a/packages/mediapipe-core/lib/src/interface/task_options.dart +++ b/packages/mediapipe-core/lib/src/interface/task_options.dart @@ -6,9 +6,12 @@ import 'dart:typed_data'; import 'package:equatable/equatable.dart'; -/// {@template TaskOptions} +/// {@template BaseOptions} /// Root class for options classes for MediaPipe tasks. -/// +/// {@endtemplate} +abstract class Options extends Equatable {} + +/// {@template TaskOptions} /// Implementing classes will contain two [BaseInnerTaskOptions] subclasses, /// including a descendent of the universal options struct, [BaseBaseOptions]. /// The second field will be task-specific. @@ -17,7 +20,7 @@ import 'package:equatable/equatable.dart'; /// This implementation is not immutable to track whether `dispose` has been /// called. All values used by pkg:equatable are in fact immutable. // ignore: must_be_immutable -abstract class BaseTaskOptions extends Equatable { +abstract class BaseTaskOptions extends Options { /// {@macro TaskOptions} BaseTaskOptions(); diff --git a/packages/mediapipe-core/lib/src/io/ffi_utils.dart b/packages/mediapipe-core/lib/src/io/ffi_utils.dart index 6403cee3..acbdd060 100644 --- a/packages/mediapipe-core/lib/src/io/ffi_utils.dart +++ b/packages/mediapipe-core/lib/src/io/ffi_utils.dart @@ -90,11 +90,11 @@ extension DartAwarePointerChars on Pointer> { /// /// See also: /// * [toDartString], for a non-list equivalent. - List toDartStrings(int length) { + List toDartStrings(int length) { if (isNullPointer) { throw Exception('Unexpectedly called `toDartStrings` on nullptr'); } - final dartStrings = []; + final dartStrings = []; int counter = 0; while (counter < length) { dartStrings.add(this[counter].toDartString()); diff --git a/packages/mediapipe-core/lib/src/io/task_options.dart b/packages/mediapipe-core/lib/src/io/task_options.dart index 450ac310..2f453cb8 100644 --- a/packages/mediapipe-core/lib/src/io/task_options.dart +++ b/packages/mediapipe-core/lib/src/io/task_options.dart @@ -15,7 +15,7 @@ import 'third_party/mediapipe/generated/mediapipe_common_bindings.dart' /// should manage their [InnerTaskOptions] fields. The two suggested methods are /// [copyToNative] and [dispose]. /// {@endtemplate} -mixin TaskOptions on BaseTaskOptions { +mixin TaskOptions on Options { /// {@template TaskOptions.copyToNative} /// Copies these task options into native memory. Any fields of type /// [InnerTaskOptions] should have their `assignToStruct` method called. diff --git a/packages/mediapipe-core/test/io/task_options_test.dart b/packages/mediapipe-core/test/io/task_options_test.dart index 31f5b0dd..f3d4d720 100644 --- a/packages/mediapipe-core/test/io/task_options_test.dart +++ b/packages/mediapipe-core/test/io/task_options_test.dart @@ -86,13 +86,17 @@ void main() { expect(ptr.ref.score_threshold, lessThan(0.90001)); expect(ptr.ref.category_allowlist_count, 3); expect( - ptr.ref.category_allowlist.toDartStrings(3), + ptr.ref.category_allowlist.toDartStrings( + 3, + ), ['good', 'great', 'best'], ); expect(ptr.ref.category_denylist_count, 4); expect( - ptr.ref.category_denylist.toDartStrings(4), + ptr.ref.category_denylist.toDartStrings( + 4, + ), ['bad', 'terrible', 'worst', 'honestly come on'], ); }); diff --git a/packages/mediapipe-task-audio/sdk_downloads.dart b/packages/mediapipe-task-audio/sdk_downloads.dart index 1f0512e2..cd327001 100644 --- a/packages/mediapipe-task-audio/sdk_downloads.dart +++ b/packages/mediapipe-task-audio/sdk_downloads.dart @@ -1,2 +1,4 @@ // Generated file. Do not manually edit. -final Map> sdkDownloadUrls = {}; +// Used by the flutter toolchain (via build.dart) during compilation of any +// Flutter app using this package. +final Map>> sdkDownloadUrls = {}; diff --git a/packages/mediapipe-task-genai/.gitignore b/packages/mediapipe-task-genai/.gitignore new file mode 100644 index 00000000..ac5aa989 --- /dev/null +++ b/packages/mediapipe-task-genai/.gitignore @@ -0,0 +1,29 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. +/pubspec.lock +**/doc/api/ +.dart_tool/ +build/ diff --git a/packages/mediapipe-task-genai/.metadata b/packages/mediapipe-task-genai/.metadata new file mode 100644 index 00000000..a3b1a1a2 --- /dev/null +++ b/packages/mediapipe-task-genai/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "bd909542a33ab1d5249363a2434ae50ee468094f" + channel: "master" + +project_type: package diff --git a/packages/mediapipe-task-genai/CHANGELOG.md b/packages/mediapipe-task-genai/CHANGELOG.md new file mode 100644 index 00000000..41cc7d81 --- /dev/null +++ b/packages/mediapipe-task-genai/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.0.1 + +* TODO: Describe initial release. diff --git a/packages/mediapipe-task-genai/LICENSE b/packages/mediapipe-task-genai/LICENSE new file mode 100644 index 00000000..ba75c69f --- /dev/null +++ b/packages/mediapipe-task-genai/LICENSE @@ -0,0 +1 @@ +TODO: Add your license here. diff --git a/packages/mediapipe-task-genai/README.md b/packages/mediapipe-task-genai/README.md new file mode 100644 index 00000000..02fe8eca --- /dev/null +++ b/packages/mediapipe-task-genai/README.md @@ -0,0 +1,39 @@ + + +TODO: Put a short description of the package here that helps potential users +know whether this package might be useful for them. + +## Features + +TODO: List what your package can do. Maybe include images, gifs, or videos. + +## Getting started + +TODO: List prerequisites and provide or point to information on how to +start using the package. + +## Usage + +TODO: Include short and useful examples for package users. Add longer examples +to `/example` folder. + +```dart +const like = 'sample'; +``` + +## Additional information + +TODO: Tell users more about the package: where to find more information, how to +contribute to the package, how to file issues, what response they can expect +from the package authors, and more. diff --git a/packages/mediapipe-task-genai/analysis_options.yaml b/packages/mediapipe-task-genai/analysis_options.yaml new file mode 100644 index 00000000..d2dbdacf --- /dev/null +++ b/packages/mediapipe-task-genai/analysis_options.yaml @@ -0,0 +1,9 @@ +include: ../analysis_options.yaml + +linter: + rules: + - public_member_api_docs # see https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#documentation-dartdocs-javadocs-etc + +analyzer: + exclude: + - "**/mediapipe_genai_bindings.dart" diff --git a/packages/mediapipe-task-genai/build.dart b/packages/mediapipe-task-genai/build.dart new file mode 100644 index 00000000..fb63b000 --- /dev/null +++ b/packages/mediapipe-task-genai/build.dart @@ -0,0 +1,122 @@ +import 'dart:io'; +import 'package:native_assets_cli/native_assets_cli.dart'; +import 'package:http/http.dart' as http; +import 'package:path/path.dart' as path; + +import 'sdk_downloads.dart'; + +late File logFile; + +final logs = <(DateTime, String)>[]; +void log(String msg) { + logs.add((DateTime.now(), msg)); + if (!logFile.parent.existsSync()) { + logFile.parent.createSync(); + } + + if (logFile.existsSync()) { + logFile.deleteSync(); + } + logFile.createSync(); + logFile.writeAsStringSync(logs + .map((rec) => '[${rec.$1.toIso8601String()}] ${rec.$2}') + .toList() + .join('\n\n')); +} + +Future main(List args) async { + final buildConfig = await BuildConfig.fromArgs(args); + logFile = File( + path.joinAll([ + Directory.current.path, // root dir of app using `mediapipe-task-xyz` + 'build/${buildConfig.dryRun ? "dryrun" : "live-run"}-build-log.txt', + ]), + ); + + log(args.join(' ')); + final String targetOs = buildConfig.targetOs.toString(); + + log('dir.current: ${Directory.current.absolute.path}'); + + // Throw if target runtime is unsupported. + if (!sdkDownloadUrls.containsKey(targetOs)) { + throw Exception('Unsupported target OS: $targetOs. ' + 'Supported values are: ${sdkDownloadUrls.keys.toSet()}'); + } + + final buildOutput = BuildOutput(); + buildOutput.dependencies.dependencies + .add(buildConfig.packageRoot.resolve('build.dart')); + buildOutput.dependencies.dependencies + .add(buildConfig.packageRoot.resolve('sdk_downloads.dart')); + + final modelName = 'libllm_inference_engine'; + final Iterable archKeys; + if (buildConfig.dryRun) { + archKeys = sdkDownloadUrls[targetOs]![modelName]!.keys; + } else { + archKeys = [buildConfig.targetArchitecture.toString()]; + } + for (String arch in archKeys) { + arch = getArchAlias(arch); + log("arch: $arch"); + log("sdkDownloadUrls[targetOs]: ${sdkDownloadUrls[targetOs]}"); + log("sdkDownloadUrls[targetOs]['$modelName']: ${sdkDownloadUrls[targetOs]![modelName]}"); + log("sdkDownloadUrls[targetOs]['$modelName'][$arch]: ${sdkDownloadUrls[targetOs]![modelName]![arch]}"); + + if (!sdkDownloadUrls[targetOs]!['libllm_inference_engine']! + .containsKey(arch)) { + continue; + } + final assetUrl = + sdkDownloadUrls[targetOs]!['libllm_inference_engine']![arch]!; + final downloadFileLocation = buildConfig.outDir.resolve( + '${arch}_${assetUrl.split('/').last}', + ); + log('downloadFileLocation: $downloadFileLocation'); + buildOutput.assets.add( + Asset( + id: 'package:mediapipe_genai/src/io/third_party/mediapipe/generated/mediapipe_genai_bindings.dart', + linkMode: LinkMode.dynamic, + target: Target.fromArchitectureAndOs( + Architecture.fromString(arch), buildConfig.targetOs), + path: AssetAbsolutePath(downloadFileLocation), + ), + ); + if (!buildConfig.dryRun) { + downloadAsset(assetUrl, downloadFileLocation); + } + } + + await buildOutput.writeToFile(outDir: buildConfig.outDir); +} + +Future downloadAsset(String assetUrl, Uri destinationFile) async { + final downloadUri = Uri.parse(assetUrl); + final downloadedFile = File(destinationFile.toFilePath()); + log('Saving file to ${downloadedFile.absolute.path}'); + + final downloadResponse = await http.get(downloadUri); + log('Download response: ${downloadResponse.statusCode}'); + + if (downloadResponse.statusCode == 200) { + if (downloadedFile.existsSync()) { + downloadedFile.deleteSync(); + } + downloadedFile.createSync(); + log('Saved file to ${downloadedFile.absolute.path}\n'); + downloadedFile.writeAsBytes(downloadResponse.bodyBytes); + } else { + log('${downloadResponse.statusCode} :: ${downloadResponse.body}'); + throw Exception( + '${downloadResponse.statusCode} :: ${downloadResponse.body}'); + } +} + +/// Translates native-assets architecture names into MediaPipe architecture names +String getArchAlias(String arch) { + return { + 'arm': 'arm64', + }[arch] ?? + arch; +} diff --git a/packages/mediapipe-task-genai/example/.gitignore b/packages/mediapipe-task-genai/example/.gitignore new file mode 100644 index 00000000..29a3a501 --- /dev/null +++ b/packages/mediapipe-task-genai/example/.gitignore @@ -0,0 +1,43 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.pub-cache/ +.pub/ +/build/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/packages/mediapipe-task-genai/example/.metadata b/packages/mediapipe-task-genai/example/.metadata new file mode 100644 index 00000000..fb83e613 --- /dev/null +++ b/packages/mediapipe-task-genai/example/.metadata @@ -0,0 +1,30 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "86135b7774e32fa7b0ad0d116511471f36048579" + channel: "master" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 86135b7774e32fa7b0ad0d116511471f36048579 + base_revision: 86135b7774e32fa7b0ad0d116511471f36048579 + - platform: ios + create_revision: 86135b7774e32fa7b0ad0d116511471f36048579 + base_revision: 86135b7774e32fa7b0ad0d116511471f36048579 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/packages/mediapipe-task-genai/example/.vscode/launch.json b/packages/mediapipe-task-genai/example/.vscode/launch.json new file mode 100644 index 00000000..1e31c356 --- /dev/null +++ b/packages/mediapipe-task-genai/example/.vscode/launch.json @@ -0,0 +1,16 @@ +{ + "configurations": [ + { + "name": "Flutter", + "request": "launch", + "type": "dart", + "env": { + "GEMMA_4B_CPU_URI": "https://storage.googleapis.com/random-storage-asdf/gemma/gemma-2b-it-cpu-int4.bin", + "GEMMA_4B_GPU_URI": "https://storage.googleapis.com/random-storage-asdf/gemma/gemma-2b-it-gpu-int4.bin", + "GEMMA_8B_CPU_URI": "https://storage.googleapis.com/random-storage-asdf/gemma/gemma-2b-it-cpu-int8.bin", + "GEMMA_8B_GPU_URI": "https://storage.googleapis.com/random-storage-asdf/gemma/gemma-2b-it-gpu-int8.bin", + }, + "toolArgs": [] + } + ] +} \ No newline at end of file diff --git a/packages/mediapipe-task-genai/example/README.md b/packages/mediapipe-task-genai/example/README.md new file mode 100644 index 00000000..2b3fce4c --- /dev/null +++ b/packages/mediapipe-task-genai/example/README.md @@ -0,0 +1,16 @@ +# example + +A new Flutter project. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) + +For help getting started with Flutter development, view the +[online documentation](https://docs.flutter.dev/), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/packages/mediapipe-task-genai/example/analysis_options.yaml b/packages/mediapipe-task-genai/example/analysis_options.yaml new file mode 100644 index 00000000..0d290213 --- /dev/null +++ b/packages/mediapipe-task-genai/example/analysis_options.yaml @@ -0,0 +1,28 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at https://dart.dev/lints. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/packages/mediapipe-task-genai/example/android/.gitignore b/packages/mediapipe-task-genai/example/android/.gitignore new file mode 100644 index 00000000..6f568019 --- /dev/null +++ b/packages/mediapipe-task-genai/example/android/.gitignore @@ -0,0 +1,13 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties +**/*.keystore +**/*.jks diff --git a/packages/mediapipe-task-genai/example/android/app/build.gradle b/packages/mediapipe-task-genai/example/android/app/build.gradle new file mode 100644 index 00000000..2f5eea4b --- /dev/null +++ b/packages/mediapipe-task-genai/example/android/app/build.gradle @@ -0,0 +1,40 @@ +plugins { + id "com.android.application" + id "kotlin-android" + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id "dev.flutter.flutter-gradle-plugin" +} + +android { + namespace = "com.example.example" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "com.example.example" + // You can update the following values to match your application needs. + // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. + minSdk = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig = signingConfigs.debug + } + } +} + +flutter { + source = "../.." +} diff --git a/packages/mediapipe-task-genai/example/android/app/src/debug/AndroidManifest.xml b/packages/mediapipe-task-genai/example/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 00000000..399f6981 --- /dev/null +++ b/packages/mediapipe-task-genai/example/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/packages/mediapipe-task-genai/example/android/app/src/main/AndroidManifest.xml b/packages/mediapipe-task-genai/example/android/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..74a78b93 --- /dev/null +++ b/packages/mediapipe-task-genai/example/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/mediapipe-task-genai/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt b/packages/mediapipe-task-genai/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt new file mode 100644 index 00000000..70f8f08f --- /dev/null +++ b/packages/mediapipe-task-genai/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt @@ -0,0 +1,5 @@ +package com.example.example + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() diff --git a/packages/mediapipe-task-genai/example/android/app/src/main/res/drawable-v21/launch_background.xml b/packages/mediapipe-task-genai/example/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 00000000..f74085f3 --- /dev/null +++ b/packages/mediapipe-task-genai/example/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/packages/mediapipe-task-genai/example/android/app/src/main/res/drawable/launch_background.xml b/packages/mediapipe-task-genai/example/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 00000000..304732f8 --- /dev/null +++ b/packages/mediapipe-task-genai/example/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/packages/mediapipe-task-genai/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/packages/mediapipe-task-genai/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 00000000..db77bb4b Binary files /dev/null and b/packages/mediapipe-task-genai/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/packages/mediapipe-task-genai/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/packages/mediapipe-task-genai/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 00000000..17987b79 Binary files /dev/null and b/packages/mediapipe-task-genai/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/packages/mediapipe-task-genai/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/packages/mediapipe-task-genai/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 00000000..09d43914 Binary files /dev/null and b/packages/mediapipe-task-genai/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/packages/mediapipe-task-genai/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/packages/mediapipe-task-genai/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 00000000..d5f1c8d3 Binary files /dev/null and b/packages/mediapipe-task-genai/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/packages/mediapipe-task-genai/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/packages/mediapipe-task-genai/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 00000000..4d6372ee Binary files /dev/null and b/packages/mediapipe-task-genai/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/packages/mediapipe-task-genai/example/android/app/src/main/res/values-night/styles.xml b/packages/mediapipe-task-genai/example/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 00000000..06952be7 --- /dev/null +++ b/packages/mediapipe-task-genai/example/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/packages/mediapipe-task-genai/example/android/app/src/main/res/values/styles.xml b/packages/mediapipe-task-genai/example/android/app/src/main/res/values/styles.xml new file mode 100644 index 00000000..cb1ef880 --- /dev/null +++ b/packages/mediapipe-task-genai/example/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/packages/mediapipe-task-genai/example/android/app/src/profile/AndroidManifest.xml b/packages/mediapipe-task-genai/example/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 00000000..399f6981 --- /dev/null +++ b/packages/mediapipe-task-genai/example/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/packages/mediapipe-task-genai/example/android/build.gradle b/packages/mediapipe-task-genai/example/android/build.gradle new file mode 100644 index 00000000..d2ffbffa --- /dev/null +++ b/packages/mediapipe-task-genai/example/android/build.gradle @@ -0,0 +1,18 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = "../build" +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean", Delete) { + delete rootProject.buildDir +} diff --git a/packages/mediapipe-task-genai/example/android/gradle.properties b/packages/mediapipe-task-genai/example/android/gradle.properties new file mode 100644 index 00000000..25971708 --- /dev/null +++ b/packages/mediapipe-task-genai/example/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError +android.useAndroidX=true +android.enableJetifier=true diff --git a/packages/mediapipe-task-genai/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/mediapipe-task-genai/example/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..e1ca574e --- /dev/null +++ b/packages/mediapipe-task-genai/example/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-all.zip diff --git a/packages/mediapipe-task-genai/example/android/settings.gradle b/packages/mediapipe-task-genai/example/android/settings.gradle new file mode 100644 index 00000000..536165d3 --- /dev/null +++ b/packages/mediapipe-task-genai/example/android/settings.gradle @@ -0,0 +1,25 @@ +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + }() + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "7.3.0" apply false + id "org.jetbrains.kotlin.android" version "1.7.10" apply false +} + +include ":app" diff --git a/packages/mediapipe-task-genai/example/ios/.gitignore b/packages/mediapipe-task-genai/example/ios/.gitignore new file mode 100644 index 00000000..7a7f9873 --- /dev/null +++ b/packages/mediapipe-task-genai/example/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/packages/mediapipe-task-genai/example/ios/Flutter/AppFrameworkInfo.plist b/packages/mediapipe-task-genai/example/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 00000000..7c569640 --- /dev/null +++ b/packages/mediapipe-task-genai/example/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 12.0 + + diff --git a/packages/mediapipe-task-genai/example/ios/Flutter/Debug.xcconfig b/packages/mediapipe-task-genai/example/ios/Flutter/Debug.xcconfig new file mode 100644 index 00000000..ec97fc6f --- /dev/null +++ b/packages/mediapipe-task-genai/example/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Generated.xcconfig" diff --git a/packages/mediapipe-task-genai/example/ios/Flutter/Release.xcconfig b/packages/mediapipe-task-genai/example/ios/Flutter/Release.xcconfig new file mode 100644 index 00000000..c4855bfe --- /dev/null +++ b/packages/mediapipe-task-genai/example/ios/Flutter/Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "Generated.xcconfig" diff --git a/packages/mediapipe-task-genai/example/ios/Podfile b/packages/mediapipe-task-genai/example/ios/Podfile new file mode 100644 index 00000000..d97f17e2 --- /dev/null +++ b/packages/mediapipe-task-genai/example/ios/Podfile @@ -0,0 +1,44 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '12.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/packages/mediapipe-task-genai/example/ios/Runner.xcodeproj/project.pbxproj b/packages/mediapipe-task-genai/example/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 00000000..4058dea0 --- /dev/null +++ b/packages/mediapipe-task-genai/example/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,730 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 2BA8F9A9879C1C79B684C581 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2C3AE42D1F79790E1C6D5014 /* Pods_RunnerTests.framework */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + DE4B5E1472166A8DE944D300 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F170798CC92D15D955391F3F /* Pods_Runner.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 16A1FFE4AFA29B692C10F23B /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 2C3AE42D1F79790E1C6D5014 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 3FD653649A1229A49EE20B3B /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 53A151154686C269BC9D457B /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 89FE9E7F61D35D0FE20874BD /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + CB26957DAA8AB9062D6647F4 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + D989F8E5323AFDC907C2087E /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + F170798CC92D15D955391F3F /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 89FF63B16EF52C5B49618A60 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 2BA8F9A9879C1C79B684C581 /* Pods_RunnerTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + DE4B5E1472166A8DE944D300 /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 5C2F8C36CA5C38DC06F3047E /* Frameworks */ = { + isa = PBXGroup; + children = ( + F170798CC92D15D955391F3F /* Pods_Runner.framework */, + 2C3AE42D1F79790E1C6D5014 /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, + D1F35D73BB0B8DBBFCD76104 /* Pods */, + 5C2F8C36CA5C38DC06F3047E /* Frameworks */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; + D1F35D73BB0B8DBBFCD76104 /* Pods */ = { + isa = PBXGroup; + children = ( + 3FD653649A1229A49EE20B3B /* Pods-Runner.debug.xcconfig */, + 89FE9E7F61D35D0FE20874BD /* Pods-Runner.release.xcconfig */, + 16A1FFE4AFA29B692C10F23B /* Pods-Runner.profile.xcconfig */, + CB26957DAA8AB9062D6647F4 /* Pods-RunnerTests.debug.xcconfig */, + 53A151154686C269BC9D457B /* Pods-RunnerTests.release.xcconfig */, + D989F8E5323AFDC907C2087E /* Pods-RunnerTests.profile.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 7A8089E692296077F09EC119 /* [CP] Check Pods Manifest.lock */, + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + 89FF63B16EF52C5B49618A60 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + F6AB1831DF41B7649B4B44B7 /* [CP] Check Pods Manifest.lock */, + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + CE6CBA82D8D8763D1B3ACF2E /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 7A8089E692296077F09EC119 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; + CE6CBA82D8D8763D1B3ACF2E /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + F6AB1831DF41B7649B4B44B7 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 35JKZ342M3; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.mediapipe.genai; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = CB26957DAA8AB9062D6647F4 /* Pods-RunnerTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 53A151154686C269BC9D457B /* Pods-RunnerTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D989F8E5323AFDC907C2087E /* Pods-RunnerTests.profile.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 35JKZ342M3; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.mediapipe.genai; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 35JKZ342M3; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.mediapipe.genai; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/packages/mediapipe-task-genai/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/mediapipe-task-genai/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..919434a6 --- /dev/null +++ b/packages/mediapipe-task-genai/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/packages/mediapipe-task-genai/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/mediapipe-task-genai/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/packages/mediapipe-task-genai/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/mediapipe-task-genai/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/mediapipe-task-genai/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 00000000..f9b0d7c5 --- /dev/null +++ b/packages/mediapipe-task-genai/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/packages/mediapipe-task-genai/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/mediapipe-task-genai/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 00000000..8e3ca5df --- /dev/null +++ b/packages/mediapipe-task-genai/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/mediapipe-task-genai/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/packages/mediapipe-task-genai/example/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..21a3cc14 --- /dev/null +++ b/packages/mediapipe-task-genai/example/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/packages/mediapipe-task-genai/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/mediapipe-task-genai/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/packages/mediapipe-task-genai/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/mediapipe-task-genai/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/mediapipe-task-genai/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 00000000..f9b0d7c5 --- /dev/null +++ b/packages/mediapipe-task-genai/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/packages/mediapipe-task-genai/example/ios/Runner/AppDelegate.swift b/packages/mediapipe-task-genai/example/ios/Runner/AppDelegate.swift new file mode 100644 index 00000000..62666446 --- /dev/null +++ b/packages/mediapipe-task-genai/example/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import Flutter +import UIKit + +@main +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/packages/mediapipe-task-genai/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/mediapipe-task-genai/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..d36b1fab --- /dev/null +++ b/packages/mediapipe-task-genai/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/packages/mediapipe-task-genai/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/packages/mediapipe-task-genai/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 00000000..dc9ada47 Binary files /dev/null and b/packages/mediapipe-task-genai/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/packages/mediapipe-task-genai/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/packages/mediapipe-task-genai/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 00000000..7353c41e Binary files /dev/null and b/packages/mediapipe-task-genai/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/packages/mediapipe-task-genai/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/packages/mediapipe-task-genai/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 00000000..797d452e Binary files /dev/null and b/packages/mediapipe-task-genai/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/packages/mediapipe-task-genai/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/packages/mediapipe-task-genai/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 00000000..6ed2d933 Binary files /dev/null and b/packages/mediapipe-task-genai/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/packages/mediapipe-task-genai/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/packages/mediapipe-task-genai/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 00000000..4cd7b009 Binary files /dev/null and b/packages/mediapipe-task-genai/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/packages/mediapipe-task-genai/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/packages/mediapipe-task-genai/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 00000000..fe730945 Binary files /dev/null and b/packages/mediapipe-task-genai/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/packages/mediapipe-task-genai/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/packages/mediapipe-task-genai/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 00000000..321773cd Binary files /dev/null and b/packages/mediapipe-task-genai/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/packages/mediapipe-task-genai/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/packages/mediapipe-task-genai/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 00000000..797d452e Binary files /dev/null and b/packages/mediapipe-task-genai/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/packages/mediapipe-task-genai/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/packages/mediapipe-task-genai/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 00000000..502f463a Binary files /dev/null and b/packages/mediapipe-task-genai/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/packages/mediapipe-task-genai/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/packages/mediapipe-task-genai/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 00000000..0ec30343 Binary files /dev/null and b/packages/mediapipe-task-genai/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/packages/mediapipe-task-genai/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/packages/mediapipe-task-genai/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 00000000..0ec30343 Binary files /dev/null and b/packages/mediapipe-task-genai/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/packages/mediapipe-task-genai/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/packages/mediapipe-task-genai/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 00000000..e9f5fea2 Binary files /dev/null and b/packages/mediapipe-task-genai/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/packages/mediapipe-task-genai/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/packages/mediapipe-task-genai/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 00000000..84ac32ae Binary files /dev/null and b/packages/mediapipe-task-genai/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/packages/mediapipe-task-genai/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/packages/mediapipe-task-genai/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 00000000..8953cba0 Binary files /dev/null and b/packages/mediapipe-task-genai/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/packages/mediapipe-task-genai/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/packages/mediapipe-task-genai/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 00000000..0467bf12 Binary files /dev/null and b/packages/mediapipe-task-genai/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/packages/mediapipe-task-genai/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/packages/mediapipe-task-genai/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 00000000..0bedcf2f --- /dev/null +++ b/packages/mediapipe-task-genai/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/packages/mediapipe-task-genai/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/packages/mediapipe-task-genai/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 00000000..9da19eac Binary files /dev/null and b/packages/mediapipe-task-genai/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/packages/mediapipe-task-genai/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/packages/mediapipe-task-genai/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 00000000..9da19eac Binary files /dev/null and b/packages/mediapipe-task-genai/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/packages/mediapipe-task-genai/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/packages/mediapipe-task-genai/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 00000000..9da19eac Binary files /dev/null and b/packages/mediapipe-task-genai/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/packages/mediapipe-task-genai/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/packages/mediapipe-task-genai/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 00000000..89c2725b --- /dev/null +++ b/packages/mediapipe-task-genai/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +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. \ No newline at end of file diff --git a/packages/mediapipe-task-genai/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/packages/mediapipe-task-genai/example/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 00000000..f2e259c7 --- /dev/null +++ b/packages/mediapipe-task-genai/example/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/mediapipe-task-genai/example/ios/Runner/Base.lproj/Main.storyboard b/packages/mediapipe-task-genai/example/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 00000000..f3c28516 --- /dev/null +++ b/packages/mediapipe-task-genai/example/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/mediapipe-task-genai/example/ios/Runner/Info.plist b/packages/mediapipe-task-genai/example/ios/Runner/Info.plist new file mode 100644 index 00000000..f15383a8 --- /dev/null +++ b/packages/mediapipe-task-genai/example/ios/Runner/Info.plist @@ -0,0 +1,49 @@ + + + + + CADisableMinimumFrameDurationOnPhone + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Example + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + example + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UIApplicationSupportsIndirectInputEvents + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/packages/mediapipe-task-genai/example/ios/Runner/Runner-Bridging-Header.h b/packages/mediapipe-task-genai/example/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 00000000..308a2a56 --- /dev/null +++ b/packages/mediapipe-task-genai/example/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/packages/mediapipe-task-genai/example/ios/RunnerTests/RunnerTests.swift b/packages/mediapipe-task-genai/example/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 00000000..86a7c3b1 --- /dev/null +++ b/packages/mediapipe-task-genai/example/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/packages/mediapipe-task-genai/example/lib/bloc.dart b/packages/mediapipe-task-genai/example/lib/bloc.dart new file mode 100644 index 00000000..d10502c0 --- /dev/null +++ b/packages/mediapipe-task-genai/example/lib/bloc.dart @@ -0,0 +1,510 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:math'; +import 'package:example/model_location_provider.dart'; +import 'package:example/models/models.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:logging/logging.dart'; +import 'package:mediapipe_genai/mediapipe_genai.dart'; +import 'package:path_provider/path_provider.dart' as path_provider; + +part 'bloc.freezed.dart'; + +typedef Emit = Emitter; +final _log = Logger('TranscriptBloc'); + +class TranscriptBloc extends Bloc { + TranscriptBloc({required this.engineBuilder}) + : modelProvider = ModelLocationProvider.fromEnvironment(), + super(TranscriptState.initial()) { + on( + (event, emit) { + event.map( + addMessage: (e) => _addMessage(e, emit), + extendMessage: (e) => _extendMessage(e, emit), + checkForModel: (e) => _checkForModel(e, emit), + completeResponse: (e) => _completeResponse(e, emit), + downloadModel: (e) => _downloadModel(e, emit), + deleteModel: (e) => _deleteModel(e, emit), + setPercentDownloaded: (e) => _setPercentDownloaded(e, emit), + updateTemperature: (e) => _updateTemperature(e, emit), + updateTopK: (e) => _updateTopK(e, emit), + updateMaxTokens: (e) => _updateMaxTokens(e, emit), + initEngine: (e) => _initEngine(e, emit), + initializeModelInfo: (e) => _initializeModelInfo(e, emit), + ); + }, + ); + final cacheDirFuture = path_provider.getApplicationCacheDirectory(); + modelProvider.ready.then((_) async { + cacheDir = (await cacheDirFuture).absolute.path; + add(const InitializeModelInfo()); + }); + } + + late final String cacheDir; + + /// Utility which knows how to download and store the model file. + ModelLocationProvider modelProvider; + + /// Constructor for the inference engine. + final LlmInferenceEngine Function(LlmInferenceOptions) engineBuilder; + + Future _initializeModelInfo( + InitializeModelInfo event, + Emit emit, + ) async { + // This is added post-construction because the ModelProvider must first + // be ready, then we can sort out what models we have available, what can + // be downloaded, and what is completely inaccessible given the current app + // configuration. + final modelInfoMap = {}; + for (final model in LlmModel.values) { + modelInfoMap[model] = _getInfo(model); + } + emit(state.copyWith(modelInfoMap: modelInfoMap, modelsReady: true)); + } + + Future _checkForModel(CheckForModel event, Emit emit) async { + final existingPath = modelProvider.pathFor(event.model); + final modelInfo = _getInfo(event.model); + _emitNewModelInfo(event.model, modelInfo, emit); + if (existingPath != null) { + add(InitEngine(event.model)); + } + } + + ModelInfo _getInfo(LlmModel model) { + final existingPath = modelProvider.pathFor(model); + return existingPath != null + ? ModelInfo( + path: existingPath, + downloadedBytes: modelProvider.binarySize(existingPath), + downloadPercent: null, + remoteLocation: modelProvider.urlFor(model), + ) + : ModelInfo( + // The model may be downloading, so preserve whatever value that has + downloadPercent: state.modelInfoMap[model]!.downloadPercent, + remoteLocation: modelProvider.urlFor(model), + ); + } + + void _setPercentDownloaded(SetPercentDownloaded event, Emit emit) async { + _emitNewModelInfo( + event.model, + state.modelInfoMap[event.model]!.copyWith( + downloadPercent: event.percentDownloaded, + downloadedBytes: null, + path: null, + ), + emit, + ); + } + + /// Copies the `state.modelInfo` map, slots the provided [info] into the + /// correct place, and emits a copy of [state] with that new map. + /// However, if you have already made other modifications to a state object + /// (via copying), consider passing that copied value to the [overrideState] + /// parameter. + void _emitNewModelInfo( + LlmModel model, + ModelInfo info, + Emit emit, [ + TranscriptState? overrideState, + ]) { + // Copy the map of `ModelInfo` objects + final modelInfoMap = Map.from(state.modelInfoMap); + + // Update the map copy + modelInfoMap[model] = info; + + // Emit a copy of `state` with the new ModelInfo map + emit((overrideState ?? state).copyWith(modelInfoMap: modelInfoMap)); + } + + Future _updateTemperature(UpdateTemperature event, Emit emit) async { + state.engine?.dispose(); + emit(state.copyWith(engine: null, temperature: event.value)); + } + + Future _updateTopK(UpdateTopK event, Emit emit) async { + state.engine?.dispose(); + emit(state.copyWith(engine: null, topK: event.value)); + } + + Future _updateMaxTokens(UpdateMaxTokens event, Emit emit) async { + state.engine?.dispose(); + emit(state.copyWith(engine: null, maxTokens: event.value)); + } + + Future _initEngine(InitEngine event, Emit emit) async { + state.engine?.dispose(); + final modelPath = modelProvider.pathFor(event.model); + if (modelPath == null) { + throw Exception('Called _initEngine before model was downloaded'); + } + final options = switch (event.model.hardware) { + Hardware.gpu => LlmInferenceOptions.gpu( + modelPath: modelPath, + maxTokens: state.maxTokens, + temperature: state.temperature, + topK: state.topK, + sequenceBatchSize: state.sequenceBatchSize, + ), + Hardware.cpu => LlmInferenceOptions.cpu( + modelPath: modelPath, + cacheDir: cacheDir, + maxTokens: state.maxTokens, + temperature: state.temperature, + topK: state.topK, + ), + }; + _log.fine('Initializing inference engine with $options'); + final engine = engineBuilder(options); + emit(state.copyWith(engine: engine)); + + // If we queued a message for the Llm, process it! + if (_queuedMessageForEngine != null) { + _sendMessageToLlm(_queuedMessageForEngine!, emit); + _queuedMessageForEngine = null; + } + } + + Future _downloadModel(DownloadModel event, Emit emit) async { + assert( + await modelProvider.downloadExistsForModel(event.model) == false, + 'The UI should not ask to download an existing model. Model exists for ' + '${event.model} at ${modelProvider.pathFor(event.model)}.', + ); + + // Hold onto this variable in case the user switches the active model while + // we are downloading. + final modelToDownload = event.model; + + late final Future modelLocationFuture; + late final Stream? downloadStream; + try { + // Request the model download and, once a string value is returned, + // mark that the model is available and that the download is complete. + (modelLocationFuture, downloadStream) = + await modelProvider.getModelLocation(modelToDownload); + } on Exception catch (e, s) { + _log.severe('Error: $e'); + _log.severe('Stack trace: $s'); + emit( + state.copyWith( + error: 'Unable to download ${modelToDownload.displayName}', + ), + ); + await Future.delayed(const Duration(milliseconds: 100)); + emit(state.copyWith(error: null)); + return; + } + + if (downloadStream != null) { + await for (final percent in downloadStream) { + add(SetPercentDownloaded(modelToDownload, percent)); + } + } + + await modelLocationFuture; + add(CheckForModel(modelToDownload)); + } + + Future _deleteModel(DeleteModel event, Emit emit) async { + final modelToDelete = event.model; + emit(state.copyWith(engine: null)); + await modelProvider.delete(modelToDelete); + add(CheckForModel(modelToDelete)); + } + + void _completeResponse(CompleteResponse e, emit) => + emit(state.copyWith(isLlmTyping: false).completeMessage(e.model)); + + Future _addMessage(AddMessage event, Emit emit) async { + // Value equality will detect that this state is "new" + emit(state.addMessage(event.message, event.model)); + if (state.engine == null) { + await _queueMessageForLlm(event, emit); + } else { + await _sendMessageToLlm(event, emit); + } + } + + Future _queueMessageForLlm(AddMessage event, Emit emit) async { + _queuedMessageForEngine = event; + add(InitEngine(event.model)); + } + + AddMessage? _queuedMessageForEngine; + + Future _sendMessageToLlm( + AddMessage event, + Emit emit, + ) async { + final formattedChatHistory = + _formatChatHistoryForLlm(state.transcript[event.model]!); + final responseStream = state.engine!.generateResponse(formattedChatHistory); + + // Add a blank response for the LLM into which we can write its answer. + // Create a synthetic event just to pass to this helper method, but don't + // route it through the `add` method. + emit( + state + .copyWith(isLlmTyping: true) + .addMessage(ChatMessage.llm(''), event.model), + ); + + final messageIndex = state.transcript[event.model]!.length - 1; + bool first = true; + await for (final String chunk in responseStream) { + add( + ExtendMessage( + chunk: chunk, + model: event.model, + index: messageIndex, + first: first, + last: false, + ), + ); + first = false; + } + add( + ExtendMessage( + chunk: '', + model: event.model, + index: messageIndex, + first: first, + last: true, + ), + ); + add(CompleteResponse(event.model)); + } + + void _extendMessage(ExtendMessage event, Emit emit) => emit( + state.extendMessage( + event.chunk, + model: event.model, + index: event.index, + first: event.first, + last: event.last, + ), + ); + + static const _begin = ''; + static const _end = ''; + + String _formatChatHistoryForLlm(List transcript) { + // The current message is already in the transcript, so the length will be 1 + // on the first message. That message can go straight to the LLM without any + // special decoration. + if (transcript.length == 1) return transcript.last.body; + + final formattedHistory = transcript + .map( + (message) => '$_begin\n' + '${message.origin.transcriptName}: ${message.body}\n' + '$_end\n', + ) + .join('\n'); + + return 'You are "${MessageOrigin.llm.transcriptName}" in the ensuing ' + 'conversation. Messages tagged as having been written by ' + '"${MessageOrigin.llm.transcriptName}" are things you previously said ' + 'in this conversation. Messages tagged as having been written by ' + '"${MessageOrigin.user.transcriptName}" came from the other party with ' + 'which you are conversing.\n\n' + '$formattedHistory\n\n' + 'Your response should not address the other party as ' + '"${MessageOrigin.user.transcriptName}". \n\n' + 'What is your response? Give a single answer.'; + } +} + +@Freezed(makeCollectionsUnmodifiable: false) +class TranscriptState with _$TranscriptState { + TranscriptState._(); + factory TranscriptState({ + /// Log of messages for the [selectedModel]. Other models may have other + /// message logs found on the [TranscriptBloc]. + required Map> transcript, + + /// True only after the [ModelLocationProvider] has sorted out the initial + /// state. + @Default(false) bool modelsReady, + + /// Engine for the current [selectedModel]. + LlmInferenceEngine? engine, + + /// True if the model is in the process of composing its response. + @Default(false) isLlmTyping, + + /// Meta download information about each [LlmModel]. + required Map modelInfoMap, + + /// Randomness during token sampling selection. + @Default(0.8) double temperature, + + /// Top K number of tokens to be sampled from for each decoding step. + @Default(40) int topK, + + /// The LLM's maximum context window. + @Default(1024) int maxTokens, + + /// Randomness seed. + required int randomSeed, + + /// Error message to show in a toast. + String? error, + }) = _TranscriptState; + + factory TranscriptState.initial([int? seed]) { + final modelInfoMap = {}; + final transcript = >{}; + for (final model in LlmModel.values) { + modelInfoMap[model] = const ModelInfo(); + transcript[model] = []; + } + return TranscriptState( + transcript: transcript, + randomSeed: seed ?? Random().nextInt(1 << 32), + modelInfoMap: modelInfoMap, + ); + } + + int get sequenceBatchSize => 20; + + Map> _copyTranscript() { + final newTranscript = >{}; + for (final key in transcript.keys) { + newTranscript[key] = List.from(transcript[key]!); + } + return newTranscript; + } + + TranscriptState addMessage(ChatMessage message, LlmModel model) { + final newTranscript = _copyTranscript(); + newTranscript[model]!.add(message); + return copyWith(transcript: newTranscript); + } + + TranscriptState extendMessage( + String chunk, { + required int index, + required LlmModel model, + required bool first, + required bool last, + }) { + final newTranscript = _copyTranscript(); + assert(() { + if (newTranscript[model]!.length < index + 1) { + throw Exception('Tried to add to index $index, but length is ' + 'only ${newTranscript[model]!.length} for $model'); + } + return true; + }()); + final oldMessage = newTranscript[model]![index]; + final newMessage = oldMessage.copyWith( + body: '${oldMessage.body}$chunk'.sanitize(first, last), + ); + newTranscript[model]![index] = newMessage; + return copyWith(transcript: newTranscript); + } + + TranscriptState completeMessage(LlmModel model) { + final newTranscript = _copyTranscript(); + newTranscript[model]!.last = + newTranscript[model]!.last.copyWith(isComplete: true); + return copyWith(transcript: newTranscript); + } +} + +@Freezed() +class TranscriptEvent with _$TranscriptEvent { + const factory TranscriptEvent.checkForModel(LlmModel model) = CheckForModel; + const factory TranscriptEvent.downloadModel(LlmModel model) = DownloadModel; + const factory TranscriptEvent.setPercentDownloaded( + LlmModel model, + int percentDownloaded, + ) = SetPercentDownloaded; + const factory TranscriptEvent.deleteModel(LlmModel model) = DeleteModel; + const factory TranscriptEvent.initEngine(LlmModel model) = InitEngine; + const factory TranscriptEvent.initializeModelInfo() = InitializeModelInfo; + const factory TranscriptEvent.updateTemperature(double value) = + UpdateTemperature; + const factory TranscriptEvent.updateTopK(int value) = UpdateTopK; + const factory TranscriptEvent.updateMaxTokens(int value) = UpdateMaxTokens; + const factory TranscriptEvent.addMessage( + ChatMessage message, LlmModel model) = AddMessage; + const factory TranscriptEvent.extendMessage({ + required String chunk, + required int index, + required LlmModel model, + required bool first, + required bool last, + }) = ExtendMessage; + const factory TranscriptEvent.completeResponse(LlmModel model) = + CompleteResponse; +} + +extension on String { + String sanitize(bool first, bool last) { + final firstOrLast = ['\n', ' ']; + final invalidSubstrings = [ + ':', + ';', + TranscriptBloc._begin, + TranscriptBloc._end, + MessageOrigin.llm.transcriptName, + MessageOrigin.user.transcriptName, + ]; + String val = sanitizeBeginning( + invalidSubstrings..addAll(first ? firstOrLast : []), + ); + return val.sanitizeEnd( + invalidSubstrings..addAll(last ? firstOrLast : []), + ); + } + + String sanitizeBeginning(List invalidSubstrings) { + String val = this; + while (true) { + String? matchingSubstring; + for (String invalidSubstring in invalidSubstrings) { + if (val.startsWith(invalidSubstring)) { + matchingSubstring = invalidSubstring; + break; + } + } + if (matchingSubstring != null) { + val = val.substring(matchingSubstring.length); + } else { + break; + } + } + return val; + } + + String sanitizeEnd(List invalidSubstrings) { + String val = this; + while (true) { + String? matchingSubstring; + for (String invalidSubstring in invalidSubstrings) { + if (val.endsWith(invalidSubstring)) { + matchingSubstring = invalidSubstring; + break; + } + } + if (matchingSubstring != null) { + val = val.substring(0, val.length - matchingSubstring.length); + } else { + break; + } + } + return val; + } +} diff --git a/packages/mediapipe-task-genai/example/lib/bloc.freezed.dart b/packages/mediapipe-task-genai/example/lib/bloc.freezed.dart new file mode 100644 index 00000000..a8fde541 --- /dev/null +++ b/packages/mediapipe-task-genai/example/lib/bloc.freezed.dart @@ -0,0 +1,3012 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'bloc.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +/// @nodoc +mixin _$TranscriptState { + /// Log of messages for the [selectedModel]. Other models may have other + /// message logs found on the [TranscriptBloc]. + Map> get transcript => + throw _privateConstructorUsedError; + + /// True only after the [ModelLocationProvider] has sorted out the initial + /// state. + bool get modelsReady => throw _privateConstructorUsedError; + + /// Engine for the current [selectedModel]. + LlmInferenceEngine? get engine => throw _privateConstructorUsedError; + + /// True if the model is in the process of composing its response. + dynamic get isLlmTyping => throw _privateConstructorUsedError; + + /// Meta download information about each [LlmModel]. + Map get modelInfoMap => + throw _privateConstructorUsedError; + + /// Randomness during token sampling selection. + double get temperature => throw _privateConstructorUsedError; + + /// Top K number of tokens to be sampled from for each decoding step. + int get topK => throw _privateConstructorUsedError; + + /// The LLM's maximum context window. + int get maxTokens => throw _privateConstructorUsedError; + + /// Randomness seed. + int get randomSeed => throw _privateConstructorUsedError; + + /// Error message to show in a toast. + String? get error => throw _privateConstructorUsedError; + + @JsonKey(ignore: true) + $TranscriptStateCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $TranscriptStateCopyWith<$Res> { + factory $TranscriptStateCopyWith( + TranscriptState value, $Res Function(TranscriptState) then) = + _$TranscriptStateCopyWithImpl<$Res, TranscriptState>; + @useResult + $Res call( + {Map> transcript, + bool modelsReady, + LlmInferenceEngine? engine, + dynamic isLlmTyping, + Map modelInfoMap, + double temperature, + int topK, + int maxTokens, + int randomSeed, + String? error}); +} + +/// @nodoc +class _$TranscriptStateCopyWithImpl<$Res, $Val extends TranscriptState> + implements $TranscriptStateCopyWith<$Res> { + _$TranscriptStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? transcript = null, + Object? modelsReady = null, + Object? engine = freezed, + Object? isLlmTyping = freezed, + Object? modelInfoMap = null, + Object? temperature = null, + Object? topK = null, + Object? maxTokens = null, + Object? randomSeed = null, + Object? error = freezed, + }) { + return _then(_value.copyWith( + transcript: null == transcript + ? _value.transcript + : transcript // ignore: cast_nullable_to_non_nullable + as Map>, + modelsReady: null == modelsReady + ? _value.modelsReady + : modelsReady // ignore: cast_nullable_to_non_nullable + as bool, + engine: freezed == engine + ? _value.engine + : engine // ignore: cast_nullable_to_non_nullable + as LlmInferenceEngine?, + isLlmTyping: freezed == isLlmTyping + ? _value.isLlmTyping + : isLlmTyping // ignore: cast_nullable_to_non_nullable + as dynamic, + modelInfoMap: null == modelInfoMap + ? _value.modelInfoMap + : modelInfoMap // ignore: cast_nullable_to_non_nullable + as Map, + temperature: null == temperature + ? _value.temperature + : temperature // ignore: cast_nullable_to_non_nullable + as double, + topK: null == topK + ? _value.topK + : topK // ignore: cast_nullable_to_non_nullable + as int, + maxTokens: null == maxTokens + ? _value.maxTokens + : maxTokens // ignore: cast_nullable_to_non_nullable + as int, + randomSeed: null == randomSeed + ? _value.randomSeed + : randomSeed // ignore: cast_nullable_to_non_nullable + as int, + error: freezed == error + ? _value.error + : error // ignore: cast_nullable_to_non_nullable + as String?, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$TranscriptStateImplCopyWith<$Res> + implements $TranscriptStateCopyWith<$Res> { + factory _$$TranscriptStateImplCopyWith(_$TranscriptStateImpl value, + $Res Function(_$TranscriptStateImpl) then) = + __$$TranscriptStateImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {Map> transcript, + bool modelsReady, + LlmInferenceEngine? engine, + dynamic isLlmTyping, + Map modelInfoMap, + double temperature, + int topK, + int maxTokens, + int randomSeed, + String? error}); +} + +/// @nodoc +class __$$TranscriptStateImplCopyWithImpl<$Res> + extends _$TranscriptStateCopyWithImpl<$Res, _$TranscriptStateImpl> + implements _$$TranscriptStateImplCopyWith<$Res> { + __$$TranscriptStateImplCopyWithImpl( + _$TranscriptStateImpl _value, $Res Function(_$TranscriptStateImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? transcript = null, + Object? modelsReady = null, + Object? engine = freezed, + Object? isLlmTyping = freezed, + Object? modelInfoMap = null, + Object? temperature = null, + Object? topK = null, + Object? maxTokens = null, + Object? randomSeed = null, + Object? error = freezed, + }) { + return _then(_$TranscriptStateImpl( + transcript: null == transcript + ? _value.transcript + : transcript // ignore: cast_nullable_to_non_nullable + as Map>, + modelsReady: null == modelsReady + ? _value.modelsReady + : modelsReady // ignore: cast_nullable_to_non_nullable + as bool, + engine: freezed == engine + ? _value.engine + : engine // ignore: cast_nullable_to_non_nullable + as LlmInferenceEngine?, + isLlmTyping: freezed == isLlmTyping ? _value.isLlmTyping! : isLlmTyping, + modelInfoMap: null == modelInfoMap + ? _value.modelInfoMap + : modelInfoMap // ignore: cast_nullable_to_non_nullable + as Map, + temperature: null == temperature + ? _value.temperature + : temperature // ignore: cast_nullable_to_non_nullable + as double, + topK: null == topK + ? _value.topK + : topK // ignore: cast_nullable_to_non_nullable + as int, + maxTokens: null == maxTokens + ? _value.maxTokens + : maxTokens // ignore: cast_nullable_to_non_nullable + as int, + randomSeed: null == randomSeed + ? _value.randomSeed + : randomSeed // ignore: cast_nullable_to_non_nullable + as int, + error: freezed == error + ? _value.error + : error // ignore: cast_nullable_to_non_nullable + as String?, + )); + } +} + +/// @nodoc + +class _$TranscriptStateImpl extends _TranscriptState { + _$TranscriptStateImpl( + {required this.transcript, + this.modelsReady = false, + this.engine, + this.isLlmTyping = false, + required this.modelInfoMap, + this.temperature = 0.8, + this.topK = 40, + this.maxTokens = 1024, + required this.randomSeed, + this.error}) + : super._(); + + /// Log of messages for the [selectedModel]. Other models may have other + /// message logs found on the [TranscriptBloc]. + @override + final Map> transcript; + + /// True only after the [ModelLocationProvider] has sorted out the initial + /// state. + @override + @JsonKey() + final bool modelsReady; + + /// Engine for the current [selectedModel]. + @override + final LlmInferenceEngine? engine; + + /// True if the model is in the process of composing its response. + @override + @JsonKey() + final dynamic isLlmTyping; + + /// Meta download information about each [LlmModel]. + @override + final Map modelInfoMap; + + /// Randomness during token sampling selection. + @override + @JsonKey() + final double temperature; + + /// Top K number of tokens to be sampled from for each decoding step. + @override + @JsonKey() + final int topK; + + /// The LLM's maximum context window. + @override + @JsonKey() + final int maxTokens; + + /// Randomness seed. + @override + final int randomSeed; + + /// Error message to show in a toast. + @override + final String? error; + + @override + String toString() { + return 'TranscriptState(transcript: $transcript, modelsReady: $modelsReady, engine: $engine, isLlmTyping: $isLlmTyping, modelInfoMap: $modelInfoMap, temperature: $temperature, topK: $topK, maxTokens: $maxTokens, randomSeed: $randomSeed, error: $error)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$TranscriptStateImpl && + const DeepCollectionEquality() + .equals(other.transcript, transcript) && + (identical(other.modelsReady, modelsReady) || + other.modelsReady == modelsReady) && + (identical(other.engine, engine) || other.engine == engine) && + const DeepCollectionEquality() + .equals(other.isLlmTyping, isLlmTyping) && + const DeepCollectionEquality() + .equals(other.modelInfoMap, modelInfoMap) && + (identical(other.temperature, temperature) || + other.temperature == temperature) && + (identical(other.topK, topK) || other.topK == topK) && + (identical(other.maxTokens, maxTokens) || + other.maxTokens == maxTokens) && + (identical(other.randomSeed, randomSeed) || + other.randomSeed == randomSeed) && + (identical(other.error, error) || other.error == error)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + const DeepCollectionEquality().hash(transcript), + modelsReady, + engine, + const DeepCollectionEquality().hash(isLlmTyping), + const DeepCollectionEquality().hash(modelInfoMap), + temperature, + topK, + maxTokens, + randomSeed, + error); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$TranscriptStateImplCopyWith<_$TranscriptStateImpl> get copyWith => + __$$TranscriptStateImplCopyWithImpl<_$TranscriptStateImpl>( + this, _$identity); +} + +abstract class _TranscriptState extends TranscriptState { + factory _TranscriptState( + {required final Map> transcript, + final bool modelsReady, + final LlmInferenceEngine? engine, + final dynamic isLlmTyping, + required final Map modelInfoMap, + final double temperature, + final int topK, + final int maxTokens, + required final int randomSeed, + final String? error}) = _$TranscriptStateImpl; + _TranscriptState._() : super._(); + + @override + + /// Log of messages for the [selectedModel]. Other models may have other + /// message logs found on the [TranscriptBloc]. + Map> get transcript; + @override + + /// True only after the [ModelLocationProvider] has sorted out the initial + /// state. + bool get modelsReady; + @override + + /// Engine for the current [selectedModel]. + LlmInferenceEngine? get engine; + @override + + /// True if the model is in the process of composing its response. + dynamic get isLlmTyping; + @override + + /// Meta download information about each [LlmModel]. + Map get modelInfoMap; + @override + + /// Randomness during token sampling selection. + double get temperature; + @override + + /// Top K number of tokens to be sampled from for each decoding step. + int get topK; + @override + + /// The LLM's maximum context window. + int get maxTokens; + @override + + /// Randomness seed. + int get randomSeed; + @override + + /// Error message to show in a toast. + String? get error; + @override + @JsonKey(ignore: true) + _$$TranscriptStateImplCopyWith<_$TranscriptStateImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +mixin _$TranscriptEvent { + @optionalTypeArgs + TResult when({ + required TResult Function(LlmModel model) checkForModel, + required TResult Function(LlmModel model) downloadModel, + required TResult Function(LlmModel model, int percentDownloaded) + setPercentDownloaded, + required TResult Function(LlmModel model) deleteModel, + required TResult Function(LlmModel model) initEngine, + required TResult Function() initializeModelInfo, + required TResult Function(double value) updateTemperature, + required TResult Function(int value) updateTopK, + required TResult Function(int value) updateMaxTokens, + required TResult Function(ChatMessage message, LlmModel model) addMessage, + required TResult Function( + String chunk, int index, LlmModel model, bool first, bool last) + extendMessage, + required TResult Function(LlmModel model) completeResponse, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(LlmModel model)? checkForModel, + TResult? Function(LlmModel model)? downloadModel, + TResult? Function(LlmModel model, int percentDownloaded)? + setPercentDownloaded, + TResult? Function(LlmModel model)? deleteModel, + TResult? Function(LlmModel model)? initEngine, + TResult? Function()? initializeModelInfo, + TResult? Function(double value)? updateTemperature, + TResult? Function(int value)? updateTopK, + TResult? Function(int value)? updateMaxTokens, + TResult? Function(ChatMessage message, LlmModel model)? addMessage, + TResult? Function( + String chunk, int index, LlmModel model, bool first, bool last)? + extendMessage, + TResult? Function(LlmModel model)? completeResponse, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(LlmModel model)? checkForModel, + TResult Function(LlmModel model)? downloadModel, + TResult Function(LlmModel model, int percentDownloaded)? + setPercentDownloaded, + TResult Function(LlmModel model)? deleteModel, + TResult Function(LlmModel model)? initEngine, + TResult Function()? initializeModelInfo, + TResult Function(double value)? updateTemperature, + TResult Function(int value)? updateTopK, + TResult Function(int value)? updateMaxTokens, + TResult Function(ChatMessage message, LlmModel model)? addMessage, + TResult Function( + String chunk, int index, LlmModel model, bool first, bool last)? + extendMessage, + TResult Function(LlmModel model)? completeResponse, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(CheckForModel value) checkForModel, + required TResult Function(DownloadModel value) downloadModel, + required TResult Function(SetPercentDownloaded value) setPercentDownloaded, + required TResult Function(DeleteModel value) deleteModel, + required TResult Function(InitEngine value) initEngine, + required TResult Function(InitializeModelInfo value) initializeModelInfo, + required TResult Function(UpdateTemperature value) updateTemperature, + required TResult Function(UpdateTopK value) updateTopK, + required TResult Function(UpdateMaxTokens value) updateMaxTokens, + required TResult Function(AddMessage value) addMessage, + required TResult Function(ExtendMessage value) extendMessage, + required TResult Function(CompleteResponse value) completeResponse, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(CheckForModel value)? checkForModel, + TResult? Function(DownloadModel value)? downloadModel, + TResult? Function(SetPercentDownloaded value)? setPercentDownloaded, + TResult? Function(DeleteModel value)? deleteModel, + TResult? Function(InitEngine value)? initEngine, + TResult? Function(InitializeModelInfo value)? initializeModelInfo, + TResult? Function(UpdateTemperature value)? updateTemperature, + TResult? Function(UpdateTopK value)? updateTopK, + TResult? Function(UpdateMaxTokens value)? updateMaxTokens, + TResult? Function(AddMessage value)? addMessage, + TResult? Function(ExtendMessage value)? extendMessage, + TResult? Function(CompleteResponse value)? completeResponse, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(CheckForModel value)? checkForModel, + TResult Function(DownloadModel value)? downloadModel, + TResult Function(SetPercentDownloaded value)? setPercentDownloaded, + TResult Function(DeleteModel value)? deleteModel, + TResult Function(InitEngine value)? initEngine, + TResult Function(InitializeModelInfo value)? initializeModelInfo, + TResult Function(UpdateTemperature value)? updateTemperature, + TResult Function(UpdateTopK value)? updateTopK, + TResult Function(UpdateMaxTokens value)? updateMaxTokens, + TResult Function(AddMessage value)? addMessage, + TResult Function(ExtendMessage value)? extendMessage, + TResult Function(CompleteResponse value)? completeResponse, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $TranscriptEventCopyWith<$Res> { + factory $TranscriptEventCopyWith( + TranscriptEvent value, $Res Function(TranscriptEvent) then) = + _$TranscriptEventCopyWithImpl<$Res, TranscriptEvent>; +} + +/// @nodoc +class _$TranscriptEventCopyWithImpl<$Res, $Val extends TranscriptEvent> + implements $TranscriptEventCopyWith<$Res> { + _$TranscriptEventCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; +} + +/// @nodoc +abstract class _$$CheckForModelImplCopyWith<$Res> { + factory _$$CheckForModelImplCopyWith( + _$CheckForModelImpl value, $Res Function(_$CheckForModelImpl) then) = + __$$CheckForModelImplCopyWithImpl<$Res>; + @useResult + $Res call({LlmModel model}); +} + +/// @nodoc +class __$$CheckForModelImplCopyWithImpl<$Res> + extends _$TranscriptEventCopyWithImpl<$Res, _$CheckForModelImpl> + implements _$$CheckForModelImplCopyWith<$Res> { + __$$CheckForModelImplCopyWithImpl( + _$CheckForModelImpl _value, $Res Function(_$CheckForModelImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? model = null, + }) { + return _then(_$CheckForModelImpl( + null == model + ? _value.model + : model // ignore: cast_nullable_to_non_nullable + as LlmModel, + )); + } +} + +/// @nodoc + +class _$CheckForModelImpl implements CheckForModel { + const _$CheckForModelImpl(this.model); + + @override + final LlmModel model; + + @override + String toString() { + return 'TranscriptEvent.checkForModel(model: $model)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$CheckForModelImpl && + (identical(other.model, model) || other.model == model)); + } + + @override + int get hashCode => Object.hash(runtimeType, model); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$CheckForModelImplCopyWith<_$CheckForModelImpl> get copyWith => + __$$CheckForModelImplCopyWithImpl<_$CheckForModelImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(LlmModel model) checkForModel, + required TResult Function(LlmModel model) downloadModel, + required TResult Function(LlmModel model, int percentDownloaded) + setPercentDownloaded, + required TResult Function(LlmModel model) deleteModel, + required TResult Function(LlmModel model) initEngine, + required TResult Function() initializeModelInfo, + required TResult Function(double value) updateTemperature, + required TResult Function(int value) updateTopK, + required TResult Function(int value) updateMaxTokens, + required TResult Function(ChatMessage message, LlmModel model) addMessage, + required TResult Function( + String chunk, int index, LlmModel model, bool first, bool last) + extendMessage, + required TResult Function(LlmModel model) completeResponse, + }) { + return checkForModel(model); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(LlmModel model)? checkForModel, + TResult? Function(LlmModel model)? downloadModel, + TResult? Function(LlmModel model, int percentDownloaded)? + setPercentDownloaded, + TResult? Function(LlmModel model)? deleteModel, + TResult? Function(LlmModel model)? initEngine, + TResult? Function()? initializeModelInfo, + TResult? Function(double value)? updateTemperature, + TResult? Function(int value)? updateTopK, + TResult? Function(int value)? updateMaxTokens, + TResult? Function(ChatMessage message, LlmModel model)? addMessage, + TResult? Function( + String chunk, int index, LlmModel model, bool first, bool last)? + extendMessage, + TResult? Function(LlmModel model)? completeResponse, + }) { + return checkForModel?.call(model); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(LlmModel model)? checkForModel, + TResult Function(LlmModel model)? downloadModel, + TResult Function(LlmModel model, int percentDownloaded)? + setPercentDownloaded, + TResult Function(LlmModel model)? deleteModel, + TResult Function(LlmModel model)? initEngine, + TResult Function()? initializeModelInfo, + TResult Function(double value)? updateTemperature, + TResult Function(int value)? updateTopK, + TResult Function(int value)? updateMaxTokens, + TResult Function(ChatMessage message, LlmModel model)? addMessage, + TResult Function( + String chunk, int index, LlmModel model, bool first, bool last)? + extendMessage, + TResult Function(LlmModel model)? completeResponse, + required TResult orElse(), + }) { + if (checkForModel != null) { + return checkForModel(model); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(CheckForModel value) checkForModel, + required TResult Function(DownloadModel value) downloadModel, + required TResult Function(SetPercentDownloaded value) setPercentDownloaded, + required TResult Function(DeleteModel value) deleteModel, + required TResult Function(InitEngine value) initEngine, + required TResult Function(InitializeModelInfo value) initializeModelInfo, + required TResult Function(UpdateTemperature value) updateTemperature, + required TResult Function(UpdateTopK value) updateTopK, + required TResult Function(UpdateMaxTokens value) updateMaxTokens, + required TResult Function(AddMessage value) addMessage, + required TResult Function(ExtendMessage value) extendMessage, + required TResult Function(CompleteResponse value) completeResponse, + }) { + return checkForModel(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(CheckForModel value)? checkForModel, + TResult? Function(DownloadModel value)? downloadModel, + TResult? Function(SetPercentDownloaded value)? setPercentDownloaded, + TResult? Function(DeleteModel value)? deleteModel, + TResult? Function(InitEngine value)? initEngine, + TResult? Function(InitializeModelInfo value)? initializeModelInfo, + TResult? Function(UpdateTemperature value)? updateTemperature, + TResult? Function(UpdateTopK value)? updateTopK, + TResult? Function(UpdateMaxTokens value)? updateMaxTokens, + TResult? Function(AddMessage value)? addMessage, + TResult? Function(ExtendMessage value)? extendMessage, + TResult? Function(CompleteResponse value)? completeResponse, + }) { + return checkForModel?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(CheckForModel value)? checkForModel, + TResult Function(DownloadModel value)? downloadModel, + TResult Function(SetPercentDownloaded value)? setPercentDownloaded, + TResult Function(DeleteModel value)? deleteModel, + TResult Function(InitEngine value)? initEngine, + TResult Function(InitializeModelInfo value)? initializeModelInfo, + TResult Function(UpdateTemperature value)? updateTemperature, + TResult Function(UpdateTopK value)? updateTopK, + TResult Function(UpdateMaxTokens value)? updateMaxTokens, + TResult Function(AddMessage value)? addMessage, + TResult Function(ExtendMessage value)? extendMessage, + TResult Function(CompleteResponse value)? completeResponse, + required TResult orElse(), + }) { + if (checkForModel != null) { + return checkForModel(this); + } + return orElse(); + } +} + +abstract class CheckForModel implements TranscriptEvent { + const factory CheckForModel(final LlmModel model) = _$CheckForModelImpl; + + LlmModel get model; + @JsonKey(ignore: true) + _$$CheckForModelImplCopyWith<_$CheckForModelImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$DownloadModelImplCopyWith<$Res> { + factory _$$DownloadModelImplCopyWith( + _$DownloadModelImpl value, $Res Function(_$DownloadModelImpl) then) = + __$$DownloadModelImplCopyWithImpl<$Res>; + @useResult + $Res call({LlmModel model}); +} + +/// @nodoc +class __$$DownloadModelImplCopyWithImpl<$Res> + extends _$TranscriptEventCopyWithImpl<$Res, _$DownloadModelImpl> + implements _$$DownloadModelImplCopyWith<$Res> { + __$$DownloadModelImplCopyWithImpl( + _$DownloadModelImpl _value, $Res Function(_$DownloadModelImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? model = null, + }) { + return _then(_$DownloadModelImpl( + null == model + ? _value.model + : model // ignore: cast_nullable_to_non_nullable + as LlmModel, + )); + } +} + +/// @nodoc + +class _$DownloadModelImpl implements DownloadModel { + const _$DownloadModelImpl(this.model); + + @override + final LlmModel model; + + @override + String toString() { + return 'TranscriptEvent.downloadModel(model: $model)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$DownloadModelImpl && + (identical(other.model, model) || other.model == model)); + } + + @override + int get hashCode => Object.hash(runtimeType, model); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$DownloadModelImplCopyWith<_$DownloadModelImpl> get copyWith => + __$$DownloadModelImplCopyWithImpl<_$DownloadModelImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(LlmModel model) checkForModel, + required TResult Function(LlmModel model) downloadModel, + required TResult Function(LlmModel model, int percentDownloaded) + setPercentDownloaded, + required TResult Function(LlmModel model) deleteModel, + required TResult Function(LlmModel model) initEngine, + required TResult Function() initializeModelInfo, + required TResult Function(double value) updateTemperature, + required TResult Function(int value) updateTopK, + required TResult Function(int value) updateMaxTokens, + required TResult Function(ChatMessage message, LlmModel model) addMessage, + required TResult Function( + String chunk, int index, LlmModel model, bool first, bool last) + extendMessage, + required TResult Function(LlmModel model) completeResponse, + }) { + return downloadModel(model); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(LlmModel model)? checkForModel, + TResult? Function(LlmModel model)? downloadModel, + TResult? Function(LlmModel model, int percentDownloaded)? + setPercentDownloaded, + TResult? Function(LlmModel model)? deleteModel, + TResult? Function(LlmModel model)? initEngine, + TResult? Function()? initializeModelInfo, + TResult? Function(double value)? updateTemperature, + TResult? Function(int value)? updateTopK, + TResult? Function(int value)? updateMaxTokens, + TResult? Function(ChatMessage message, LlmModel model)? addMessage, + TResult? Function( + String chunk, int index, LlmModel model, bool first, bool last)? + extendMessage, + TResult? Function(LlmModel model)? completeResponse, + }) { + return downloadModel?.call(model); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(LlmModel model)? checkForModel, + TResult Function(LlmModel model)? downloadModel, + TResult Function(LlmModel model, int percentDownloaded)? + setPercentDownloaded, + TResult Function(LlmModel model)? deleteModel, + TResult Function(LlmModel model)? initEngine, + TResult Function()? initializeModelInfo, + TResult Function(double value)? updateTemperature, + TResult Function(int value)? updateTopK, + TResult Function(int value)? updateMaxTokens, + TResult Function(ChatMessage message, LlmModel model)? addMessage, + TResult Function( + String chunk, int index, LlmModel model, bool first, bool last)? + extendMessage, + TResult Function(LlmModel model)? completeResponse, + required TResult orElse(), + }) { + if (downloadModel != null) { + return downloadModel(model); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(CheckForModel value) checkForModel, + required TResult Function(DownloadModel value) downloadModel, + required TResult Function(SetPercentDownloaded value) setPercentDownloaded, + required TResult Function(DeleteModel value) deleteModel, + required TResult Function(InitEngine value) initEngine, + required TResult Function(InitializeModelInfo value) initializeModelInfo, + required TResult Function(UpdateTemperature value) updateTemperature, + required TResult Function(UpdateTopK value) updateTopK, + required TResult Function(UpdateMaxTokens value) updateMaxTokens, + required TResult Function(AddMessage value) addMessage, + required TResult Function(ExtendMessage value) extendMessage, + required TResult Function(CompleteResponse value) completeResponse, + }) { + return downloadModel(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(CheckForModel value)? checkForModel, + TResult? Function(DownloadModel value)? downloadModel, + TResult? Function(SetPercentDownloaded value)? setPercentDownloaded, + TResult? Function(DeleteModel value)? deleteModel, + TResult? Function(InitEngine value)? initEngine, + TResult? Function(InitializeModelInfo value)? initializeModelInfo, + TResult? Function(UpdateTemperature value)? updateTemperature, + TResult? Function(UpdateTopK value)? updateTopK, + TResult? Function(UpdateMaxTokens value)? updateMaxTokens, + TResult? Function(AddMessage value)? addMessage, + TResult? Function(ExtendMessage value)? extendMessage, + TResult? Function(CompleteResponse value)? completeResponse, + }) { + return downloadModel?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(CheckForModel value)? checkForModel, + TResult Function(DownloadModel value)? downloadModel, + TResult Function(SetPercentDownloaded value)? setPercentDownloaded, + TResult Function(DeleteModel value)? deleteModel, + TResult Function(InitEngine value)? initEngine, + TResult Function(InitializeModelInfo value)? initializeModelInfo, + TResult Function(UpdateTemperature value)? updateTemperature, + TResult Function(UpdateTopK value)? updateTopK, + TResult Function(UpdateMaxTokens value)? updateMaxTokens, + TResult Function(AddMessage value)? addMessage, + TResult Function(ExtendMessage value)? extendMessage, + TResult Function(CompleteResponse value)? completeResponse, + required TResult orElse(), + }) { + if (downloadModel != null) { + return downloadModel(this); + } + return orElse(); + } +} + +abstract class DownloadModel implements TranscriptEvent { + const factory DownloadModel(final LlmModel model) = _$DownloadModelImpl; + + LlmModel get model; + @JsonKey(ignore: true) + _$$DownloadModelImplCopyWith<_$DownloadModelImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$SetPercentDownloadedImplCopyWith<$Res> { + factory _$$SetPercentDownloadedImplCopyWith(_$SetPercentDownloadedImpl value, + $Res Function(_$SetPercentDownloadedImpl) then) = + __$$SetPercentDownloadedImplCopyWithImpl<$Res>; + @useResult + $Res call({LlmModel model, int percentDownloaded}); +} + +/// @nodoc +class __$$SetPercentDownloadedImplCopyWithImpl<$Res> + extends _$TranscriptEventCopyWithImpl<$Res, _$SetPercentDownloadedImpl> + implements _$$SetPercentDownloadedImplCopyWith<$Res> { + __$$SetPercentDownloadedImplCopyWithImpl(_$SetPercentDownloadedImpl _value, + $Res Function(_$SetPercentDownloadedImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? model = null, + Object? percentDownloaded = null, + }) { + return _then(_$SetPercentDownloadedImpl( + null == model + ? _value.model + : model // ignore: cast_nullable_to_non_nullable + as LlmModel, + null == percentDownloaded + ? _value.percentDownloaded + : percentDownloaded // ignore: cast_nullable_to_non_nullable + as int, + )); + } +} + +/// @nodoc + +class _$SetPercentDownloadedImpl implements SetPercentDownloaded { + const _$SetPercentDownloadedImpl(this.model, this.percentDownloaded); + + @override + final LlmModel model; + @override + final int percentDownloaded; + + @override + String toString() { + return 'TranscriptEvent.setPercentDownloaded(model: $model, percentDownloaded: $percentDownloaded)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$SetPercentDownloadedImpl && + (identical(other.model, model) || other.model == model) && + (identical(other.percentDownloaded, percentDownloaded) || + other.percentDownloaded == percentDownloaded)); + } + + @override + int get hashCode => Object.hash(runtimeType, model, percentDownloaded); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$SetPercentDownloadedImplCopyWith<_$SetPercentDownloadedImpl> + get copyWith => + __$$SetPercentDownloadedImplCopyWithImpl<_$SetPercentDownloadedImpl>( + this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(LlmModel model) checkForModel, + required TResult Function(LlmModel model) downloadModel, + required TResult Function(LlmModel model, int percentDownloaded) + setPercentDownloaded, + required TResult Function(LlmModel model) deleteModel, + required TResult Function(LlmModel model) initEngine, + required TResult Function() initializeModelInfo, + required TResult Function(double value) updateTemperature, + required TResult Function(int value) updateTopK, + required TResult Function(int value) updateMaxTokens, + required TResult Function(ChatMessage message, LlmModel model) addMessage, + required TResult Function( + String chunk, int index, LlmModel model, bool first, bool last) + extendMessage, + required TResult Function(LlmModel model) completeResponse, + }) { + return setPercentDownloaded(model, percentDownloaded); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(LlmModel model)? checkForModel, + TResult? Function(LlmModel model)? downloadModel, + TResult? Function(LlmModel model, int percentDownloaded)? + setPercentDownloaded, + TResult? Function(LlmModel model)? deleteModel, + TResult? Function(LlmModel model)? initEngine, + TResult? Function()? initializeModelInfo, + TResult? Function(double value)? updateTemperature, + TResult? Function(int value)? updateTopK, + TResult? Function(int value)? updateMaxTokens, + TResult? Function(ChatMessage message, LlmModel model)? addMessage, + TResult? Function( + String chunk, int index, LlmModel model, bool first, bool last)? + extendMessage, + TResult? Function(LlmModel model)? completeResponse, + }) { + return setPercentDownloaded?.call(model, percentDownloaded); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(LlmModel model)? checkForModel, + TResult Function(LlmModel model)? downloadModel, + TResult Function(LlmModel model, int percentDownloaded)? + setPercentDownloaded, + TResult Function(LlmModel model)? deleteModel, + TResult Function(LlmModel model)? initEngine, + TResult Function()? initializeModelInfo, + TResult Function(double value)? updateTemperature, + TResult Function(int value)? updateTopK, + TResult Function(int value)? updateMaxTokens, + TResult Function(ChatMessage message, LlmModel model)? addMessage, + TResult Function( + String chunk, int index, LlmModel model, bool first, bool last)? + extendMessage, + TResult Function(LlmModel model)? completeResponse, + required TResult orElse(), + }) { + if (setPercentDownloaded != null) { + return setPercentDownloaded(model, percentDownloaded); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(CheckForModel value) checkForModel, + required TResult Function(DownloadModel value) downloadModel, + required TResult Function(SetPercentDownloaded value) setPercentDownloaded, + required TResult Function(DeleteModel value) deleteModel, + required TResult Function(InitEngine value) initEngine, + required TResult Function(InitializeModelInfo value) initializeModelInfo, + required TResult Function(UpdateTemperature value) updateTemperature, + required TResult Function(UpdateTopK value) updateTopK, + required TResult Function(UpdateMaxTokens value) updateMaxTokens, + required TResult Function(AddMessage value) addMessage, + required TResult Function(ExtendMessage value) extendMessage, + required TResult Function(CompleteResponse value) completeResponse, + }) { + return setPercentDownloaded(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(CheckForModel value)? checkForModel, + TResult? Function(DownloadModel value)? downloadModel, + TResult? Function(SetPercentDownloaded value)? setPercentDownloaded, + TResult? Function(DeleteModel value)? deleteModel, + TResult? Function(InitEngine value)? initEngine, + TResult? Function(InitializeModelInfo value)? initializeModelInfo, + TResult? Function(UpdateTemperature value)? updateTemperature, + TResult? Function(UpdateTopK value)? updateTopK, + TResult? Function(UpdateMaxTokens value)? updateMaxTokens, + TResult? Function(AddMessage value)? addMessage, + TResult? Function(ExtendMessage value)? extendMessage, + TResult? Function(CompleteResponse value)? completeResponse, + }) { + return setPercentDownloaded?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(CheckForModel value)? checkForModel, + TResult Function(DownloadModel value)? downloadModel, + TResult Function(SetPercentDownloaded value)? setPercentDownloaded, + TResult Function(DeleteModel value)? deleteModel, + TResult Function(InitEngine value)? initEngine, + TResult Function(InitializeModelInfo value)? initializeModelInfo, + TResult Function(UpdateTemperature value)? updateTemperature, + TResult Function(UpdateTopK value)? updateTopK, + TResult Function(UpdateMaxTokens value)? updateMaxTokens, + TResult Function(AddMessage value)? addMessage, + TResult Function(ExtendMessage value)? extendMessage, + TResult Function(CompleteResponse value)? completeResponse, + required TResult orElse(), + }) { + if (setPercentDownloaded != null) { + return setPercentDownloaded(this); + } + return orElse(); + } +} + +abstract class SetPercentDownloaded implements TranscriptEvent { + const factory SetPercentDownloaded( + final LlmModel model, final int percentDownloaded) = + _$SetPercentDownloadedImpl; + + LlmModel get model; + int get percentDownloaded; + @JsonKey(ignore: true) + _$$SetPercentDownloadedImplCopyWith<_$SetPercentDownloadedImpl> + get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$DeleteModelImplCopyWith<$Res> { + factory _$$DeleteModelImplCopyWith( + _$DeleteModelImpl value, $Res Function(_$DeleteModelImpl) then) = + __$$DeleteModelImplCopyWithImpl<$Res>; + @useResult + $Res call({LlmModel model}); +} + +/// @nodoc +class __$$DeleteModelImplCopyWithImpl<$Res> + extends _$TranscriptEventCopyWithImpl<$Res, _$DeleteModelImpl> + implements _$$DeleteModelImplCopyWith<$Res> { + __$$DeleteModelImplCopyWithImpl( + _$DeleteModelImpl _value, $Res Function(_$DeleteModelImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? model = null, + }) { + return _then(_$DeleteModelImpl( + null == model + ? _value.model + : model // ignore: cast_nullable_to_non_nullable + as LlmModel, + )); + } +} + +/// @nodoc + +class _$DeleteModelImpl implements DeleteModel { + const _$DeleteModelImpl(this.model); + + @override + final LlmModel model; + + @override + String toString() { + return 'TranscriptEvent.deleteModel(model: $model)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$DeleteModelImpl && + (identical(other.model, model) || other.model == model)); + } + + @override + int get hashCode => Object.hash(runtimeType, model); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$DeleteModelImplCopyWith<_$DeleteModelImpl> get copyWith => + __$$DeleteModelImplCopyWithImpl<_$DeleteModelImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(LlmModel model) checkForModel, + required TResult Function(LlmModel model) downloadModel, + required TResult Function(LlmModel model, int percentDownloaded) + setPercentDownloaded, + required TResult Function(LlmModel model) deleteModel, + required TResult Function(LlmModel model) initEngine, + required TResult Function() initializeModelInfo, + required TResult Function(double value) updateTemperature, + required TResult Function(int value) updateTopK, + required TResult Function(int value) updateMaxTokens, + required TResult Function(ChatMessage message, LlmModel model) addMessage, + required TResult Function( + String chunk, int index, LlmModel model, bool first, bool last) + extendMessage, + required TResult Function(LlmModel model) completeResponse, + }) { + return deleteModel(model); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(LlmModel model)? checkForModel, + TResult? Function(LlmModel model)? downloadModel, + TResult? Function(LlmModel model, int percentDownloaded)? + setPercentDownloaded, + TResult? Function(LlmModel model)? deleteModel, + TResult? Function(LlmModel model)? initEngine, + TResult? Function()? initializeModelInfo, + TResult? Function(double value)? updateTemperature, + TResult? Function(int value)? updateTopK, + TResult? Function(int value)? updateMaxTokens, + TResult? Function(ChatMessage message, LlmModel model)? addMessage, + TResult? Function( + String chunk, int index, LlmModel model, bool first, bool last)? + extendMessage, + TResult? Function(LlmModel model)? completeResponse, + }) { + return deleteModel?.call(model); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(LlmModel model)? checkForModel, + TResult Function(LlmModel model)? downloadModel, + TResult Function(LlmModel model, int percentDownloaded)? + setPercentDownloaded, + TResult Function(LlmModel model)? deleteModel, + TResult Function(LlmModel model)? initEngine, + TResult Function()? initializeModelInfo, + TResult Function(double value)? updateTemperature, + TResult Function(int value)? updateTopK, + TResult Function(int value)? updateMaxTokens, + TResult Function(ChatMessage message, LlmModel model)? addMessage, + TResult Function( + String chunk, int index, LlmModel model, bool first, bool last)? + extendMessage, + TResult Function(LlmModel model)? completeResponse, + required TResult orElse(), + }) { + if (deleteModel != null) { + return deleteModel(model); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(CheckForModel value) checkForModel, + required TResult Function(DownloadModel value) downloadModel, + required TResult Function(SetPercentDownloaded value) setPercentDownloaded, + required TResult Function(DeleteModel value) deleteModel, + required TResult Function(InitEngine value) initEngine, + required TResult Function(InitializeModelInfo value) initializeModelInfo, + required TResult Function(UpdateTemperature value) updateTemperature, + required TResult Function(UpdateTopK value) updateTopK, + required TResult Function(UpdateMaxTokens value) updateMaxTokens, + required TResult Function(AddMessage value) addMessage, + required TResult Function(ExtendMessage value) extendMessage, + required TResult Function(CompleteResponse value) completeResponse, + }) { + return deleteModel(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(CheckForModel value)? checkForModel, + TResult? Function(DownloadModel value)? downloadModel, + TResult? Function(SetPercentDownloaded value)? setPercentDownloaded, + TResult? Function(DeleteModel value)? deleteModel, + TResult? Function(InitEngine value)? initEngine, + TResult? Function(InitializeModelInfo value)? initializeModelInfo, + TResult? Function(UpdateTemperature value)? updateTemperature, + TResult? Function(UpdateTopK value)? updateTopK, + TResult? Function(UpdateMaxTokens value)? updateMaxTokens, + TResult? Function(AddMessage value)? addMessage, + TResult? Function(ExtendMessage value)? extendMessage, + TResult? Function(CompleteResponse value)? completeResponse, + }) { + return deleteModel?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(CheckForModel value)? checkForModel, + TResult Function(DownloadModel value)? downloadModel, + TResult Function(SetPercentDownloaded value)? setPercentDownloaded, + TResult Function(DeleteModel value)? deleteModel, + TResult Function(InitEngine value)? initEngine, + TResult Function(InitializeModelInfo value)? initializeModelInfo, + TResult Function(UpdateTemperature value)? updateTemperature, + TResult Function(UpdateTopK value)? updateTopK, + TResult Function(UpdateMaxTokens value)? updateMaxTokens, + TResult Function(AddMessage value)? addMessage, + TResult Function(ExtendMessage value)? extendMessage, + TResult Function(CompleteResponse value)? completeResponse, + required TResult orElse(), + }) { + if (deleteModel != null) { + return deleteModel(this); + } + return orElse(); + } +} + +abstract class DeleteModel implements TranscriptEvent { + const factory DeleteModel(final LlmModel model) = _$DeleteModelImpl; + + LlmModel get model; + @JsonKey(ignore: true) + _$$DeleteModelImplCopyWith<_$DeleteModelImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$InitEngineImplCopyWith<$Res> { + factory _$$InitEngineImplCopyWith( + _$InitEngineImpl value, $Res Function(_$InitEngineImpl) then) = + __$$InitEngineImplCopyWithImpl<$Res>; + @useResult + $Res call({LlmModel model}); +} + +/// @nodoc +class __$$InitEngineImplCopyWithImpl<$Res> + extends _$TranscriptEventCopyWithImpl<$Res, _$InitEngineImpl> + implements _$$InitEngineImplCopyWith<$Res> { + __$$InitEngineImplCopyWithImpl( + _$InitEngineImpl _value, $Res Function(_$InitEngineImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? model = null, + }) { + return _then(_$InitEngineImpl( + null == model + ? _value.model + : model // ignore: cast_nullable_to_non_nullable + as LlmModel, + )); + } +} + +/// @nodoc + +class _$InitEngineImpl implements InitEngine { + const _$InitEngineImpl(this.model); + + @override + final LlmModel model; + + @override + String toString() { + return 'TranscriptEvent.initEngine(model: $model)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$InitEngineImpl && + (identical(other.model, model) || other.model == model)); + } + + @override + int get hashCode => Object.hash(runtimeType, model); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$InitEngineImplCopyWith<_$InitEngineImpl> get copyWith => + __$$InitEngineImplCopyWithImpl<_$InitEngineImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(LlmModel model) checkForModel, + required TResult Function(LlmModel model) downloadModel, + required TResult Function(LlmModel model, int percentDownloaded) + setPercentDownloaded, + required TResult Function(LlmModel model) deleteModel, + required TResult Function(LlmModel model) initEngine, + required TResult Function() initializeModelInfo, + required TResult Function(double value) updateTemperature, + required TResult Function(int value) updateTopK, + required TResult Function(int value) updateMaxTokens, + required TResult Function(ChatMessage message, LlmModel model) addMessage, + required TResult Function( + String chunk, int index, LlmModel model, bool first, bool last) + extendMessage, + required TResult Function(LlmModel model) completeResponse, + }) { + return initEngine(model); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(LlmModel model)? checkForModel, + TResult? Function(LlmModel model)? downloadModel, + TResult? Function(LlmModel model, int percentDownloaded)? + setPercentDownloaded, + TResult? Function(LlmModel model)? deleteModel, + TResult? Function(LlmModel model)? initEngine, + TResult? Function()? initializeModelInfo, + TResult? Function(double value)? updateTemperature, + TResult? Function(int value)? updateTopK, + TResult? Function(int value)? updateMaxTokens, + TResult? Function(ChatMessage message, LlmModel model)? addMessage, + TResult? Function( + String chunk, int index, LlmModel model, bool first, bool last)? + extendMessage, + TResult? Function(LlmModel model)? completeResponse, + }) { + return initEngine?.call(model); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(LlmModel model)? checkForModel, + TResult Function(LlmModel model)? downloadModel, + TResult Function(LlmModel model, int percentDownloaded)? + setPercentDownloaded, + TResult Function(LlmModel model)? deleteModel, + TResult Function(LlmModel model)? initEngine, + TResult Function()? initializeModelInfo, + TResult Function(double value)? updateTemperature, + TResult Function(int value)? updateTopK, + TResult Function(int value)? updateMaxTokens, + TResult Function(ChatMessage message, LlmModel model)? addMessage, + TResult Function( + String chunk, int index, LlmModel model, bool first, bool last)? + extendMessage, + TResult Function(LlmModel model)? completeResponse, + required TResult orElse(), + }) { + if (initEngine != null) { + return initEngine(model); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(CheckForModel value) checkForModel, + required TResult Function(DownloadModel value) downloadModel, + required TResult Function(SetPercentDownloaded value) setPercentDownloaded, + required TResult Function(DeleteModel value) deleteModel, + required TResult Function(InitEngine value) initEngine, + required TResult Function(InitializeModelInfo value) initializeModelInfo, + required TResult Function(UpdateTemperature value) updateTemperature, + required TResult Function(UpdateTopK value) updateTopK, + required TResult Function(UpdateMaxTokens value) updateMaxTokens, + required TResult Function(AddMessage value) addMessage, + required TResult Function(ExtendMessage value) extendMessage, + required TResult Function(CompleteResponse value) completeResponse, + }) { + return initEngine(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(CheckForModel value)? checkForModel, + TResult? Function(DownloadModel value)? downloadModel, + TResult? Function(SetPercentDownloaded value)? setPercentDownloaded, + TResult? Function(DeleteModel value)? deleteModel, + TResult? Function(InitEngine value)? initEngine, + TResult? Function(InitializeModelInfo value)? initializeModelInfo, + TResult? Function(UpdateTemperature value)? updateTemperature, + TResult? Function(UpdateTopK value)? updateTopK, + TResult? Function(UpdateMaxTokens value)? updateMaxTokens, + TResult? Function(AddMessage value)? addMessage, + TResult? Function(ExtendMessage value)? extendMessage, + TResult? Function(CompleteResponse value)? completeResponse, + }) { + return initEngine?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(CheckForModel value)? checkForModel, + TResult Function(DownloadModel value)? downloadModel, + TResult Function(SetPercentDownloaded value)? setPercentDownloaded, + TResult Function(DeleteModel value)? deleteModel, + TResult Function(InitEngine value)? initEngine, + TResult Function(InitializeModelInfo value)? initializeModelInfo, + TResult Function(UpdateTemperature value)? updateTemperature, + TResult Function(UpdateTopK value)? updateTopK, + TResult Function(UpdateMaxTokens value)? updateMaxTokens, + TResult Function(AddMessage value)? addMessage, + TResult Function(ExtendMessage value)? extendMessage, + TResult Function(CompleteResponse value)? completeResponse, + required TResult orElse(), + }) { + if (initEngine != null) { + return initEngine(this); + } + return orElse(); + } +} + +abstract class InitEngine implements TranscriptEvent { + const factory InitEngine(final LlmModel model) = _$InitEngineImpl; + + LlmModel get model; + @JsonKey(ignore: true) + _$$InitEngineImplCopyWith<_$InitEngineImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$InitializeModelInfoImplCopyWith<$Res> { + factory _$$InitializeModelInfoImplCopyWith(_$InitializeModelInfoImpl value, + $Res Function(_$InitializeModelInfoImpl) then) = + __$$InitializeModelInfoImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$InitializeModelInfoImplCopyWithImpl<$Res> + extends _$TranscriptEventCopyWithImpl<$Res, _$InitializeModelInfoImpl> + implements _$$InitializeModelInfoImplCopyWith<$Res> { + __$$InitializeModelInfoImplCopyWithImpl(_$InitializeModelInfoImpl _value, + $Res Function(_$InitializeModelInfoImpl) _then) + : super(_value, _then); +} + +/// @nodoc + +class _$InitializeModelInfoImpl implements InitializeModelInfo { + const _$InitializeModelInfoImpl(); + + @override + String toString() { + return 'TranscriptEvent.initializeModelInfo()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$InitializeModelInfoImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(LlmModel model) checkForModel, + required TResult Function(LlmModel model) downloadModel, + required TResult Function(LlmModel model, int percentDownloaded) + setPercentDownloaded, + required TResult Function(LlmModel model) deleteModel, + required TResult Function(LlmModel model) initEngine, + required TResult Function() initializeModelInfo, + required TResult Function(double value) updateTemperature, + required TResult Function(int value) updateTopK, + required TResult Function(int value) updateMaxTokens, + required TResult Function(ChatMessage message, LlmModel model) addMessage, + required TResult Function( + String chunk, int index, LlmModel model, bool first, bool last) + extendMessage, + required TResult Function(LlmModel model) completeResponse, + }) { + return initializeModelInfo(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(LlmModel model)? checkForModel, + TResult? Function(LlmModel model)? downloadModel, + TResult? Function(LlmModel model, int percentDownloaded)? + setPercentDownloaded, + TResult? Function(LlmModel model)? deleteModel, + TResult? Function(LlmModel model)? initEngine, + TResult? Function()? initializeModelInfo, + TResult? Function(double value)? updateTemperature, + TResult? Function(int value)? updateTopK, + TResult? Function(int value)? updateMaxTokens, + TResult? Function(ChatMessage message, LlmModel model)? addMessage, + TResult? Function( + String chunk, int index, LlmModel model, bool first, bool last)? + extendMessage, + TResult? Function(LlmModel model)? completeResponse, + }) { + return initializeModelInfo?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(LlmModel model)? checkForModel, + TResult Function(LlmModel model)? downloadModel, + TResult Function(LlmModel model, int percentDownloaded)? + setPercentDownloaded, + TResult Function(LlmModel model)? deleteModel, + TResult Function(LlmModel model)? initEngine, + TResult Function()? initializeModelInfo, + TResult Function(double value)? updateTemperature, + TResult Function(int value)? updateTopK, + TResult Function(int value)? updateMaxTokens, + TResult Function(ChatMessage message, LlmModel model)? addMessage, + TResult Function( + String chunk, int index, LlmModel model, bool first, bool last)? + extendMessage, + TResult Function(LlmModel model)? completeResponse, + required TResult orElse(), + }) { + if (initializeModelInfo != null) { + return initializeModelInfo(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(CheckForModel value) checkForModel, + required TResult Function(DownloadModel value) downloadModel, + required TResult Function(SetPercentDownloaded value) setPercentDownloaded, + required TResult Function(DeleteModel value) deleteModel, + required TResult Function(InitEngine value) initEngine, + required TResult Function(InitializeModelInfo value) initializeModelInfo, + required TResult Function(UpdateTemperature value) updateTemperature, + required TResult Function(UpdateTopK value) updateTopK, + required TResult Function(UpdateMaxTokens value) updateMaxTokens, + required TResult Function(AddMessage value) addMessage, + required TResult Function(ExtendMessage value) extendMessage, + required TResult Function(CompleteResponse value) completeResponse, + }) { + return initializeModelInfo(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(CheckForModel value)? checkForModel, + TResult? Function(DownloadModel value)? downloadModel, + TResult? Function(SetPercentDownloaded value)? setPercentDownloaded, + TResult? Function(DeleteModel value)? deleteModel, + TResult? Function(InitEngine value)? initEngine, + TResult? Function(InitializeModelInfo value)? initializeModelInfo, + TResult? Function(UpdateTemperature value)? updateTemperature, + TResult? Function(UpdateTopK value)? updateTopK, + TResult? Function(UpdateMaxTokens value)? updateMaxTokens, + TResult? Function(AddMessage value)? addMessage, + TResult? Function(ExtendMessage value)? extendMessage, + TResult? Function(CompleteResponse value)? completeResponse, + }) { + return initializeModelInfo?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(CheckForModel value)? checkForModel, + TResult Function(DownloadModel value)? downloadModel, + TResult Function(SetPercentDownloaded value)? setPercentDownloaded, + TResult Function(DeleteModel value)? deleteModel, + TResult Function(InitEngine value)? initEngine, + TResult Function(InitializeModelInfo value)? initializeModelInfo, + TResult Function(UpdateTemperature value)? updateTemperature, + TResult Function(UpdateTopK value)? updateTopK, + TResult Function(UpdateMaxTokens value)? updateMaxTokens, + TResult Function(AddMessage value)? addMessage, + TResult Function(ExtendMessage value)? extendMessage, + TResult Function(CompleteResponse value)? completeResponse, + required TResult orElse(), + }) { + if (initializeModelInfo != null) { + return initializeModelInfo(this); + } + return orElse(); + } +} + +abstract class InitializeModelInfo implements TranscriptEvent { + const factory InitializeModelInfo() = _$InitializeModelInfoImpl; +} + +/// @nodoc +abstract class _$$UpdateTemperatureImplCopyWith<$Res> { + factory _$$UpdateTemperatureImplCopyWith(_$UpdateTemperatureImpl value, + $Res Function(_$UpdateTemperatureImpl) then) = + __$$UpdateTemperatureImplCopyWithImpl<$Res>; + @useResult + $Res call({double value}); +} + +/// @nodoc +class __$$UpdateTemperatureImplCopyWithImpl<$Res> + extends _$TranscriptEventCopyWithImpl<$Res, _$UpdateTemperatureImpl> + implements _$$UpdateTemperatureImplCopyWith<$Res> { + __$$UpdateTemperatureImplCopyWithImpl(_$UpdateTemperatureImpl _value, + $Res Function(_$UpdateTemperatureImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? value = null, + }) { + return _then(_$UpdateTemperatureImpl( + null == value + ? _value.value + : value // ignore: cast_nullable_to_non_nullable + as double, + )); + } +} + +/// @nodoc + +class _$UpdateTemperatureImpl implements UpdateTemperature { + const _$UpdateTemperatureImpl(this.value); + + @override + final double value; + + @override + String toString() { + return 'TranscriptEvent.updateTemperature(value: $value)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$UpdateTemperatureImpl && + (identical(other.value, value) || other.value == value)); + } + + @override + int get hashCode => Object.hash(runtimeType, value); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$UpdateTemperatureImplCopyWith<_$UpdateTemperatureImpl> get copyWith => + __$$UpdateTemperatureImplCopyWithImpl<_$UpdateTemperatureImpl>( + this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(LlmModel model) checkForModel, + required TResult Function(LlmModel model) downloadModel, + required TResult Function(LlmModel model, int percentDownloaded) + setPercentDownloaded, + required TResult Function(LlmModel model) deleteModel, + required TResult Function(LlmModel model) initEngine, + required TResult Function() initializeModelInfo, + required TResult Function(double value) updateTemperature, + required TResult Function(int value) updateTopK, + required TResult Function(int value) updateMaxTokens, + required TResult Function(ChatMessage message, LlmModel model) addMessage, + required TResult Function( + String chunk, int index, LlmModel model, bool first, bool last) + extendMessage, + required TResult Function(LlmModel model) completeResponse, + }) { + return updateTemperature(value); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(LlmModel model)? checkForModel, + TResult? Function(LlmModel model)? downloadModel, + TResult? Function(LlmModel model, int percentDownloaded)? + setPercentDownloaded, + TResult? Function(LlmModel model)? deleteModel, + TResult? Function(LlmModel model)? initEngine, + TResult? Function()? initializeModelInfo, + TResult? Function(double value)? updateTemperature, + TResult? Function(int value)? updateTopK, + TResult? Function(int value)? updateMaxTokens, + TResult? Function(ChatMessage message, LlmModel model)? addMessage, + TResult? Function( + String chunk, int index, LlmModel model, bool first, bool last)? + extendMessage, + TResult? Function(LlmModel model)? completeResponse, + }) { + return updateTemperature?.call(value); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(LlmModel model)? checkForModel, + TResult Function(LlmModel model)? downloadModel, + TResult Function(LlmModel model, int percentDownloaded)? + setPercentDownloaded, + TResult Function(LlmModel model)? deleteModel, + TResult Function(LlmModel model)? initEngine, + TResult Function()? initializeModelInfo, + TResult Function(double value)? updateTemperature, + TResult Function(int value)? updateTopK, + TResult Function(int value)? updateMaxTokens, + TResult Function(ChatMessage message, LlmModel model)? addMessage, + TResult Function( + String chunk, int index, LlmModel model, bool first, bool last)? + extendMessage, + TResult Function(LlmModel model)? completeResponse, + required TResult orElse(), + }) { + if (updateTemperature != null) { + return updateTemperature(value); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(CheckForModel value) checkForModel, + required TResult Function(DownloadModel value) downloadModel, + required TResult Function(SetPercentDownloaded value) setPercentDownloaded, + required TResult Function(DeleteModel value) deleteModel, + required TResult Function(InitEngine value) initEngine, + required TResult Function(InitializeModelInfo value) initializeModelInfo, + required TResult Function(UpdateTemperature value) updateTemperature, + required TResult Function(UpdateTopK value) updateTopK, + required TResult Function(UpdateMaxTokens value) updateMaxTokens, + required TResult Function(AddMessage value) addMessage, + required TResult Function(ExtendMessage value) extendMessage, + required TResult Function(CompleteResponse value) completeResponse, + }) { + return updateTemperature(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(CheckForModel value)? checkForModel, + TResult? Function(DownloadModel value)? downloadModel, + TResult? Function(SetPercentDownloaded value)? setPercentDownloaded, + TResult? Function(DeleteModel value)? deleteModel, + TResult? Function(InitEngine value)? initEngine, + TResult? Function(InitializeModelInfo value)? initializeModelInfo, + TResult? Function(UpdateTemperature value)? updateTemperature, + TResult? Function(UpdateTopK value)? updateTopK, + TResult? Function(UpdateMaxTokens value)? updateMaxTokens, + TResult? Function(AddMessage value)? addMessage, + TResult? Function(ExtendMessage value)? extendMessage, + TResult? Function(CompleteResponse value)? completeResponse, + }) { + return updateTemperature?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(CheckForModel value)? checkForModel, + TResult Function(DownloadModel value)? downloadModel, + TResult Function(SetPercentDownloaded value)? setPercentDownloaded, + TResult Function(DeleteModel value)? deleteModel, + TResult Function(InitEngine value)? initEngine, + TResult Function(InitializeModelInfo value)? initializeModelInfo, + TResult Function(UpdateTemperature value)? updateTemperature, + TResult Function(UpdateTopK value)? updateTopK, + TResult Function(UpdateMaxTokens value)? updateMaxTokens, + TResult Function(AddMessage value)? addMessage, + TResult Function(ExtendMessage value)? extendMessage, + TResult Function(CompleteResponse value)? completeResponse, + required TResult orElse(), + }) { + if (updateTemperature != null) { + return updateTemperature(this); + } + return orElse(); + } +} + +abstract class UpdateTemperature implements TranscriptEvent { + const factory UpdateTemperature(final double value) = _$UpdateTemperatureImpl; + + double get value; + @JsonKey(ignore: true) + _$$UpdateTemperatureImplCopyWith<_$UpdateTemperatureImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$UpdateTopKImplCopyWith<$Res> { + factory _$$UpdateTopKImplCopyWith( + _$UpdateTopKImpl value, $Res Function(_$UpdateTopKImpl) then) = + __$$UpdateTopKImplCopyWithImpl<$Res>; + @useResult + $Res call({int value}); +} + +/// @nodoc +class __$$UpdateTopKImplCopyWithImpl<$Res> + extends _$TranscriptEventCopyWithImpl<$Res, _$UpdateTopKImpl> + implements _$$UpdateTopKImplCopyWith<$Res> { + __$$UpdateTopKImplCopyWithImpl( + _$UpdateTopKImpl _value, $Res Function(_$UpdateTopKImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? value = null, + }) { + return _then(_$UpdateTopKImpl( + null == value + ? _value.value + : value // ignore: cast_nullable_to_non_nullable + as int, + )); + } +} + +/// @nodoc + +class _$UpdateTopKImpl implements UpdateTopK { + const _$UpdateTopKImpl(this.value); + + @override + final int value; + + @override + String toString() { + return 'TranscriptEvent.updateTopK(value: $value)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$UpdateTopKImpl && + (identical(other.value, value) || other.value == value)); + } + + @override + int get hashCode => Object.hash(runtimeType, value); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$UpdateTopKImplCopyWith<_$UpdateTopKImpl> get copyWith => + __$$UpdateTopKImplCopyWithImpl<_$UpdateTopKImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(LlmModel model) checkForModel, + required TResult Function(LlmModel model) downloadModel, + required TResult Function(LlmModel model, int percentDownloaded) + setPercentDownloaded, + required TResult Function(LlmModel model) deleteModel, + required TResult Function(LlmModel model) initEngine, + required TResult Function() initializeModelInfo, + required TResult Function(double value) updateTemperature, + required TResult Function(int value) updateTopK, + required TResult Function(int value) updateMaxTokens, + required TResult Function(ChatMessage message, LlmModel model) addMessage, + required TResult Function( + String chunk, int index, LlmModel model, bool first, bool last) + extendMessage, + required TResult Function(LlmModel model) completeResponse, + }) { + return updateTopK(value); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(LlmModel model)? checkForModel, + TResult? Function(LlmModel model)? downloadModel, + TResult? Function(LlmModel model, int percentDownloaded)? + setPercentDownloaded, + TResult? Function(LlmModel model)? deleteModel, + TResult? Function(LlmModel model)? initEngine, + TResult? Function()? initializeModelInfo, + TResult? Function(double value)? updateTemperature, + TResult? Function(int value)? updateTopK, + TResult? Function(int value)? updateMaxTokens, + TResult? Function(ChatMessage message, LlmModel model)? addMessage, + TResult? Function( + String chunk, int index, LlmModel model, bool first, bool last)? + extendMessage, + TResult? Function(LlmModel model)? completeResponse, + }) { + return updateTopK?.call(value); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(LlmModel model)? checkForModel, + TResult Function(LlmModel model)? downloadModel, + TResult Function(LlmModel model, int percentDownloaded)? + setPercentDownloaded, + TResult Function(LlmModel model)? deleteModel, + TResult Function(LlmModel model)? initEngine, + TResult Function()? initializeModelInfo, + TResult Function(double value)? updateTemperature, + TResult Function(int value)? updateTopK, + TResult Function(int value)? updateMaxTokens, + TResult Function(ChatMessage message, LlmModel model)? addMessage, + TResult Function( + String chunk, int index, LlmModel model, bool first, bool last)? + extendMessage, + TResult Function(LlmModel model)? completeResponse, + required TResult orElse(), + }) { + if (updateTopK != null) { + return updateTopK(value); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(CheckForModel value) checkForModel, + required TResult Function(DownloadModel value) downloadModel, + required TResult Function(SetPercentDownloaded value) setPercentDownloaded, + required TResult Function(DeleteModel value) deleteModel, + required TResult Function(InitEngine value) initEngine, + required TResult Function(InitializeModelInfo value) initializeModelInfo, + required TResult Function(UpdateTemperature value) updateTemperature, + required TResult Function(UpdateTopK value) updateTopK, + required TResult Function(UpdateMaxTokens value) updateMaxTokens, + required TResult Function(AddMessage value) addMessage, + required TResult Function(ExtendMessage value) extendMessage, + required TResult Function(CompleteResponse value) completeResponse, + }) { + return updateTopK(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(CheckForModel value)? checkForModel, + TResult? Function(DownloadModel value)? downloadModel, + TResult? Function(SetPercentDownloaded value)? setPercentDownloaded, + TResult? Function(DeleteModel value)? deleteModel, + TResult? Function(InitEngine value)? initEngine, + TResult? Function(InitializeModelInfo value)? initializeModelInfo, + TResult? Function(UpdateTemperature value)? updateTemperature, + TResult? Function(UpdateTopK value)? updateTopK, + TResult? Function(UpdateMaxTokens value)? updateMaxTokens, + TResult? Function(AddMessage value)? addMessage, + TResult? Function(ExtendMessage value)? extendMessage, + TResult? Function(CompleteResponse value)? completeResponse, + }) { + return updateTopK?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(CheckForModel value)? checkForModel, + TResult Function(DownloadModel value)? downloadModel, + TResult Function(SetPercentDownloaded value)? setPercentDownloaded, + TResult Function(DeleteModel value)? deleteModel, + TResult Function(InitEngine value)? initEngine, + TResult Function(InitializeModelInfo value)? initializeModelInfo, + TResult Function(UpdateTemperature value)? updateTemperature, + TResult Function(UpdateTopK value)? updateTopK, + TResult Function(UpdateMaxTokens value)? updateMaxTokens, + TResult Function(AddMessage value)? addMessage, + TResult Function(ExtendMessage value)? extendMessage, + TResult Function(CompleteResponse value)? completeResponse, + required TResult orElse(), + }) { + if (updateTopK != null) { + return updateTopK(this); + } + return orElse(); + } +} + +abstract class UpdateTopK implements TranscriptEvent { + const factory UpdateTopK(final int value) = _$UpdateTopKImpl; + + int get value; + @JsonKey(ignore: true) + _$$UpdateTopKImplCopyWith<_$UpdateTopKImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$UpdateMaxTokensImplCopyWith<$Res> { + factory _$$UpdateMaxTokensImplCopyWith(_$UpdateMaxTokensImpl value, + $Res Function(_$UpdateMaxTokensImpl) then) = + __$$UpdateMaxTokensImplCopyWithImpl<$Res>; + @useResult + $Res call({int value}); +} + +/// @nodoc +class __$$UpdateMaxTokensImplCopyWithImpl<$Res> + extends _$TranscriptEventCopyWithImpl<$Res, _$UpdateMaxTokensImpl> + implements _$$UpdateMaxTokensImplCopyWith<$Res> { + __$$UpdateMaxTokensImplCopyWithImpl( + _$UpdateMaxTokensImpl _value, $Res Function(_$UpdateMaxTokensImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? value = null, + }) { + return _then(_$UpdateMaxTokensImpl( + null == value + ? _value.value + : value // ignore: cast_nullable_to_non_nullable + as int, + )); + } +} + +/// @nodoc + +class _$UpdateMaxTokensImpl implements UpdateMaxTokens { + const _$UpdateMaxTokensImpl(this.value); + + @override + final int value; + + @override + String toString() { + return 'TranscriptEvent.updateMaxTokens(value: $value)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$UpdateMaxTokensImpl && + (identical(other.value, value) || other.value == value)); + } + + @override + int get hashCode => Object.hash(runtimeType, value); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$UpdateMaxTokensImplCopyWith<_$UpdateMaxTokensImpl> get copyWith => + __$$UpdateMaxTokensImplCopyWithImpl<_$UpdateMaxTokensImpl>( + this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(LlmModel model) checkForModel, + required TResult Function(LlmModel model) downloadModel, + required TResult Function(LlmModel model, int percentDownloaded) + setPercentDownloaded, + required TResult Function(LlmModel model) deleteModel, + required TResult Function(LlmModel model) initEngine, + required TResult Function() initializeModelInfo, + required TResult Function(double value) updateTemperature, + required TResult Function(int value) updateTopK, + required TResult Function(int value) updateMaxTokens, + required TResult Function(ChatMessage message, LlmModel model) addMessage, + required TResult Function( + String chunk, int index, LlmModel model, bool first, bool last) + extendMessage, + required TResult Function(LlmModel model) completeResponse, + }) { + return updateMaxTokens(value); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(LlmModel model)? checkForModel, + TResult? Function(LlmModel model)? downloadModel, + TResult? Function(LlmModel model, int percentDownloaded)? + setPercentDownloaded, + TResult? Function(LlmModel model)? deleteModel, + TResult? Function(LlmModel model)? initEngine, + TResult? Function()? initializeModelInfo, + TResult? Function(double value)? updateTemperature, + TResult? Function(int value)? updateTopK, + TResult? Function(int value)? updateMaxTokens, + TResult? Function(ChatMessage message, LlmModel model)? addMessage, + TResult? Function( + String chunk, int index, LlmModel model, bool first, bool last)? + extendMessage, + TResult? Function(LlmModel model)? completeResponse, + }) { + return updateMaxTokens?.call(value); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(LlmModel model)? checkForModel, + TResult Function(LlmModel model)? downloadModel, + TResult Function(LlmModel model, int percentDownloaded)? + setPercentDownloaded, + TResult Function(LlmModel model)? deleteModel, + TResult Function(LlmModel model)? initEngine, + TResult Function()? initializeModelInfo, + TResult Function(double value)? updateTemperature, + TResult Function(int value)? updateTopK, + TResult Function(int value)? updateMaxTokens, + TResult Function(ChatMessage message, LlmModel model)? addMessage, + TResult Function( + String chunk, int index, LlmModel model, bool first, bool last)? + extendMessage, + TResult Function(LlmModel model)? completeResponse, + required TResult orElse(), + }) { + if (updateMaxTokens != null) { + return updateMaxTokens(value); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(CheckForModel value) checkForModel, + required TResult Function(DownloadModel value) downloadModel, + required TResult Function(SetPercentDownloaded value) setPercentDownloaded, + required TResult Function(DeleteModel value) deleteModel, + required TResult Function(InitEngine value) initEngine, + required TResult Function(InitializeModelInfo value) initializeModelInfo, + required TResult Function(UpdateTemperature value) updateTemperature, + required TResult Function(UpdateTopK value) updateTopK, + required TResult Function(UpdateMaxTokens value) updateMaxTokens, + required TResult Function(AddMessage value) addMessage, + required TResult Function(ExtendMessage value) extendMessage, + required TResult Function(CompleteResponse value) completeResponse, + }) { + return updateMaxTokens(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(CheckForModel value)? checkForModel, + TResult? Function(DownloadModel value)? downloadModel, + TResult? Function(SetPercentDownloaded value)? setPercentDownloaded, + TResult? Function(DeleteModel value)? deleteModel, + TResult? Function(InitEngine value)? initEngine, + TResult? Function(InitializeModelInfo value)? initializeModelInfo, + TResult? Function(UpdateTemperature value)? updateTemperature, + TResult? Function(UpdateTopK value)? updateTopK, + TResult? Function(UpdateMaxTokens value)? updateMaxTokens, + TResult? Function(AddMessage value)? addMessage, + TResult? Function(ExtendMessage value)? extendMessage, + TResult? Function(CompleteResponse value)? completeResponse, + }) { + return updateMaxTokens?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(CheckForModel value)? checkForModel, + TResult Function(DownloadModel value)? downloadModel, + TResult Function(SetPercentDownloaded value)? setPercentDownloaded, + TResult Function(DeleteModel value)? deleteModel, + TResult Function(InitEngine value)? initEngine, + TResult Function(InitializeModelInfo value)? initializeModelInfo, + TResult Function(UpdateTemperature value)? updateTemperature, + TResult Function(UpdateTopK value)? updateTopK, + TResult Function(UpdateMaxTokens value)? updateMaxTokens, + TResult Function(AddMessage value)? addMessage, + TResult Function(ExtendMessage value)? extendMessage, + TResult Function(CompleteResponse value)? completeResponse, + required TResult orElse(), + }) { + if (updateMaxTokens != null) { + return updateMaxTokens(this); + } + return orElse(); + } +} + +abstract class UpdateMaxTokens implements TranscriptEvent { + const factory UpdateMaxTokens(final int value) = _$UpdateMaxTokensImpl; + + int get value; + @JsonKey(ignore: true) + _$$UpdateMaxTokensImplCopyWith<_$UpdateMaxTokensImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$AddMessageImplCopyWith<$Res> { + factory _$$AddMessageImplCopyWith( + _$AddMessageImpl value, $Res Function(_$AddMessageImpl) then) = + __$$AddMessageImplCopyWithImpl<$Res>; + @useResult + $Res call({ChatMessage message, LlmModel model}); + + $ChatMessageCopyWith<$Res> get message; +} + +/// @nodoc +class __$$AddMessageImplCopyWithImpl<$Res> + extends _$TranscriptEventCopyWithImpl<$Res, _$AddMessageImpl> + implements _$$AddMessageImplCopyWith<$Res> { + __$$AddMessageImplCopyWithImpl( + _$AddMessageImpl _value, $Res Function(_$AddMessageImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? message = null, + Object? model = null, + }) { + return _then(_$AddMessageImpl( + null == message + ? _value.message + : message // ignore: cast_nullable_to_non_nullable + as ChatMessage, + null == model + ? _value.model + : model // ignore: cast_nullable_to_non_nullable + as LlmModel, + )); + } + + @override + @pragma('vm:prefer-inline') + $ChatMessageCopyWith<$Res> get message { + return $ChatMessageCopyWith<$Res>(_value.message, (value) { + return _then(_value.copyWith(message: value)); + }); + } +} + +/// @nodoc + +class _$AddMessageImpl implements AddMessage { + const _$AddMessageImpl(this.message, this.model); + + @override + final ChatMessage message; + @override + final LlmModel model; + + @override + String toString() { + return 'TranscriptEvent.addMessage(message: $message, model: $model)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$AddMessageImpl && + (identical(other.message, message) || other.message == message) && + (identical(other.model, model) || other.model == model)); + } + + @override + int get hashCode => Object.hash(runtimeType, message, model); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$AddMessageImplCopyWith<_$AddMessageImpl> get copyWith => + __$$AddMessageImplCopyWithImpl<_$AddMessageImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(LlmModel model) checkForModel, + required TResult Function(LlmModel model) downloadModel, + required TResult Function(LlmModel model, int percentDownloaded) + setPercentDownloaded, + required TResult Function(LlmModel model) deleteModel, + required TResult Function(LlmModel model) initEngine, + required TResult Function() initializeModelInfo, + required TResult Function(double value) updateTemperature, + required TResult Function(int value) updateTopK, + required TResult Function(int value) updateMaxTokens, + required TResult Function(ChatMessage message, LlmModel model) addMessage, + required TResult Function( + String chunk, int index, LlmModel model, bool first, bool last) + extendMessage, + required TResult Function(LlmModel model) completeResponse, + }) { + return addMessage(message, model); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(LlmModel model)? checkForModel, + TResult? Function(LlmModel model)? downloadModel, + TResult? Function(LlmModel model, int percentDownloaded)? + setPercentDownloaded, + TResult? Function(LlmModel model)? deleteModel, + TResult? Function(LlmModel model)? initEngine, + TResult? Function()? initializeModelInfo, + TResult? Function(double value)? updateTemperature, + TResult? Function(int value)? updateTopK, + TResult? Function(int value)? updateMaxTokens, + TResult? Function(ChatMessage message, LlmModel model)? addMessage, + TResult? Function( + String chunk, int index, LlmModel model, bool first, bool last)? + extendMessage, + TResult? Function(LlmModel model)? completeResponse, + }) { + return addMessage?.call(message, model); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(LlmModel model)? checkForModel, + TResult Function(LlmModel model)? downloadModel, + TResult Function(LlmModel model, int percentDownloaded)? + setPercentDownloaded, + TResult Function(LlmModel model)? deleteModel, + TResult Function(LlmModel model)? initEngine, + TResult Function()? initializeModelInfo, + TResult Function(double value)? updateTemperature, + TResult Function(int value)? updateTopK, + TResult Function(int value)? updateMaxTokens, + TResult Function(ChatMessage message, LlmModel model)? addMessage, + TResult Function( + String chunk, int index, LlmModel model, bool first, bool last)? + extendMessage, + TResult Function(LlmModel model)? completeResponse, + required TResult orElse(), + }) { + if (addMessage != null) { + return addMessage(message, model); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(CheckForModel value) checkForModel, + required TResult Function(DownloadModel value) downloadModel, + required TResult Function(SetPercentDownloaded value) setPercentDownloaded, + required TResult Function(DeleteModel value) deleteModel, + required TResult Function(InitEngine value) initEngine, + required TResult Function(InitializeModelInfo value) initializeModelInfo, + required TResult Function(UpdateTemperature value) updateTemperature, + required TResult Function(UpdateTopK value) updateTopK, + required TResult Function(UpdateMaxTokens value) updateMaxTokens, + required TResult Function(AddMessage value) addMessage, + required TResult Function(ExtendMessage value) extendMessage, + required TResult Function(CompleteResponse value) completeResponse, + }) { + return addMessage(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(CheckForModel value)? checkForModel, + TResult? Function(DownloadModel value)? downloadModel, + TResult? Function(SetPercentDownloaded value)? setPercentDownloaded, + TResult? Function(DeleteModel value)? deleteModel, + TResult? Function(InitEngine value)? initEngine, + TResult? Function(InitializeModelInfo value)? initializeModelInfo, + TResult? Function(UpdateTemperature value)? updateTemperature, + TResult? Function(UpdateTopK value)? updateTopK, + TResult? Function(UpdateMaxTokens value)? updateMaxTokens, + TResult? Function(AddMessage value)? addMessage, + TResult? Function(ExtendMessage value)? extendMessage, + TResult? Function(CompleteResponse value)? completeResponse, + }) { + return addMessage?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(CheckForModel value)? checkForModel, + TResult Function(DownloadModel value)? downloadModel, + TResult Function(SetPercentDownloaded value)? setPercentDownloaded, + TResult Function(DeleteModel value)? deleteModel, + TResult Function(InitEngine value)? initEngine, + TResult Function(InitializeModelInfo value)? initializeModelInfo, + TResult Function(UpdateTemperature value)? updateTemperature, + TResult Function(UpdateTopK value)? updateTopK, + TResult Function(UpdateMaxTokens value)? updateMaxTokens, + TResult Function(AddMessage value)? addMessage, + TResult Function(ExtendMessage value)? extendMessage, + TResult Function(CompleteResponse value)? completeResponse, + required TResult orElse(), + }) { + if (addMessage != null) { + return addMessage(this); + } + return orElse(); + } +} + +abstract class AddMessage implements TranscriptEvent { + const factory AddMessage(final ChatMessage message, final LlmModel model) = + _$AddMessageImpl; + + ChatMessage get message; + LlmModel get model; + @JsonKey(ignore: true) + _$$AddMessageImplCopyWith<_$AddMessageImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$ExtendMessageImplCopyWith<$Res> { + factory _$$ExtendMessageImplCopyWith( + _$ExtendMessageImpl value, $Res Function(_$ExtendMessageImpl) then) = + __$$ExtendMessageImplCopyWithImpl<$Res>; + @useResult + $Res call({String chunk, int index, LlmModel model, bool first, bool last}); +} + +/// @nodoc +class __$$ExtendMessageImplCopyWithImpl<$Res> + extends _$TranscriptEventCopyWithImpl<$Res, _$ExtendMessageImpl> + implements _$$ExtendMessageImplCopyWith<$Res> { + __$$ExtendMessageImplCopyWithImpl( + _$ExtendMessageImpl _value, $Res Function(_$ExtendMessageImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? chunk = null, + Object? index = null, + Object? model = null, + Object? first = null, + Object? last = null, + }) { + return _then(_$ExtendMessageImpl( + chunk: null == chunk + ? _value.chunk + : chunk // ignore: cast_nullable_to_non_nullable + as String, + index: null == index + ? _value.index + : index // ignore: cast_nullable_to_non_nullable + as int, + model: null == model + ? _value.model + : model // ignore: cast_nullable_to_non_nullable + as LlmModel, + first: null == first + ? _value.first + : first // ignore: cast_nullable_to_non_nullable + as bool, + last: null == last + ? _value.last + : last // ignore: cast_nullable_to_non_nullable + as bool, + )); + } +} + +/// @nodoc + +class _$ExtendMessageImpl implements ExtendMessage { + const _$ExtendMessageImpl( + {required this.chunk, + required this.index, + required this.model, + required this.first, + required this.last}); + + @override + final String chunk; + @override + final int index; + @override + final LlmModel model; + @override + final bool first; + @override + final bool last; + + @override + String toString() { + return 'TranscriptEvent.extendMessage(chunk: $chunk, index: $index, model: $model, first: $first, last: $last)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ExtendMessageImpl && + (identical(other.chunk, chunk) || other.chunk == chunk) && + (identical(other.index, index) || other.index == index) && + (identical(other.model, model) || other.model == model) && + (identical(other.first, first) || other.first == first) && + (identical(other.last, last) || other.last == last)); + } + + @override + int get hashCode => + Object.hash(runtimeType, chunk, index, model, first, last); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$ExtendMessageImplCopyWith<_$ExtendMessageImpl> get copyWith => + __$$ExtendMessageImplCopyWithImpl<_$ExtendMessageImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(LlmModel model) checkForModel, + required TResult Function(LlmModel model) downloadModel, + required TResult Function(LlmModel model, int percentDownloaded) + setPercentDownloaded, + required TResult Function(LlmModel model) deleteModel, + required TResult Function(LlmModel model) initEngine, + required TResult Function() initializeModelInfo, + required TResult Function(double value) updateTemperature, + required TResult Function(int value) updateTopK, + required TResult Function(int value) updateMaxTokens, + required TResult Function(ChatMessage message, LlmModel model) addMessage, + required TResult Function( + String chunk, int index, LlmModel model, bool first, bool last) + extendMessage, + required TResult Function(LlmModel model) completeResponse, + }) { + return extendMessage(chunk, index, model, first, last); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(LlmModel model)? checkForModel, + TResult? Function(LlmModel model)? downloadModel, + TResult? Function(LlmModel model, int percentDownloaded)? + setPercentDownloaded, + TResult? Function(LlmModel model)? deleteModel, + TResult? Function(LlmModel model)? initEngine, + TResult? Function()? initializeModelInfo, + TResult? Function(double value)? updateTemperature, + TResult? Function(int value)? updateTopK, + TResult? Function(int value)? updateMaxTokens, + TResult? Function(ChatMessage message, LlmModel model)? addMessage, + TResult? Function( + String chunk, int index, LlmModel model, bool first, bool last)? + extendMessage, + TResult? Function(LlmModel model)? completeResponse, + }) { + return extendMessage?.call(chunk, index, model, first, last); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(LlmModel model)? checkForModel, + TResult Function(LlmModel model)? downloadModel, + TResult Function(LlmModel model, int percentDownloaded)? + setPercentDownloaded, + TResult Function(LlmModel model)? deleteModel, + TResult Function(LlmModel model)? initEngine, + TResult Function()? initializeModelInfo, + TResult Function(double value)? updateTemperature, + TResult Function(int value)? updateTopK, + TResult Function(int value)? updateMaxTokens, + TResult Function(ChatMessage message, LlmModel model)? addMessage, + TResult Function( + String chunk, int index, LlmModel model, bool first, bool last)? + extendMessage, + TResult Function(LlmModel model)? completeResponse, + required TResult orElse(), + }) { + if (extendMessage != null) { + return extendMessage(chunk, index, model, first, last); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(CheckForModel value) checkForModel, + required TResult Function(DownloadModel value) downloadModel, + required TResult Function(SetPercentDownloaded value) setPercentDownloaded, + required TResult Function(DeleteModel value) deleteModel, + required TResult Function(InitEngine value) initEngine, + required TResult Function(InitializeModelInfo value) initializeModelInfo, + required TResult Function(UpdateTemperature value) updateTemperature, + required TResult Function(UpdateTopK value) updateTopK, + required TResult Function(UpdateMaxTokens value) updateMaxTokens, + required TResult Function(AddMessage value) addMessage, + required TResult Function(ExtendMessage value) extendMessage, + required TResult Function(CompleteResponse value) completeResponse, + }) { + return extendMessage(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(CheckForModel value)? checkForModel, + TResult? Function(DownloadModel value)? downloadModel, + TResult? Function(SetPercentDownloaded value)? setPercentDownloaded, + TResult? Function(DeleteModel value)? deleteModel, + TResult? Function(InitEngine value)? initEngine, + TResult? Function(InitializeModelInfo value)? initializeModelInfo, + TResult? Function(UpdateTemperature value)? updateTemperature, + TResult? Function(UpdateTopK value)? updateTopK, + TResult? Function(UpdateMaxTokens value)? updateMaxTokens, + TResult? Function(AddMessage value)? addMessage, + TResult? Function(ExtendMessage value)? extendMessage, + TResult? Function(CompleteResponse value)? completeResponse, + }) { + return extendMessage?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(CheckForModel value)? checkForModel, + TResult Function(DownloadModel value)? downloadModel, + TResult Function(SetPercentDownloaded value)? setPercentDownloaded, + TResult Function(DeleteModel value)? deleteModel, + TResult Function(InitEngine value)? initEngine, + TResult Function(InitializeModelInfo value)? initializeModelInfo, + TResult Function(UpdateTemperature value)? updateTemperature, + TResult Function(UpdateTopK value)? updateTopK, + TResult Function(UpdateMaxTokens value)? updateMaxTokens, + TResult Function(AddMessage value)? addMessage, + TResult Function(ExtendMessage value)? extendMessage, + TResult Function(CompleteResponse value)? completeResponse, + required TResult orElse(), + }) { + if (extendMessage != null) { + return extendMessage(this); + } + return orElse(); + } +} + +abstract class ExtendMessage implements TranscriptEvent { + const factory ExtendMessage( + {required final String chunk, + required final int index, + required final LlmModel model, + required final bool first, + required final bool last}) = _$ExtendMessageImpl; + + String get chunk; + int get index; + LlmModel get model; + bool get first; + bool get last; + @JsonKey(ignore: true) + _$$ExtendMessageImplCopyWith<_$ExtendMessageImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$CompleteResponseImplCopyWith<$Res> { + factory _$$CompleteResponseImplCopyWith(_$CompleteResponseImpl value, + $Res Function(_$CompleteResponseImpl) then) = + __$$CompleteResponseImplCopyWithImpl<$Res>; + @useResult + $Res call({LlmModel model}); +} + +/// @nodoc +class __$$CompleteResponseImplCopyWithImpl<$Res> + extends _$TranscriptEventCopyWithImpl<$Res, _$CompleteResponseImpl> + implements _$$CompleteResponseImplCopyWith<$Res> { + __$$CompleteResponseImplCopyWithImpl(_$CompleteResponseImpl _value, + $Res Function(_$CompleteResponseImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? model = null, + }) { + return _then(_$CompleteResponseImpl( + null == model + ? _value.model + : model // ignore: cast_nullable_to_non_nullable + as LlmModel, + )); + } +} + +/// @nodoc + +class _$CompleteResponseImpl implements CompleteResponse { + const _$CompleteResponseImpl(this.model); + + @override + final LlmModel model; + + @override + String toString() { + return 'TranscriptEvent.completeResponse(model: $model)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$CompleteResponseImpl && + (identical(other.model, model) || other.model == model)); + } + + @override + int get hashCode => Object.hash(runtimeType, model); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$CompleteResponseImplCopyWith<_$CompleteResponseImpl> get copyWith => + __$$CompleteResponseImplCopyWithImpl<_$CompleteResponseImpl>( + this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(LlmModel model) checkForModel, + required TResult Function(LlmModel model) downloadModel, + required TResult Function(LlmModel model, int percentDownloaded) + setPercentDownloaded, + required TResult Function(LlmModel model) deleteModel, + required TResult Function(LlmModel model) initEngine, + required TResult Function() initializeModelInfo, + required TResult Function(double value) updateTemperature, + required TResult Function(int value) updateTopK, + required TResult Function(int value) updateMaxTokens, + required TResult Function(ChatMessage message, LlmModel model) addMessage, + required TResult Function( + String chunk, int index, LlmModel model, bool first, bool last) + extendMessage, + required TResult Function(LlmModel model) completeResponse, + }) { + return completeResponse(model); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(LlmModel model)? checkForModel, + TResult? Function(LlmModel model)? downloadModel, + TResult? Function(LlmModel model, int percentDownloaded)? + setPercentDownloaded, + TResult? Function(LlmModel model)? deleteModel, + TResult? Function(LlmModel model)? initEngine, + TResult? Function()? initializeModelInfo, + TResult? Function(double value)? updateTemperature, + TResult? Function(int value)? updateTopK, + TResult? Function(int value)? updateMaxTokens, + TResult? Function(ChatMessage message, LlmModel model)? addMessage, + TResult? Function( + String chunk, int index, LlmModel model, bool first, bool last)? + extendMessage, + TResult? Function(LlmModel model)? completeResponse, + }) { + return completeResponse?.call(model); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(LlmModel model)? checkForModel, + TResult Function(LlmModel model)? downloadModel, + TResult Function(LlmModel model, int percentDownloaded)? + setPercentDownloaded, + TResult Function(LlmModel model)? deleteModel, + TResult Function(LlmModel model)? initEngine, + TResult Function()? initializeModelInfo, + TResult Function(double value)? updateTemperature, + TResult Function(int value)? updateTopK, + TResult Function(int value)? updateMaxTokens, + TResult Function(ChatMessage message, LlmModel model)? addMessage, + TResult Function( + String chunk, int index, LlmModel model, bool first, bool last)? + extendMessage, + TResult Function(LlmModel model)? completeResponse, + required TResult orElse(), + }) { + if (completeResponse != null) { + return completeResponse(model); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(CheckForModel value) checkForModel, + required TResult Function(DownloadModel value) downloadModel, + required TResult Function(SetPercentDownloaded value) setPercentDownloaded, + required TResult Function(DeleteModel value) deleteModel, + required TResult Function(InitEngine value) initEngine, + required TResult Function(InitializeModelInfo value) initializeModelInfo, + required TResult Function(UpdateTemperature value) updateTemperature, + required TResult Function(UpdateTopK value) updateTopK, + required TResult Function(UpdateMaxTokens value) updateMaxTokens, + required TResult Function(AddMessage value) addMessage, + required TResult Function(ExtendMessage value) extendMessage, + required TResult Function(CompleteResponse value) completeResponse, + }) { + return completeResponse(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(CheckForModel value)? checkForModel, + TResult? Function(DownloadModel value)? downloadModel, + TResult? Function(SetPercentDownloaded value)? setPercentDownloaded, + TResult? Function(DeleteModel value)? deleteModel, + TResult? Function(InitEngine value)? initEngine, + TResult? Function(InitializeModelInfo value)? initializeModelInfo, + TResult? Function(UpdateTemperature value)? updateTemperature, + TResult? Function(UpdateTopK value)? updateTopK, + TResult? Function(UpdateMaxTokens value)? updateMaxTokens, + TResult? Function(AddMessage value)? addMessage, + TResult? Function(ExtendMessage value)? extendMessage, + TResult? Function(CompleteResponse value)? completeResponse, + }) { + return completeResponse?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(CheckForModel value)? checkForModel, + TResult Function(DownloadModel value)? downloadModel, + TResult Function(SetPercentDownloaded value)? setPercentDownloaded, + TResult Function(DeleteModel value)? deleteModel, + TResult Function(InitEngine value)? initEngine, + TResult Function(InitializeModelInfo value)? initializeModelInfo, + TResult Function(UpdateTemperature value)? updateTemperature, + TResult Function(UpdateTopK value)? updateTopK, + TResult Function(UpdateMaxTokens value)? updateMaxTokens, + TResult Function(AddMessage value)? addMessage, + TResult Function(ExtendMessage value)? extendMessage, + TResult Function(CompleteResponse value)? completeResponse, + required TResult orElse(), + }) { + if (completeResponse != null) { + return completeResponse(this); + } + return orElse(); + } +} + +abstract class CompleteResponse implements TranscriptEvent { + const factory CompleteResponse(final LlmModel model) = _$CompleteResponseImpl; + + LlmModel get model; + @JsonKey(ignore: true) + _$$CompleteResponseImplCopyWith<_$CompleteResponseImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/packages/mediapipe-task-genai/example/lib/fake_inference_engine.dart b/packages/mediapipe-task-genai/example/lib/fake_inference_engine.dart new file mode 100644 index 00000000..83be0496 --- /dev/null +++ b/packages/mediapipe-task-genai/example/lib/fake_inference_engine.dart @@ -0,0 +1,72 @@ +// // Copyright 2014 The Flutter Authors. All rights reserved. +// // Use of this source code is governed by a BSD-style license that can be +// // found in the LICENSE file. + +// import 'dart:async'; +// import 'dart:ffi'; +// import 'dart:math'; + +// import 'package:logging/logging.dart'; +// import 'package:mediapipe_genai/mediapipe_genai.dart'; + +// final _log = Logger('FakeInferenceEngine'); + +// class FakeInferenceEngine implements LlmInferenceEngine { +// FakeInferenceEngine(LlmInferenceOptions options) { +// _log.info('Initializing FakeInferenceEngine with $options'); +// } + +// @override +// Stream generateResponse(String text) { +// final controller = StreamController.broadcast(); + +// final rnd = Random(); +// // Delay between 500 and 1000ms +// Future.delayed(Duration(milliseconds: rnd.nextInt(500) + 500)).then( +// (_) async { +// final List message = +// _genericResponses[rnd.nextInt(_genericResponses.length)]; + +// for (final chunk in message) { +// // Delay between 500 and 1000ms +// await Future.delayed(Duration(milliseconds: rnd.nextInt(500) + 500)); +// controller.add(chunk); +// } +// controller.close(); +// }, +// ); + +// return controller.stream; +// } + +// @override +// Future sizeInTokens(String text) async => +// (text.split(' ').length * 1.5).toInt(); + +// // These three methods, `cancel`, `dispose`, and `handleErrorMessage` serve +// // no purpose whatsoever for this fake implementation and should not be +// // necessary to implement here; but surprisingly the type system won't compile +// // if they aren't present. Strangely, the analysis server does not report any +// // issue, so the (false positive, I believe) error only arises when you run +// // the actual app. +// void cancel() => throw UnimplementedError(); + +// @override +// void dispose() => throw UnimplementedError(); + +// void handleErrorMessage(Pointer> errorMessage, [int? status]) => +// throw UnimplementedError(); +// } + +// const _genericResponses = >[ +// ['Hello', 'my good', 'friend!', 'How are you doing?', 'Are you well?'], +// ['Well now, don\'t', 'get so hasty! There are still reasonable', 'options.'], +// ['From what I\'m hearing,', 'it sounds like you need to dance more.'], +// [ +// 'I too enjoy a nice sandwich,', +// 'but you\'ve gotta', +// 'toast the bread - come', +// 'on!', +// ], +// ['I don\'t appreciate that slander and', 'I own\'t', 'stand for it!'], +// ]; diff --git a/packages/mediapipe-task-genai/example/lib/llm_inference_demo.dart b/packages/mediapipe-task-genai/example/lib/llm_inference_demo.dart new file mode 100644 index 00000000..421418f4 --- /dev/null +++ b/packages/mediapipe-task-genai/example/lib/llm_inference_demo.dart @@ -0,0 +1,94 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:example/bloc.dart'; +import 'package:example/model_selection_screen.dart'; +import 'package:example/models/models.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:mediapipe_genai/mediapipe_genai.dart'; +// import 'fake_inference_engine.dart'; +import 'widgets/widgets.dart'; + +class LlmInferenceDemo extends StatefulWidget { + const LlmInferenceDemo({super.key}); + + @override + State createState() => _LlmInferenceDemoState(); +} + +class _LlmInferenceDemoState extends State + with AutomaticKeepAliveClientMixin { + final results = []; + + late TranscriptBloc bloc; + + @override + void initState() { + super.initState(); + bloc = TranscriptBloc( + engineBuilder: LlmInferenceEngine.new, + // engineBuilder: FakeInferenceEngine.new, + ); + } + + @override + Widget build(BuildContext context) { + super.build(context); + + return MultiBlocListener( + listeners: [ + // Error listener + BlocListener( + bloc: bloc, + listener: (context, TranscriptState state) { + if (state.error != null) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(state.error!)), + ); + } + }, + ), + ], + child: BlocBuilder( + bloc: bloc, + builder: (context, TranscriptState state) { + return Scaffold( + appBar: AppBar(title: const Text('Inference')), + body: SafeArea( + child: ModelSelectionScreen( + modelsReady: state.modelsReady, + deleteModel: (LlmModel model) => bloc.add(DeleteModel(model)), + downloadModel: (LlmModel model) => + bloc.add(DownloadModel(model)), + modelInfoMap: state.modelInfoMap, + selectModel: (LlmModel model) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => ChatScreen( + bloc, + model: model, + key: ValueKey(model.displayName), + ), + ), + ); + }, + ), + ), + ); + }, + ), + ); + } + + @override + bool get wantKeepAlive => true; + + @override + void dispose() { + bloc.close(); + super.dispose(); + } +} diff --git a/packages/mediapipe-task-genai/example/lib/logging.dart b/packages/mediapipe-task-genai/example/lib/logging.dart new file mode 100644 index 00000000..b07a9179 --- /dev/null +++ b/packages/mediapipe-task-genai/example/lib/logging.dart @@ -0,0 +1,21 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:io' as io; +import 'package:logging/logging.dart'; + +final log = Logger('Genai'); + +void initLogging() { + Logger.root.level = Level.FINEST; + Logger.root.onRecord.listen((record) { + io.stdout.writeln('${record.level.name} [${record.loggerName}]' + '[' + '${record.time.hour.toString()}:' + '${record.time.minute.toString().padLeft(2, "0")}:' + '${record.time.second.toString().padLeft(2, "0")}.' + '${record.time.millisecond.toString().padRight(3, "0")}' + '] ${record.message}'); + }); +} diff --git a/packages/mediapipe-task-genai/example/lib/main.dart b/packages/mediapipe-task-genai/example/lib/main.dart new file mode 100644 index 00000000..6b27396a --- /dev/null +++ b/packages/mediapipe-task-genai/example/lib/main.dart @@ -0,0 +1,119 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:logging/logging.dart'; +import 'llm_inference_demo.dart'; +import 'logging.dart'; + +/// {@template AppBlocObserver} +/// Logger of all things related to `pkg:flutter_bloc`. +/// {@endtemplate} +class AppBlocObserver extends BlocObserver { + /// {@macro AppBlocObserver} + const AppBlocObserver(); + + static final _log = Logger('AppBlocObserver'); + + @override + void onChange(BlocBase bloc, Change change) { + _log.finer('onChange(${bloc.runtimeType}, $change)'); + super.onChange(bloc, change); + } + + @override + void onEvent(Bloc bloc, Object? event) { + _log.finer('onEvent($event)'); + super.onEvent(bloc, event); + } + + @override + void onError(BlocBase bloc, Object error, StackTrace stackTrace) { + // print('onError(${bloc.runtimeType}, $error, $stackTrace)'); + super.onError(bloc, error, stackTrace); + } +} + +void main() { + initLogging(); + Bloc.observer = const AppBlocObserver(); + runApp( + const MaterialApp( + localizationsDelegates: [ + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, // This is required + ], + home: MainApp(), + ), + ); +} + +class MainApp extends StatefulWidget { + const MainApp({super.key}); + + @override + State createState() => _MainAppState(); +} + +class _MainAppState extends State { + final PageController controller = PageController(); + + final titles = ['Inference']; + int titleIndex = 0; + + void switchToPage(int index) { + controller.animateToPage( + index, + duration: const Duration(milliseconds: 200), + curve: Curves.easeOut, + ); + setState(() { + titleIndex = index; + }); + } + + @override + Widget build(BuildContext context) { + const activeTextStyle = TextStyle( + fontWeight: FontWeight.bold, + color: Colors.orange, + ); + const inactiveTextStyle = TextStyle( + color: Colors.white, + ); + return Scaffold( + body: PageView( + controller: controller, + children: const [ + LlmInferenceDemo(), + ], + ), + bottomNavigationBar: SizedBox( + height: 50 + MediaQuery.of(context).viewPadding.bottom / 2, + child: ColoredBox( + color: Colors.blueGrey, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + TextButton( + onPressed: () => switchToPage(0), + child: Text( + 'Inference', + style: + titleIndex == 0 ? activeTextStyle : inactiveTextStyle, + ), + ), + ], + ), + SizedBox( + height: MediaQuery.of(context).viewPadding.bottom / 2, + ), + ], + ), + ), + ), + ); + } +} diff --git a/packages/mediapipe-task-genai/example/lib/model_location_provider.dart b/packages/mediapipe-task-genai/example/lib/model_location_provider.dart new file mode 100644 index 00000000..50f31c4d --- /dev/null +++ b/packages/mediapipe-task-genai/example/lib/model_location_provider.dart @@ -0,0 +1,243 @@ +import 'dart:async'; +import 'dart:io'; +import 'package:example/models/models.dart'; +import 'package:example/model_storage/model_storage.dart'; +import 'package:http/http.dart' as http; +import 'package:logging/logging.dart'; + +final _log = Logger('ModelLocationProvider'); + +/// Provides the locations of a given [LlmModel]. +/// +/// There are two constructors, but [fromEnvironment] is expected to be the most +/// useful assuming the Flutter application was build with the +/// `--dart-define=GEMMA_8B_GPU_PATH=` flags, or similar, depending +/// on which models the developer and user intend to use. +/// +/// Usage: +/// ```dart +/// final provider = ModelLocationProvider.fromEnvironment(); +/// (Future locationFuture, Stream downloadProgress,) = +/// await provider.getModelLocation(LlmModel.gemma8bGpu); +/// if (downloadProgress != null) { +/// await for (final percent in downloadProgress) { +/// showDownloadPercentage(percent); +/// } +/// } +/// // Should complete instantly +/// final location = await locationFuture; +/// ``` +/// +/// See also: +/// * [LlmModel], the enum which tracks each available LLM. +class ModelLocationProvider { + ModelLocationProvider._({required ModelPaths modelLocations}) { + storage = ModelStorage() + ..setInitialModelLocations(modelLocations).then( + (_) { + _ready.complete(); + }, + ); + } + factory ModelLocationProvider.fromEnvironment() { + return ModelLocationProvider._( + modelLocations: ModelLocationProvider._getModelLocationsFromEnvironment(), + ); + } + + late final ModelStorageInterface storage; + + final _ready = Completer(); + Future get ready => _ready.future; + + // Useful for quick development if there is any friction around passing + // environment variables + static const hardcodedLocations = { + LlmModel.gemma4bCpu: + 'https://storage.googleapis.com/random-storage-asdf/gemma/gemma-2b-it-cpu-int4.bin', + LlmModel.gemma4bGpu: + 'https://storage.googleapis.com/random-storage-asdf/gemma/gemma-2b-it-gpu-int4.bin', + LlmModel.gemma8bCpu: + 'https://storage.googleapis.com/random-storage-asdf/gemma/gemma-2b-it-cpu-int8.bin', + LlmModel.gemma8bGpu: + 'https://storage.googleapis.com/random-storage-asdf/gemma/gemma-2b-it-gpu-int8.bin', + }; + + static ModelPaths _getModelLocationsFromEnvironment() { + final locations = {}; + for (final model in LlmModel.values) { + String location = hardcodedLocations[model] ?? + Platform.environment[model.environmentVariableUriName] ?? + model.dartDefine; + + // `model.dartDefine` has an empty state of an empty string, not null, + // which is why `location` is a `String` and not a `String?` + if (location.isNotEmpty) { + locations[model] = location; + } + } + return locations; + } + + /// {@macro downloadExists} + Future downloadExists(String downloadLocation) => + storage.downloadExists(downloadLocation); + + Future downloadExistsForModel(LlmModel model) async { + String? path = storage.pathFor(model); + return path == null ? false : storage.downloadExists(path); + } + + Future delete(LlmModel model) async => await storage.delete(model); + + /// {@macro binarySize} + int binarySize(String location) => storage.binarySize(location); + + /// Accepts a model and returns the size on disk for said model if it is + /// already downloaded and fully available. + int? binarySizeForModel(LlmModel model) { + final location = pathFor(model); + return location != null ? storage.binarySize(location) : null; + } + + /// {@macro pathFor} + String? pathFor(LlmModel model) => storage.pathFor(model); + + /// {@macro urlFor} + Uri? urlFor(LlmModel model) => storage.urlFor(model); + + /// Storage for the controllers tied to in-progress downloads, each of which + /// should emit download progress updates as a percentage. + final _downloadControllers = >{}; + + /// Asychronously returns the String for the location of a locally available + /// copy of the requested model and an optional download progress stream if + /// the model is downloading. + Future<(Future, Stream?)> getModelLocation( + LlmModel model, + ) async { + await ready; + final path = storage.pathFor(model); + if (path != null) { + if (!await storage.downloadExists(path)) { + throw Exception( + 'Location $path for $model was expected to have a file, but it is ' + 'missing.', + ); + } + + final binarySize = storage.binarySize(path); + if (binarySize > 0) { + _log.finer( + 'Returning already downloaded model for $model of ' + '$binarySize bytes', + ); + return (Future.value(path), null); + } + _log.finer('Deleting existing $model of 0 bytes'); + storage.delete(model); + } + + final url = storage.urlFor(model); + if (url != null) { + return _getOrStartDownload(model, url); + } + throw Exception( + 'ModelLocationProvider does not know where to find a ' + '${model.name} model, either on disk or elsewhere. Did you include a ' + 'clause like `${model.environmentVariableUriName}=` before ' + '`flutter run`? See the example README for more details.', + ); + } + + /// Either initiates a fresh download or returns an ongoing download. + Future<(Future, Stream?)> _getOrStartDownload( + LlmModel model, + Uri url, + ) async { + final downloadDestination = await storage.urlToDownloadDestination(url); + + // This is the way to figure out if the file is still being downloaded. + // If there is a download controller for the model, we must await its + // completion. + if (!_downloadControllers.containsKey(model)) { + return ( + Future.value(downloadDestination), + await _downloadFile(model, url, downloadDestination) + ); + } + return ( + Future.value(downloadDestination), + _downloadControllers[model]!.stream + ); + } + + /// Downloads the file, creating and updating a stream with progress. + /// + /// This method does not return the location of the file, and in fact should + /// not be called directly. Call [getModelLocation], which makes use of + /// [_getOrStartDownload] if the file is not yet available on disk. + Future> _downloadFile( + LlmModel model, + Uri location, + String downloadDestination, + ) async { + // Prepare a place for the file to be downloaded. + if (await storage.downloadExists(downloadDestination)) { + throw Exception('File exists at LLM model location in _downloadFile, ' + 'which expects to only be called when said model location is empty. ' + 'Unexpectedly occupied file location was ${location.path}'); + } + final downloadSink = await storage.create(downloadDestination); + + // Setup the request itself and read preliminary headers. + final request = http.Request('GET', location); + final response = await request.send(); + final contentLength = int.parse(response.headers['content-length']!); + _log.finer('File download: $contentLength bytes'); + final downloadCompleter = Completer(); + int downloadedBytes = 0; + int lastPercentEmitted = 0; + + // Setup our reporting stream. Use a broadcast stream because calls to + // `_getOrStartDownload` will need to resubscribe if the file is already + // being downloaded. + _downloadControllers[model] = StreamController.broadcast(); + + // Actually begin downloading + response.stream.listen( + (List bytes) { + downloadSink.add(bytes); + downloadedBytes += bytes.length; + int percent = ((downloadedBytes / contentLength) * 100).toInt(); + if ((percent > lastPercentEmitted)) { + _log.finest('ModelLocationProvider :: $percent%'); + _downloadControllers[model]!.add(percent); + lastPercentEmitted = percent; + } + }, + onDone: () => downloadCompleter.complete(true), + onError: (error, stacktrace) { + _log.shout('error: $error'); + _log.shout('stacktrace: $stacktrace'); + downloadCompleter.complete(false); + }, + ); + downloadCompleter.future.then((downloadSuccessful) async { + if (downloadSuccessful) { + // Setting this value marks the model binary as fully downloaded. + storage.setPathCache(model, downloadDestination); + storage.close(downloadDestination); + } else { + // If the download did not complete, remove a partial download + storage.abort(downloadDestination); + } + // Clean-up resources + // Closing this controller is what signals to outside code that the file + // download is complete. + _downloadControllers[model]?.close(); + _downloadControllers.remove(model); + }); + return _downloadControllers[model]!.stream; + } +} diff --git a/packages/mediapipe-task-genai/example/lib/model_selection_screen.dart b/packages/mediapipe-task-genai/example/lib/model_selection_screen.dart new file mode 100644 index 00000000..c1b79814 --- /dev/null +++ b/packages/mediapipe-task-genai/example/lib/model_selection_screen.dart @@ -0,0 +1,223 @@ +// import 'package:example/bloc.dart'; +import 'dart:math'; + +import 'package:adaptive_dialog/adaptive_dialog.dart'; +import 'package:example/models/llm_model.dart'; +import 'package:flutter/material.dart'; +import 'package:getwidget/getwidget.dart'; + +class ModelSelectionScreen extends StatelessWidget { + const ModelSelectionScreen({ + required this.deleteModel, + required this.downloadModel, + required this.modelInfoMap, + required this.modelsReady, + required this.selectModel, + super.key, + }); + + // final TranscriptBloc bloc; + final Map modelInfoMap; + final bool modelsReady; + + /// Handler to delete the selected model. + final Function(LlmModel) deleteModel; + + /// Handler to downloaded the selected model. + final Function(LlmModel) downloadModel; + + /// Handler to change the selected model. + final Function(LlmModel) selectModel; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(8.0), + child: ListView( + // scrollDirection: Axis.horizontal, + children: [ + _modelSelectionTile(LlmModel.gemma4bCpu), + _modelSelectionTile(LlmModel.gemma4bGpu), + _modelSelectionTile(LlmModel.gemma8bCpu), + _modelSelectionTile(LlmModel.gemma8bGpu), + ], + ), + ); + } + + Widget _modelSelectionTile(LlmModel model) { + return ModelSelectionTile( + model, + ready: modelsReady, + modelInfo: modelInfoMap[model]!, + selectModel: () => selectModel(model), + delete: () => deleteModel(model), + download: () => downloadModel(model), + ); + } +} + +class ModelSelectionTile extends StatelessWidget { + const ModelSelectionTile( + this.model, { + required this.delete, + required this.download, + required this.modelInfo, + required this.ready, + required this.selectModel, + super.key, + }); + + final LlmModel model; + final ModelInfo modelInfo; + + /// False until the initial state of models is fully loaded. Do not render + /// warnings until this is true. + final bool ready; + + /// Handler to change the selected model to [model]. + final VoidCallback selectModel; + + /// Handler to downloaded [model]. + final VoidCallback download; + + /// Handler to delete [model]. + final VoidCallback delete; + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: _handleTap, + child: Card( + child: ListTile( + title: Text( + model.displayName, + // style: const TextStyle(fontSize: 12), + ), + leading: switch (modelInfo.state) { + ModelState.downloaded => + const Icon(Icons.check, color: Colors.green), + ModelState.downloading => const Icon(Icons.downloading), + ModelState.empty => SizedBox.fromSize(size: const Size.square(1)), + }, + subtitle: switch (modelInfo.state) { + ModelState.downloaded => Text( + humanize(modelInfo.downloadedBytes!), + style: TextStyle(fontSize: 11, color: Colors.grey[600]!), + ), + ModelState.downloading => + _DownloadingBar(downloadPercent: modelInfo.downloadPercent!), + ModelState.empty => ready && modelInfo.remoteLocation == null + ? const Text('Configure URL to download', + style: TextStyle(color: Colors.red)) + : Container(), // const Icon(Icons.download), + }, + trailing: GestureDetector( + onTap: () => _launchModal(context), + child: switch (modelInfo.state) { + ModelState.downloaded => const Icon(Icons.delete), + ModelState.downloading => + SizedBox.fromSize(size: const Size.square(1)), + ModelState.empty => modelInfo.remoteLocation == null + ? SizedBox.fromSize(size: const Size.square(1)) + : const Icon(Icons.download), + }, + ), + ), + ), + ); + } + + // Responds to taps anywhere on the `ModelSelectionTile` except for the + // trailing action icon. + void _handleTap() { + final VoidCallback? actionHandler = switch (modelInfo.state) { + ModelState.downloaded => selectModel, + ModelState.downloading => null, + ModelState.empty => null, + }; + actionHandler?.call(); + } + + // Responds to taps on the trailing action icon, which should always either + // do nothing or launch a modal. + void _launchModal(BuildContext context) { + final VoidCallback? actionHandler = switch (modelInfo.state) { + ModelState.downloaded => delete, + ModelState.downloading => null, + ModelState.empty => download, + }; + if (actionHandler == null) { + return; + } + final String title = switch (modelInfo.state) { + ModelState.downloaded => 'Delete', + ModelState.downloading => '', + ModelState.empty => 'Download', + }; + + final String? message = switch (modelInfo.state) { + ModelState.downloaded => + 'Would you like to delete this downloaded model?', + ModelState.downloading => null, + ModelState.empty => 'Would you like to download this model?', + }; + + showAlertDialog( + context: context, + title: title, + message: message, + actions: >[ + AlertDialogAction( + key: false, + label: 'Cancel', + isDefaultAction: true, + isDestructiveAction: modelInfo.state == ModelState.downloaded, + ), + AlertDialogAction( + key: true, + label: title, + isDefaultAction: true, + isDestructiveAction: modelInfo.state == ModelState.downloaded, + ), + ], + ).then((result) { + if (result == true) { + actionHandler(); + } + }); + } +} + +class _DownloadingBar extends StatelessWidget { + const _DownloadingBar({required this.downloadPercent}); + + final int downloadPercent; + + @override + Widget build(BuildContext context) { + return GFProgressBar( + percentage: (downloadPercent / 100).toDouble().clamp(0, 1.0), + lineHeight: 8, + backgroundColor: Colors.green[100]!, + progressBarColor: Colors.green[700]!, + ); + } +} + +final gigaByte = pow(2, 30); +final megaByte = pow(2, 20); + +String humanize(int fileSize) { + if (fileSize > gigaByte) { + return '${(fileSize / gigaByte).roundTo(2)} GB'; + } else if (fileSize > megaByte) { + return '${(fileSize / megaByte).roundTo(2)} MB'; + } + return '$fileSize bytes'; +} + +extension RoundableDouble on double { + double roundTo(int decimalPlaces) => + double.parse(toStringAsFixed(decimalPlaces)); +} diff --git a/packages/mediapipe-task-genai/example/lib/model_storage/io_model_storage.dart b/packages/mediapipe-task-genai/example/lib/model_storage/io_model_storage.dart new file mode 100644 index 00000000..11b53986 --- /dev/null +++ b/packages/mediapipe-task-genai/example/lib/model_storage/io_model_storage.dart @@ -0,0 +1,85 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:example/models/models.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:path/path.dart' as path; + +import 'model_storage_interface.dart'; + +class ModelStorage extends ModelStorageInterface { + @override + int binarySize(String location) { + final file = File(location); + if (!file.existsSync()) { + throw Exception('Unexpectedly asked for binary size of non-existent ' + 'file at $location'); + } + return file.lengthSync(); + } + + @override + Future downloadExists(String downloadLocation) => + File(downloadLocation).exists(); + + @override + Future urlToDownloadDestination(Uri location) async => path.join( + (await _getDownloadFolder()).absolute.path, + location.pathSegments.last, + ); + + Directory? _downloadFolder; + Future _getDownloadFolder() async { + _downloadFolder ??= await getApplicationCacheDirectory(); + return Future.value(_downloadFolder); + } + + @override + Future abort(String location) async { + if (!_downloadCache.containsKey(location)) { + throw Exception('Abort called for location $location, which is not the ' + 'site of an ongoing donwload.'); + } + final file = File(location); + if (await file.exists()) { + await file.delete(); + } + _downloadCache[location]!.close(); + _downloadCache.remove(location); + } + + @override + Future close(String location) async { + if (!_downloadCache.containsKey(location)) { + throw Exception('Abort called for location $location, which is not the ' + 'site of an ongoing donwload.'); + } + _downloadCache[location]!.close(); + _downloadCache.remove(location); + } + + final _downloadCache = >>{}; + + @override + Future>> create(String location) async { + final file = File(location); + if (await file.exists()) { + throw Exception('Attempted to download on top of existing file at ' + '$location. Delete that file before proceeding.'); + } + _downloadCache[location] = file.openWrite(); + return _downloadCache[location]!; + } + + @override + Future delete(LlmModel model) async { + final path = pathFor(model); + if (path != null) { + clearPathCache(model); + final file = File(path); + if (await file.exists()) { + await file.delete(); + } + } + } +} diff --git a/packages/mediapipe-task-genai/example/lib/model_storage/model_storage.dart b/packages/mediapipe-task-genai/example/lib/model_storage/model_storage.dart new file mode 100644 index 00000000..0c4b6238 --- /dev/null +++ b/packages/mediapipe-task-genai/example/lib/model_storage/model_storage.dart @@ -0,0 +1,5 @@ +export 'model_storage_interface.dart'; + +export 'universal_model_storage.dart' + if (dart.library.html) 'web_model_storage.dart' + if (dart.library.io) 'io_model_storage.dart'; diff --git a/packages/mediapipe-task-genai/example/lib/model_storage/model_storage_interface.dart b/packages/mediapipe-task-genai/example/lib/model_storage/model_storage_interface.dart new file mode 100644 index 00000000..fee155f4 --- /dev/null +++ b/packages/mediapipe-task-genai/example/lib/model_storage/model_storage_interface.dart @@ -0,0 +1,120 @@ +import 'dart:async'; + +import 'package:example/models/models.dart'; +import 'package:logging/logging.dart'; + +typedef ModelPaths = Map; + +final _log = Logger('ModelLocationStorage'); + +abstract class ModelStorageInterface { + Future setInitialModelLocations( + ModelPaths initialModelLocations, + ) async { + for (final (model, location) in initialModelLocations.items) { + _log.finer('${model.name} :: $location'); + if (location.isEmpty) continue; + final uri = Uri.parse(location); + + late final String downloadDestination; + switch (uri.scheme.startsWith('http')) { + case (true): + { + downloadDestination = await urlToDownloadDestination(uri); + if (await downloadExists(downloadDestination)) { + setPathCache(model, downloadDestination); + } + setUriCache(model, uri); + } + case (false): + { + // If the location provided does not start with "http", it must be + // something immediately accessible to the current runtime. + downloadDestination = location; + if (await downloadExists(downloadDestination)) { + setPathCache(model, downloadDestination); + } else { + _log.warning( + 'Bad specification. Model for ${model.name} not found ' + 'at at $location', + ); + } + } + } + } + } + + /// Deletes the file at the given location. + Future delete(LlmModel model); + + /// Opens the resources to save an [LlmModel] binary and returns a stream to + /// which bytes may be written. + Future>> create(String location); + + /// Marks a download initiated by [create] as successfully completed. Throws + /// an exception if no such download is in progress. + Future close(String location); + + /// Terminates a download and deletes all progress. Throws an exception if no + /// such download is in progress. + Future abort(String location); + + /// Determine to where in on-device storage this remote Url should be downloaded. + Future urlToDownloadDestination(Uri location); + + /// {@template downloadExists} + /// Returns true if the model at the given file is completely downloaded, or + /// false if it does not exist at all or is still downloading. + /// {@endtemplate} + Future downloadExists(String downloadLocation); + + /// {@template binarySize} + /// Returns the size of the binary at the given on-device location. Throws an + /// exception if the file does not exist, so before calling this method, + /// verify that the model is downloaded with [downloadExists]. + /// {@endtemplate} + int binarySize(String location); + + /// Returns the on-disk location of a downloaded model. + String? pathFor(LlmModel model) => _pathCache[model]; + + void clearPathCache(LlmModel model) => _pathCache.remove(model); + + /// Returns the remote location of a model. + Uri? urlFor(LlmModel model) => _urlCache[model]; + + /// On disk locations for the model. This could either have been supplied + /// at compile-time via environment variable, or at runtime due to a web url + /// location (also defined at compile-time) that was since been downloaded. + final _pathCache = {}; + + /// Off-disk locations for models that could be downloaded to disk. Doing so + /// will creating an entry in [_pathCache] for where the downloaded model + /// resides on disk. + /// + /// This is stored (if known) even if the storage system believes the model is + /// already downloaded, because the user could need to delete and redownload + /// said model. + final _urlCache = {}; + + void setUriCache(LlmModel model, Uri location) { + _log.fine('Registered remote ${model.name} location at $location'); + _urlCache[model] = location; + } + + void setPathCache(LlmModel model, String location) { + _log.fine( + 'Detected downloaded binary for ${model.name} of size ' + '${binarySize(location)} bytes', + ); + _pathCache[model] = location; + } +} + +extension ItemsMap on Map { + Iterable<(K, V)> get items sync* { + for (final K key in keys) { + yield (key, this[key] as V); + } + } +} diff --git a/packages/mediapipe-task-genai/example/lib/model_storage/universal_model_storage.dart b/packages/mediapipe-task-genai/example/lib/model_storage/universal_model_storage.dart new file mode 100644 index 00000000..c58ea15c --- /dev/null +++ b/packages/mediapipe-task-genai/example/lib/model_storage/universal_model_storage.dart @@ -0,0 +1,31 @@ +import 'dart:async'; + +import 'package:example/models/models.dart'; + +import 'model_storage_interface.dart'; + +class ModelStorage extends ModelStorageInterface { + @override + int binarySize(String location) => throw UnimplementedError(); + + @override + Future downloadExists(String downloadLocation) => + throw UnimplementedError(); + + @override + Future urlToDownloadDestination(Uri location) async => + throw UnimplementedError(); + + @override + Future abort(String location) => throw UnimplementedError(); + + @override + Future close(String location) => throw UnimplementedError(); + + @override + Future delete(LlmModel model) => throw UnimplementedError(); + + @override + Future>> create(String location) => + throw UnimplementedError(); +} diff --git a/packages/mediapipe-task-genai/example/lib/models/chat_message.dart b/packages/mediapipe-task-genai/example/lib/models/chat_message.dart new file mode 100644 index 00000000..8deaa5f4 --- /dev/null +++ b/packages/mediapipe-task-genai/example/lib/models/chat_message.dart @@ -0,0 +1,94 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:uuid/uuid.dart'; + +part 'chat_message.freezed.dart'; + +@Freezed() +class ChatMessage with _$ChatMessage { + const ChatMessage._(); + const factory ChatMessage({ + required String id, + required String body, + required MessageOrigin origin, + required int cursorPosition, + + /// Always true for a user's message, but only true for the LLM once it has + /// finished composing its reply. + required bool isComplete, + }) = _ChatMessage; + + factory ChatMessage.origin(String body, MessageOrigin origin) => + origin == MessageOrigin.user + ? ChatMessage.user(body) + : ChatMessage.llm(body); + + factory ChatMessage.llm(String body, {int? cursorPosition}) => ChatMessage( + // Sometimes the LLM starts a response with multiple empty newlines + body: body, + origin: MessageOrigin.llm, + cursorPosition: cursorPosition ?? 0, + isComplete: false, + id: const Uuid().v4(), + ); + + factory ChatMessage.user(String body) => ChatMessage( + body: body, + origin: MessageOrigin.user, + cursorPosition: body.length, + isComplete: true, + id: const Uuid().v4(), + ); + + ChatMessage complete() { + assert(() { + if (origin.isUser) { + throw Exception( + 'Only expected to complete messages from the LLM. ' + 'Did you call complete() on the wrong ChatMessage?', + ); + } + return true; + }()); + return copyWith(isComplete: true); + } + + bool get displayingFullString => cursorPosition == body.length; + + // String get displayString => body.substring(0, cursorPosition); + String get displayString => body; + + ChatMessage advanceCursor() => copyWith(cursorPosition: cursorPosition + 1); +} + +enum MessageOrigin { + user, + llm; + + bool get isUser => switch (this) { + MessageOrigin.user => true, + MessageOrigin.llm => false, + }; + + bool get isLlm => switch (this) { + MessageOrigin.user => false, + MessageOrigin.llm => true, + }; + + String get transcriptName => switch (this) { + MessageOrigin.user => 'USER', + MessageOrigin.llm => 'LLM', + }; + + Alignment alignmentFromTextDirection(TextDirection textDirection) => + switch (textDirection) { + TextDirection.ltr => + isUser ? Alignment.centerRight : Alignment.centerLeft, + TextDirection.rtl => + isUser ? Alignment.centerLeft : Alignment.centerRight, + }; +} diff --git a/packages/mediapipe-task-genai/example/lib/models/chat_message.freezed.dart b/packages/mediapipe-task-genai/example/lib/models/chat_message.freezed.dart new file mode 100644 index 00000000..f41af6fa --- /dev/null +++ b/packages/mediapipe-task-genai/example/lib/models/chat_message.freezed.dart @@ -0,0 +1,230 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'chat_message.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +/// @nodoc +mixin _$ChatMessage { + String get id => throw _privateConstructorUsedError; + String get body => throw _privateConstructorUsedError; + MessageOrigin get origin => throw _privateConstructorUsedError; + int get cursorPosition => throw _privateConstructorUsedError; + + /// Always true for a user's message, but only true for the LLM once it has + /// finished composing its reply. + bool get isComplete => throw _privateConstructorUsedError; + + @JsonKey(ignore: true) + $ChatMessageCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ChatMessageCopyWith<$Res> { + factory $ChatMessageCopyWith( + ChatMessage value, $Res Function(ChatMessage) then) = + _$ChatMessageCopyWithImpl<$Res, ChatMessage>; + @useResult + $Res call( + {String id, + String body, + MessageOrigin origin, + int cursorPosition, + bool isComplete}); +} + +/// @nodoc +class _$ChatMessageCopyWithImpl<$Res, $Val extends ChatMessage> + implements $ChatMessageCopyWith<$Res> { + _$ChatMessageCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? body = null, + Object? origin = null, + Object? cursorPosition = null, + Object? isComplete = null, + }) { + return _then(_value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + body: null == body + ? _value.body + : body // ignore: cast_nullable_to_non_nullable + as String, + origin: null == origin + ? _value.origin + : origin // ignore: cast_nullable_to_non_nullable + as MessageOrigin, + cursorPosition: null == cursorPosition + ? _value.cursorPosition + : cursorPosition // ignore: cast_nullable_to_non_nullable + as int, + isComplete: null == isComplete + ? _value.isComplete + : isComplete // ignore: cast_nullable_to_non_nullable + as bool, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$ChatMessageImplCopyWith<$Res> + implements $ChatMessageCopyWith<$Res> { + factory _$$ChatMessageImplCopyWith( + _$ChatMessageImpl value, $Res Function(_$ChatMessageImpl) then) = + __$$ChatMessageImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String id, + String body, + MessageOrigin origin, + int cursorPosition, + bool isComplete}); +} + +/// @nodoc +class __$$ChatMessageImplCopyWithImpl<$Res> + extends _$ChatMessageCopyWithImpl<$Res, _$ChatMessageImpl> + implements _$$ChatMessageImplCopyWith<$Res> { + __$$ChatMessageImplCopyWithImpl( + _$ChatMessageImpl _value, $Res Function(_$ChatMessageImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? body = null, + Object? origin = null, + Object? cursorPosition = null, + Object? isComplete = null, + }) { + return _then(_$ChatMessageImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + body: null == body + ? _value.body + : body // ignore: cast_nullable_to_non_nullable + as String, + origin: null == origin + ? _value.origin + : origin // ignore: cast_nullable_to_non_nullable + as MessageOrigin, + cursorPosition: null == cursorPosition + ? _value.cursorPosition + : cursorPosition // ignore: cast_nullable_to_non_nullable + as int, + isComplete: null == isComplete + ? _value.isComplete + : isComplete // ignore: cast_nullable_to_non_nullable + as bool, + )); + } +} + +/// @nodoc + +class _$ChatMessageImpl extends _ChatMessage { + const _$ChatMessageImpl( + {required this.id, + required this.body, + required this.origin, + required this.cursorPosition, + required this.isComplete}) + : super._(); + + @override + final String id; + @override + final String body; + @override + final MessageOrigin origin; + @override + final int cursorPosition; + + /// Always true for a user's message, but only true for the LLM once it has + /// finished composing its reply. + @override + final bool isComplete; + + @override + String toString() { + return 'ChatMessage(id: $id, body: $body, origin: $origin, cursorPosition: $cursorPosition, isComplete: $isComplete)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ChatMessageImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.body, body) || other.body == body) && + (identical(other.origin, origin) || other.origin == origin) && + (identical(other.cursorPosition, cursorPosition) || + other.cursorPosition == cursorPosition) && + (identical(other.isComplete, isComplete) || + other.isComplete == isComplete)); + } + + @override + int get hashCode => + Object.hash(runtimeType, id, body, origin, cursorPosition, isComplete); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$ChatMessageImplCopyWith<_$ChatMessageImpl> get copyWith => + __$$ChatMessageImplCopyWithImpl<_$ChatMessageImpl>(this, _$identity); +} + +abstract class _ChatMessage extends ChatMessage { + const factory _ChatMessage( + {required final String id, + required final String body, + required final MessageOrigin origin, + required final int cursorPosition, + required final bool isComplete}) = _$ChatMessageImpl; + const _ChatMessage._() : super._(); + + @override + String get id; + @override + String get body; + @override + MessageOrigin get origin; + @override + int get cursorPosition; + @override + + /// Always true for a user's message, but only true for the LLM once it has + /// finished composing its reply. + bool get isComplete; + @override + @JsonKey(ignore: true) + _$$ChatMessageImplCopyWith<_$ChatMessageImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/packages/mediapipe-task-genai/example/lib/models/llm_model.dart b/packages/mediapipe-task-genai/example/lib/models/llm_model.dart new file mode 100644 index 00000000..d1ac79c8 --- /dev/null +++ b/packages/mediapipe-task-genai/example/lib/models/llm_model.dart @@ -0,0 +1,76 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'llm_model.freezed.dart'; + +enum Hardware { cpu, gpu } + +enum LlmModel { + gemma4bCpu, + gemma4bGpu, + gemma8bCpu, + gemma8bGpu; + + Hardware get hardware => switch (this) { + gemma4bCpu => Hardware.cpu, + gemma4bGpu => Hardware.gpu, + gemma8bCpu => Hardware.cpu, + gemma8bGpu => Hardware.gpu, + }; + + String get dartDefine => switch (this) { + gemma4bCpu => const String.fromEnvironment('GEMMA_4B_CPU_URI'), + gemma4bGpu => const String.fromEnvironment('GEMMA_4B_GPU_URI'), + gemma8bCpu => const String.fromEnvironment('GEMMA_8B_CPU_URI'), + gemma8bGpu => const String.fromEnvironment('GEMMA_8B_GPU_URI'), + }; + + String get environmentVariableUriName => switch (this) { + gemma4bCpu => 'GEMMA_4B_CPU_URI', + gemma4bGpu => 'GEMMA_4B_GPU_URI', + gemma8bCpu => 'GEMMA_8B_CPU_URI', + gemma8bGpu => 'GEMMA_8B_GPU_URI', + }; + + String get displayName => switch (this) { + gemma4bCpu => 'Gemma 4b CPU', + gemma4bGpu => 'Gemma 4b GPU', + gemma8bCpu => 'Gemma 8b CPU', + gemma8bGpu => 'Gemma 8b GPU', + }; +} + +@Freezed() +class ModelInfo with _$ModelInfo { + const ModelInfo._(); + const factory ModelInfo({ + /// Size of the on-disk location of this model. A null value here either + /// means that the model is currently downloading or completely missing. + int? downloadedBytes, + + /// 0-100 if a model is being downloaded. A null value here means no + /// download is in progress for the given model. + int? downloadPercent, + + /// Location of the model if it is available on disk. + String? path, + + /// Location from which the model can be downloaded if it is not already + /// available. + Uri? remoteLocation, + }) = _ModelInfo; + + ModelState get state { + if (downloadedBytes != null) { + return ModelState.downloaded; + } else if (downloadPercent != null) { + return ModelState.downloading; + } + return ModelState.empty; + } +} + +enum ModelState { downloaded, downloading, empty } diff --git a/packages/mediapipe-task-genai/example/lib/models/llm_model.freezed.dart b/packages/mediapipe-task-genai/example/lib/models/llm_model.freezed.dart new file mode 100644 index 00000000..75c7dab9 --- /dev/null +++ b/packages/mediapipe-task-genai/example/lib/models/llm_model.freezed.dart @@ -0,0 +1,232 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'llm_model.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +/// @nodoc +mixin _$ModelInfo { + /// Size of the on-disk location of this model. A null value here either + /// means that the model is currently downloading or completely missing. + int? get downloadedBytes => throw _privateConstructorUsedError; + + /// 0-100 if a model is being downloaded. A null value here means no + /// download is in progress for the given model. + int? get downloadPercent => throw _privateConstructorUsedError; + + /// Location of the model if it is available on disk. + String? get path => throw _privateConstructorUsedError; + + /// Location from which the model can be downloaded if it is not already + /// available. + Uri? get remoteLocation => throw _privateConstructorUsedError; + + @JsonKey(ignore: true) + $ModelInfoCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ModelInfoCopyWith<$Res> { + factory $ModelInfoCopyWith(ModelInfo value, $Res Function(ModelInfo) then) = + _$ModelInfoCopyWithImpl<$Res, ModelInfo>; + @useResult + $Res call( + {int? downloadedBytes, + int? downloadPercent, + String? path, + Uri? remoteLocation}); +} + +/// @nodoc +class _$ModelInfoCopyWithImpl<$Res, $Val extends ModelInfo> + implements $ModelInfoCopyWith<$Res> { + _$ModelInfoCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? downloadedBytes = freezed, + Object? downloadPercent = freezed, + Object? path = freezed, + Object? remoteLocation = freezed, + }) { + return _then(_value.copyWith( + downloadedBytes: freezed == downloadedBytes + ? _value.downloadedBytes + : downloadedBytes // ignore: cast_nullable_to_non_nullable + as int?, + downloadPercent: freezed == downloadPercent + ? _value.downloadPercent + : downloadPercent // ignore: cast_nullable_to_non_nullable + as int?, + path: freezed == path + ? _value.path + : path // ignore: cast_nullable_to_non_nullable + as String?, + remoteLocation: freezed == remoteLocation + ? _value.remoteLocation + : remoteLocation // ignore: cast_nullable_to_non_nullable + as Uri?, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$ModelInfoImplCopyWith<$Res> + implements $ModelInfoCopyWith<$Res> { + factory _$$ModelInfoImplCopyWith( + _$ModelInfoImpl value, $Res Function(_$ModelInfoImpl) then) = + __$$ModelInfoImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {int? downloadedBytes, + int? downloadPercent, + String? path, + Uri? remoteLocation}); +} + +/// @nodoc +class __$$ModelInfoImplCopyWithImpl<$Res> + extends _$ModelInfoCopyWithImpl<$Res, _$ModelInfoImpl> + implements _$$ModelInfoImplCopyWith<$Res> { + __$$ModelInfoImplCopyWithImpl( + _$ModelInfoImpl _value, $Res Function(_$ModelInfoImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? downloadedBytes = freezed, + Object? downloadPercent = freezed, + Object? path = freezed, + Object? remoteLocation = freezed, + }) { + return _then(_$ModelInfoImpl( + downloadedBytes: freezed == downloadedBytes + ? _value.downloadedBytes + : downloadedBytes // ignore: cast_nullable_to_non_nullable + as int?, + downloadPercent: freezed == downloadPercent + ? _value.downloadPercent + : downloadPercent // ignore: cast_nullable_to_non_nullable + as int?, + path: freezed == path + ? _value.path + : path // ignore: cast_nullable_to_non_nullable + as String?, + remoteLocation: freezed == remoteLocation + ? _value.remoteLocation + : remoteLocation // ignore: cast_nullable_to_non_nullable + as Uri?, + )); + } +} + +/// @nodoc + +class _$ModelInfoImpl extends _ModelInfo { + const _$ModelInfoImpl( + {this.downloadedBytes, + this.downloadPercent, + this.path, + this.remoteLocation}) + : super._(); + + /// Size of the on-disk location of this model. A null value here either + /// means that the model is currently downloading or completely missing. + @override + final int? downloadedBytes; + + /// 0-100 if a model is being downloaded. A null value here means no + /// download is in progress for the given model. + @override + final int? downloadPercent; + + /// Location of the model if it is available on disk. + @override + final String? path; + + /// Location from which the model can be downloaded if it is not already + /// available. + @override + final Uri? remoteLocation; + + @override + String toString() { + return 'ModelInfo(downloadedBytes: $downloadedBytes, downloadPercent: $downloadPercent, path: $path, remoteLocation: $remoteLocation)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ModelInfoImpl && + (identical(other.downloadedBytes, downloadedBytes) || + other.downloadedBytes == downloadedBytes) && + (identical(other.downloadPercent, downloadPercent) || + other.downloadPercent == downloadPercent) && + (identical(other.path, path) || other.path == path) && + (identical(other.remoteLocation, remoteLocation) || + other.remoteLocation == remoteLocation)); + } + + @override + int get hashCode => Object.hash( + runtimeType, downloadedBytes, downloadPercent, path, remoteLocation); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$ModelInfoImplCopyWith<_$ModelInfoImpl> get copyWith => + __$$ModelInfoImplCopyWithImpl<_$ModelInfoImpl>(this, _$identity); +} + +abstract class _ModelInfo extends ModelInfo { + const factory _ModelInfo( + {final int? downloadedBytes, + final int? downloadPercent, + final String? path, + final Uri? remoteLocation}) = _$ModelInfoImpl; + const _ModelInfo._() : super._(); + + @override + + /// Size of the on-disk location of this model. A null value here either + /// means that the model is currently downloading or completely missing. + int? get downloadedBytes; + @override + + /// 0-100 if a model is being downloaded. A null value here means no + /// download is in progress for the given model. + int? get downloadPercent; + @override + + /// Location of the model if it is available on disk. + String? get path; + @override + + /// Location from which the model can be downloaded if it is not already + /// available. + Uri? get remoteLocation; + @override + @JsonKey(ignore: true) + _$$ModelInfoImplCopyWith<_$ModelInfoImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/packages/mediapipe-task-genai/example/lib/models/models.dart b/packages/mediapipe-task-genai/example/lib/models/models.dart new file mode 100644 index 00000000..903b77f4 --- /dev/null +++ b/packages/mediapipe-task-genai/example/lib/models/models.dart @@ -0,0 +1,2 @@ +export 'chat_message.dart'; +export 'llm_model.dart'; diff --git a/packages/mediapipe-task-genai/example/lib/widgets/chat_input.dart b/packages/mediapipe-task-genai/example/lib/widgets/chat_input.dart new file mode 100644 index 00000000..8ae59ba2 --- /dev/null +++ b/packages/mediapipe-task-genai/example/lib/widgets/chat_input.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; + +class ChatInput extends StatelessWidget { + const ChatInput({ + required this.submit, + required this.controller, + required this.isLlmTyping, + super.key, + }); + + final TextEditingController controller; + + final void Function(String) submit; + + /// Prevents submitting new messages when true. + final bool isLlmTyping; + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Expanded(child: TextField(controller: controller)), + ValueListenableBuilder( + valueListenable: controller, + builder: (context, value, child) { + return IconButton( + icon: const Icon(Icons.send), + onPressed: controller.text != '' && !isLlmTyping + ? () { + submit(controller.text); + controller.clear(); + } + : null, + ); + }, + ), + ], + ); + } +} diff --git a/packages/mediapipe-task-genai/example/lib/widgets/chat_screen.dart b/packages/mediapipe-task-genai/example/lib/widgets/chat_screen.dart new file mode 100644 index 00000000..2aa8871d --- /dev/null +++ b/packages/mediapipe-task-genai/example/lib/widgets/chat_screen.dart @@ -0,0 +1,165 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:chat_bubbles/chat_bubbles.dart'; +import 'package:example/bloc.dart'; +import 'package:example/models/models.dart'; +import 'package:example/widgets/widgets.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class ChatScreen extends StatefulWidget { + const ChatScreen(this.bloc, {required this.model, super.key}); + + final TranscriptBloc bloc; + final LlmModel model; + + @override + State createState() => _ChatScreenState(); +} + +class _ChatScreenState extends State { + final TextEditingController _controller = TextEditingController(); + + static const initialText = 'Hello, world!'; + + @override + void initState() { + if (widget.bloc.state.transcript[widget.model]!.isEmpty) { + _controller.text = initialText; + } + super.initState(); + } + + @override + Widget build(BuildContext context) { + final bloc = widget.bloc; + return BlocBuilder( + bloc: bloc, + builder: (context, state) { + return Scaffold( + appBar: AppBar(title: Text(widget.model.displayName)), + endDrawer: Drawer( + child: Padding( + padding: const EdgeInsets.all(12), + child: InferenceConfigurationPanel( + topK: state.topK, + temp: state.temperature, + maxTokens: state.maxTokens, + updateTopK: (val) => bloc.add(UpdateTopK(val)), + updateTemp: (val) => bloc.add(UpdateTemperature(val)), + updateMaxTokens: (val) => bloc.add(UpdateMaxTokens(val)), + ), + ), + ), + body: SafeArea( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + children: [ + Expanded( + child: KeyboardHider( + child: ChatTranscript(state.transcript[widget.model]!), + ), + ), + ChatInput( + controller: _controller, + isLlmTyping: state.isLlmTyping, + submit: (String value) { + bloc.add( + AddMessage(ChatMessage.user(value), widget.model), + ); + }, + ), + ], + ), + ), + ), + ); + }, + ); + } +} + +class ChatTranscript extends StatefulWidget { + const ChatTranscript( + this.transcript, { + super.key, + }); + + final List transcript; + + @override + State createState() => _ChatTranscriptState(); +} + +class _ChatTranscriptState extends State { + @override + Widget build(BuildContext context) { + return LayoutBuilder( + builder: (context, constraints) { + return ListView.builder( + reverse: true, + itemCount: widget.transcript.length, + itemBuilder: (context, index) { + final messageIndex = widget.transcript.length - index - 1; + final message = widget.transcript[messageIndex]; + + return Padding( + padding: const EdgeInsets.only(top: 6), + child: message.displayString != '' + ? ChatMessageBubble( + message: message, + width: constraints.maxWidth * 0.8, + key: ValueKey('message-${message.id}'), + ) + : const TypingIndicator( + showIndicator: true, + ), + ); + }, + ); + }, + ); + } +} + +class ChatMessageBubble extends StatelessWidget { + const ChatMessageBubble({ + required this.message, + required this.width, + super.key, + }); + + final ChatMessage message; + final double width; + + // Colors.blue[400] + static const llmBgColor = Color(0xFF42A5F5); + + // Colors.orange[400] + static const userBgColor = Color(0xFFFFA726); + + @override + Widget build(BuildContext context) { + return Row( + children: [ + if (message.origin.isUser) Flexible(flex: 2, child: Container()), + Flexible( + flex: 6, + child: BubbleSpecialThree( + text: message.displayString, + color: message.origin.isUser ? userBgColor : llmBgColor, + isSender: message.origin.isUser, + textStyle: const TextStyle(color: Colors.white), + sent: message.isComplete, + delivered: message.isComplete, + seen: message.isComplete, + ), + ), + if (message.origin.isLlm) Flexible(flex: 2, child: Container()), + ], + ); + } +} diff --git a/packages/mediapipe-task-genai/example/lib/widgets/configuration_panel.dart b/packages/mediapipe-task-genai/example/lib/widgets/configuration_panel.dart new file mode 100644 index 00000000..41fe7114 --- /dev/null +++ b/packages/mediapipe-task-genai/example/lib/widgets/configuration_panel.dart @@ -0,0 +1,111 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; + +/// Contains sliders for each configuration option for option passed to the +/// inference engine. +class InferenceConfigurationPanel extends StatelessWidget { + const InferenceConfigurationPanel({ + required this.topK, + required this.temp, + required this.maxTokens, + required this.updateTopK, + required this.updateTemp, + required this.updateMaxTokens, + super.key, + }); + + /// Top K number of tokens to be sampled from for each decoding step. + final int topK; + + /// Handler to update [topK]. + final void Function(int) updateTopK; + + /// Context size window for the LLM. + final int maxTokens; + + /// Handler to update [maxTokens]. + final void Function(int) updateMaxTokens; + + /// Randomness when decoding the next token. + final double temp; + + /// Handler to update [temp]. + final void Function(double) updateTemp; + + @override + Widget build(BuildContext context) { + return ListView( + children: [ + Text('Top K', style: Theme.of(context).textTheme.bodyLarge), + Text('Number of tokens to be sampled from for each decoding step.', + style: Theme.of(context).textTheme.bodySmall), + Slider( + value: topK.toDouble(), + min: 1, + max: 100, + divisions: 100, + onChanged: (newTopK) => updateTopK(newTopK.toInt()), + ), + Text( + topK.toString(), + style: Theme.of(context) + .textTheme + .bodySmall! + .copyWith(color: Colors.grey), + ), + const Divider(), + Text('Temperature', style: Theme.of(context).textTheme.bodyLarge), + Text('Randomness when decoding the next token.', + style: Theme.of(context).textTheme.bodySmall), + Slider( + value: temp, + min: 0, + max: 1, + onChanged: updateTemp, + ), + Text( + temp.roundTo(3).toString(), + style: Theme.of(context) + .textTheme + .bodySmall! + .copyWith(color: Colors.grey), + ), + const Divider(), + Text('Max Tokens', style: Theme.of(context).textTheme.bodyLarge), + Text( + 'Maximum context window for the LLM. Larger windows can tax ' + 'certain devices.', + style: Theme.of(context).textTheme.bodySmall), + Slider( + value: maxTokens.toDouble(), + min: 512, + max: 8192, + onChanged: (newMaxTokens) => updateMaxTokens(newMaxTokens.toInt()), + ), + Text( + maxTokens.toString(), + style: Theme.of(context) + .textTheme + .bodySmall! + .copyWith(color: Colors.grey), + ), + const Divider(), + GestureDetector( + onTap: () => Navigator.of(context).pop(), + child: Text( + 'Close', + style: Theme.of(context).textTheme.headlineMedium, + ), + ), + ], + ); + } +} + +extension on double { + double roundTo(int decimalPlaces) => + double.parse(toStringAsFixed(decimalPlaces)); +} diff --git a/packages/mediapipe-task-genai/example/lib/widgets/keyboard_hider.dart b/packages/mediapipe-task-genai/example/lib/widgets/keyboard_hider.dart new file mode 100644 index 00000000..0dcb496c --- /dev/null +++ b/packages/mediapipe-task-genai/example/lib/widgets/keyboard_hider.dart @@ -0,0 +1,24 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; + +class KeyboardHider extends StatelessWidget { + const KeyboardHider({required this.child, super.key}); + + final Widget? child; + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () { + // Not sure why this one isn't working. + // FocusScope.of(context).unfocus(); + SystemChannels.textInput.invokeMethod('TextInput.hide'); + }, + child: child, + ); + } +} diff --git a/packages/mediapipe-task-genai/example/lib/widgets/typing_indicator.dart b/packages/mediapipe-task-genai/example/lib/widgets/typing_indicator.dart new file mode 100644 index 00000000..d7f3b34f --- /dev/null +++ b/packages/mediapipe-task-genai/example/lib/widgets/typing_indicator.dart @@ -0,0 +1,321 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; + +class TypingIndicator extends StatefulWidget { + const TypingIndicator({ + super.key, + this.showIndicator = false, + this.bubbleColor = const Color(0xFF646b7f), + this.flashingCircleDarkColor = const Color(0xFF333333), + this.flashingCircleBrightColor = const Color(0xFFaec1dd), + }); + + final bool showIndicator; + final Color bubbleColor; + final Color flashingCircleDarkColor; + final Color flashingCircleBrightColor; + + @override + State createState() => _TypingIndicatorState(); +} + +class _TypingIndicatorState extends State + with TickerProviderStateMixin { + late AnimationController _appearanceController; + + late Animation _indicatorSpaceAnimation; + + late Animation _smallBubbleAnimation; + late Animation _mediumBubbleAnimation; + late Animation _largeBubbleAnimation; + + late AnimationController _repeatingController; + final List _dotIntervals = const [ + Interval(0.25, 0.8), + Interval(0.35, 0.9), + Interval(0.45, 1.0), + ]; + + @override + void initState() { + super.initState(); + + _appearanceController = AnimationController( + vsync: this, + )..addListener(() { + setState(() {}); + }); + + _indicatorSpaceAnimation = CurvedAnimation( + parent: _appearanceController, + curve: const Interval(0.0, 0.4, curve: Curves.easeOut), + reverseCurve: const Interval(0.0, 1.0, curve: Curves.easeOut), + ).drive(Tween( + begin: 0.0, + end: 60.0, + )); + + _smallBubbleAnimation = CurvedAnimation( + parent: _appearanceController, + curve: const Interval(0.0, 0.5, curve: Curves.elasticOut), + reverseCurve: const Interval(0.0, 0.3, curve: Curves.easeOut), + ); + _mediumBubbleAnimation = CurvedAnimation( + parent: _appearanceController, + curve: const Interval(0.2, 0.7, curve: Curves.elasticOut), + reverseCurve: const Interval(0.2, 0.6, curve: Curves.easeOut), + ); + _largeBubbleAnimation = CurvedAnimation( + parent: _appearanceController, + curve: const Interval(0.3, 1.0, curve: Curves.elasticOut), + reverseCurve: const Interval(0.5, 1.0, curve: Curves.easeOut), + ); + + _repeatingController = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 1500), + ); + + if (widget.showIndicator) { + _showIndicator(); + } + } + + @override + void didUpdateWidget(TypingIndicator oldWidget) { + super.didUpdateWidget(oldWidget); + + if (widget.showIndicator != oldWidget.showIndicator) { + if (widget.showIndicator) { + _showIndicator(); + } else { + _hideIndicator(); + } + } + } + + @override + void dispose() { + _appearanceController.dispose(); + _repeatingController.dispose(); + super.dispose(); + } + + void _showIndicator() { + _appearanceController + ..duration = const Duration(milliseconds: 750) + ..forward(); + _repeatingController.repeat(); + } + + void _hideIndicator() { + _appearanceController + ..duration = const Duration(milliseconds: 150) + ..reverse(); + _repeatingController.stop(); + } + + @override + Widget build(BuildContext context) { + return AnimatedBuilder( + animation: _indicatorSpaceAnimation, + builder: (context, child) { + return SizedBox( + height: _indicatorSpaceAnimation.value, + child: child, + ); + }, + child: Stack( + children: [ + AnimatedBubble( + animation: _smallBubbleAnimation, + left: 8, + bottom: 8, + bubble: CircleBubble( + size: 8, + bubbleColor: widget.bubbleColor, + ), + ), + AnimatedBubble( + animation: _mediumBubbleAnimation, + left: 10, + bottom: 10, + bubble: CircleBubble( + size: 16, + bubbleColor: widget.bubbleColor, + ), + ), + AnimatedBubble( + animation: _largeBubbleAnimation, + left: 12, + bottom: 12, + bubble: StatusBubble( + repeatingController: _repeatingController, + dotIntervals: _dotIntervals, + flashingCircleDarkColor: widget.flashingCircleDarkColor, + flashingCircleBrightColor: widget.flashingCircleBrightColor, + bubbleColor: widget.bubbleColor, + ), + ), + ], + ), + ); + } +} + +class CircleBubble extends StatelessWidget { + const CircleBubble({ + super.key, + required this.size, + required this.bubbleColor, + }); + + final double size; + final Color bubbleColor; + + @override + Widget build(BuildContext context) { + return Container( + width: size, + height: size, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: bubbleColor, + ), + ); + } +} + +class AnimatedBubble extends StatelessWidget { + const AnimatedBubble({ + super.key, + required this.animation, + required this.left, + required this.bottom, + required this.bubble, + }); + + final Animation animation; + final double left; + final double bottom; + final Widget bubble; + + @override + Widget build(BuildContext context) { + return Positioned( + left: left, + bottom: bottom, + child: AnimatedBuilder( + animation: animation, + builder: (context, child) { + return Transform.scale( + scale: animation.value, + alignment: Alignment.bottomLeft, + child: child, + ); + }, + child: bubble, + ), + ); + } +} + +class StatusBubble extends StatelessWidget { + const StatusBubble({ + super.key, + required this.repeatingController, + required this.dotIntervals, + required this.flashingCircleBrightColor, + required this.flashingCircleDarkColor, + required this.bubbleColor, + }); + + final AnimationController repeatingController; + final List dotIntervals; + final Color flashingCircleDarkColor; + final Color flashingCircleBrightColor; + final Color bubbleColor; + + @override + Widget build(BuildContext context) { + return Container( + width: 85, + height: 44, + padding: const EdgeInsets.symmetric(horizontal: 8), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(27), + color: bubbleColor, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + FlashingCircle( + index: 0, + repeatingController: repeatingController, + dotIntervals: dotIntervals, + flashingCircleDarkColor: flashingCircleDarkColor, + flashingCircleBrightColor: flashingCircleBrightColor, + ), + FlashingCircle( + index: 1, + repeatingController: repeatingController, + dotIntervals: dotIntervals, + flashingCircleDarkColor: flashingCircleDarkColor, + flashingCircleBrightColor: flashingCircleBrightColor, + ), + FlashingCircle( + index: 2, + repeatingController: repeatingController, + dotIntervals: dotIntervals, + flashingCircleDarkColor: flashingCircleDarkColor, + flashingCircleBrightColor: flashingCircleBrightColor, + ), + ], + ), + ); + } +} + +class FlashingCircle extends StatelessWidget { + const FlashingCircle({ + super.key, + required this.index, + required this.repeatingController, + required this.dotIntervals, + required this.flashingCircleBrightColor, + required this.flashingCircleDarkColor, + }); + + final int index; + final AnimationController repeatingController; + final List dotIntervals; + final Color flashingCircleDarkColor; + final Color flashingCircleBrightColor; + + @override + Widget build(BuildContext context) { + return AnimatedBuilder( + animation: repeatingController, + builder: (context, child) { + final circleFlashPercent = dotIntervals[index].transform( + repeatingController.value, + ); + final circleColorPercent = sin(pi * circleFlashPercent); + + return Container( + width: 12, + height: 12, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Color.lerp( + flashingCircleDarkColor, + flashingCircleBrightColor, + circleColorPercent, + ), + ), + ); + }, + ); + } +} diff --git a/packages/mediapipe-task-genai/example/lib/widgets/widgets.dart b/packages/mediapipe-task-genai/example/lib/widgets/widgets.dart new file mode 100644 index 00000000..12fb76e7 --- /dev/null +++ b/packages/mediapipe-task-genai/example/lib/widgets/widgets.dart @@ -0,0 +1,5 @@ +export 'chat_input.dart'; +export 'chat_screen.dart'; +export 'configuration_panel.dart'; +export 'keyboard_hider.dart'; +export 'typing_indicator.dart'; diff --git a/packages/mediapipe-task-genai/example/macos/.gitignore b/packages/mediapipe-task-genai/example/macos/.gitignore new file mode 100644 index 00000000..746adbb6 --- /dev/null +++ b/packages/mediapipe-task-genai/example/macos/.gitignore @@ -0,0 +1,7 @@ +# Flutter-related +**/Flutter/ephemeral/ +**/Pods/ + +# Xcode-related +**/dgph +**/xcuserdata/ diff --git a/packages/mediapipe-task-genai/example/macos/Flutter/Flutter-Debug.xcconfig b/packages/mediapipe-task-genai/example/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 00000000..4b81f9b2 --- /dev/null +++ b/packages/mediapipe-task-genai/example/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/packages/mediapipe-task-genai/example/macos/Flutter/Flutter-Release.xcconfig b/packages/mediapipe-task-genai/example/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 00000000..5caa9d15 --- /dev/null +++ b/packages/mediapipe-task-genai/example/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/packages/mediapipe-task-genai/example/macos/Podfile b/packages/mediapipe-task-genai/example/macos/Podfile new file mode 100644 index 00000000..c795730d --- /dev/null +++ b/packages/mediapipe-task-genai/example/macos/Podfile @@ -0,0 +1,43 @@ +platform :osx, '10.14' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_macos_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_macos_build_settings(target) + end +end diff --git a/packages/mediapipe-task-genai/example/macos/Runner.xcodeproj/project.pbxproj b/packages/mediapipe-task-genai/example/macos/Runner.xcodeproj/project.pbxproj new file mode 100644 index 00000000..b9a7bc41 --- /dev/null +++ b/packages/mediapipe-task-genai/example/macos/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,801 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXAggregateTarget section */ + 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; + buildPhases = ( + 33CC111E2044C6BF0003C045 /* ShellScript */, + ); + dependencies = ( + ); + name = "Flutter Assemble"; + productName = FLX; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 0840A2988C170E3AE32FBC89 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4C16FD5380E1BBD75E45BE4A /* Pods_RunnerTests.framework */; }; + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; + 503290BD8A0E6D65BFB5CBD8 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 089D6862692C490299A2FF35 /* Pods_Runner.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC10EC2044A3C60003C045; + remoteInfo = Runner; + }; + 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC111A2044C6BA0003C045; + remoteInfo = FLX; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 33CC110E2044A8840003C045 /* Bundle Framework */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Bundle Framework"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 089D6862692C490299A2FF35 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; + 33CC10ED2044A3C60003C045 /* example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = example.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; + 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; + 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; + 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; + 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 45ADB99756424914F7096B28 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 4C16FD5380E1BBD75E45BE4A /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 7A0F4DF7C13152CF20A44F1E /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 8E7BDD4A3E04EC7267ECF3C3 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; + A78B74BD0EA2460A7DC01FC8 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + B086A9D0F48348164D2AC696 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + F9063451AE4B397B60C1AAF6 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 331C80D2294CF70F00263BE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 0840A2988C170E3AE32FBC89 /* Pods_RunnerTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 503290BD8A0E6D65BFB5CBD8 /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C80D6294CF71000263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C80D7294CF71000263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 33BA886A226E78AF003329D5 /* Configs */ = { + isa = PBXGroup; + children = ( + 33E5194F232828860026EE4D /* AppInfo.xcconfig */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, + ); + path = Configs; + sourceTree = ""; + }; + 33CC10E42044A3C60003C045 = { + isa = PBXGroup; + children = ( + 33FAB671232836740065AC1E /* Runner */, + 33CEB47122A05771004F2AC0 /* Flutter */, + 331C80D6294CF71000263BE5 /* RunnerTests */, + 33CC10EE2044A3C60003C045 /* Products */, + D73912EC22F37F3D000D13A0 /* Frameworks */, + 5556757779CA4F12C5809C7B /* Pods */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* example.app */, + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 33CC11242044D66E0003C045 /* Resources */ = { + isa = PBXGroup; + children = ( + 33CC10F22044A3C60003C045 /* Assets.xcassets */, + 33CC10F42044A3C60003C045 /* MainMenu.xib */, + 33CC10F72044A3C60003C045 /* Info.plist */, + ); + name = Resources; + path = ..; + sourceTree = ""; + }; + 33CEB47122A05771004F2AC0 /* Flutter */ = { + isa = PBXGroup; + children = ( + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, + ); + path = Flutter; + sourceTree = ""; + }; + 33FAB671232836740065AC1E /* Runner */ = { + isa = PBXGroup; + children = ( + 33CC10F02044A3C60003C045 /* AppDelegate.swift */, + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, + 33E51913231747F40026EE4D /* DebugProfile.entitlements */, + 33E51914231749380026EE4D /* Release.entitlements */, + 33CC11242044D66E0003C045 /* Resources */, + 33BA886A226E78AF003329D5 /* Configs */, + ); + path = Runner; + sourceTree = ""; + }; + 5556757779CA4F12C5809C7B /* Pods */ = { + isa = PBXGroup; + children = ( + B086A9D0F48348164D2AC696 /* Pods-Runner.debug.xcconfig */, + 7A0F4DF7C13152CF20A44F1E /* Pods-Runner.release.xcconfig */, + 45ADB99756424914F7096B28 /* Pods-Runner.profile.xcconfig */, + A78B74BD0EA2460A7DC01FC8 /* Pods-RunnerTests.debug.xcconfig */, + 8E7BDD4A3E04EC7267ECF3C3 /* Pods-RunnerTests.release.xcconfig */, + F9063451AE4B397B60C1AAF6 /* Pods-RunnerTests.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; + D73912EC22F37F3D000D13A0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 089D6862692C490299A2FF35 /* Pods_Runner.framework */, + 4C16FD5380E1BBD75E45BE4A /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C80D4294CF70F00263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 397DAC52579912085AA91F5F /* [CP] Check Pods Manifest.lock */, + 331C80D1294CF70F00263BE5 /* Sources */, + 331C80D2294CF70F00263BE5 /* Frameworks */, + 331C80D3294CF70F00263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C80DA294CF71000263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 33CC10EC2044A3C60003C045 /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + C759DA1EB65DE3BD2B73BFF8 /* [CP] Check Pods Manifest.lock */, + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + 4756FDFE3F2DAB1A06C709EF /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* example.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 33CC10E52044A3C60003C045 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C80D4294CF70F00263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 33CC10EC2044A3C60003C045; + }; + 33CC10EC2044A3C60003C045 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Sandbox = { + enabled = 1; + }; + }; + }; + 33CC111A2044C6BA0003C045 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 33CC10E42044A3C60003C045; + productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 33CC10EC2044A3C60003C045 /* Runner */, + 331C80D4294CF70F00263BE5 /* RunnerTests */, + 33CC111A2044C6BA0003C045 /* Flutter Assemble */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C80D3294CF70F00263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EB2044A3C60003C045 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3399D490228B24CF009A79C7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; + }; + 33CC111E2044C6BF0003C045 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + Flutter/ephemeral/FlutterInputs.xcfilelist, + ); + inputPaths = ( + Flutter/ephemeral/tripwire, + ); + outputFileListPaths = ( + Flutter/ephemeral/FlutterOutputs.xcfilelist, + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; + }; + 397DAC52579912085AA91F5F /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 4756FDFE3F2DAB1A06C709EF /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + C759DA1EB65DE3BD2B73BFF8 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C80D1294CF70F00263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10E92044A3C60003C045 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC10EC2044A3C60003C045 /* Runner */; + targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; + }; + 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; + targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 33CC10F52044A3C60003C045 /* Base */, + ); + name = MainMenu.xib; + path = Runner; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 331C80DB294CF71000263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A78B74BD0EA2460A7DC01FC8 /* Pods-RunnerTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/example"; + }; + name = Debug; + }; + 331C80DC294CF71000263BE5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 8E7BDD4A3E04EC7267ECF3C3 /* Pods-RunnerTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/example"; + }; + name = Release; + }; + 331C80DD294CF71000263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F9063451AE4B397B60C1AAF6 /* Pods-RunnerTests.profile.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/example"; + }; + name = Profile; + }; + 338D0CE9231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + 338D0CEA231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Profile; + }; + 338D0CEB231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + 33CC10F92044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 33CC10FA2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 33CC10FC2044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 33CC10FD2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 33CC111C2044C6BA0003C045 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 33CC111D2044C6BA0003C045 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C80DB294CF71000263BE5 /* Debug */, + 331C80DC294CF71000263BE5 /* Release */, + 331C80DD294CF71000263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10F92044A3C60003C045 /* Debug */, + 33CC10FA2044A3C60003C045 /* Release */, + 338D0CE9231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10FC2044A3C60003C045 /* Debug */, + 33CC10FD2044A3C60003C045 /* Release */, + 338D0CEA231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC111C2044C6BA0003C045 /* Debug */, + 33CC111D2044C6BA0003C045 /* Release */, + 338D0CEB231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/packages/mediapipe-task-genai/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/mediapipe-task-genai/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/packages/mediapipe-task-genai/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/mediapipe-task-genai/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/mediapipe-task-genai/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 00000000..15368ecc --- /dev/null +++ b/packages/mediapipe-task-genai/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/mediapipe-task-genai/example/macos/Runner.xcworkspace/contents.xcworkspacedata b/packages/mediapipe-task-genai/example/macos/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..21a3cc14 --- /dev/null +++ b/packages/mediapipe-task-genai/example/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/packages/mediapipe-task-genai/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/mediapipe-task-genai/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/packages/mediapipe-task-genai/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/mediapipe-task-genai/example/macos/Runner/AppDelegate.swift b/packages/mediapipe-task-genai/example/macos/Runner/AppDelegate.swift new file mode 100644 index 00000000..8e02df28 --- /dev/null +++ b/packages/mediapipe-task-genai/example/macos/Runner/AppDelegate.swift @@ -0,0 +1,9 @@ +import Cocoa +import FlutterMacOS + +@main +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } +} diff --git a/packages/mediapipe-task-genai/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/mediapipe-task-genai/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..a2ec33f1 --- /dev/null +++ b/packages/mediapipe-task-genai/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_16.png", + "scale" : "1x" + }, + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "2x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "1x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_64.png", + "scale" : "2x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_128.png", + "scale" : "1x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "2x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "1x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "1x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_1024.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/packages/mediapipe-task-genai/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/packages/mediapipe-task-genai/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 00000000..82b6f9d9 Binary files /dev/null and b/packages/mediapipe-task-genai/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/packages/mediapipe-task-genai/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/packages/mediapipe-task-genai/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png new file mode 100644 index 00000000..13b35eba Binary files /dev/null and b/packages/mediapipe-task-genai/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/packages/mediapipe-task-genai/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/packages/mediapipe-task-genai/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png new file mode 100644 index 00000000..0a3f5fa4 Binary files /dev/null and b/packages/mediapipe-task-genai/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/packages/mediapipe-task-genai/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/packages/mediapipe-task-genai/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png new file mode 100644 index 00000000..bdb57226 Binary files /dev/null and b/packages/mediapipe-task-genai/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/packages/mediapipe-task-genai/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/packages/mediapipe-task-genai/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png new file mode 100644 index 00000000..f083318e Binary files /dev/null and b/packages/mediapipe-task-genai/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/packages/mediapipe-task-genai/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/packages/mediapipe-task-genai/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png new file mode 100644 index 00000000..326c0e72 Binary files /dev/null and b/packages/mediapipe-task-genai/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/packages/mediapipe-task-genai/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/packages/mediapipe-task-genai/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png new file mode 100644 index 00000000..2f1632cf Binary files /dev/null and b/packages/mediapipe-task-genai/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/packages/mediapipe-task-genai/example/macos/Runner/Base.lproj/MainMenu.xib b/packages/mediapipe-task-genai/example/macos/Runner/Base.lproj/MainMenu.xib new file mode 100644 index 00000000..80e867a4 --- /dev/null +++ b/packages/mediapipe-task-genai/example/macos/Runner/Base.lproj/MainMenu.xib @@ -0,0 +1,343 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/mediapipe-task-genai/example/macos/Runner/Configs/AppInfo.xcconfig b/packages/mediapipe-task-genai/example/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 00000000..92fb3cd5 --- /dev/null +++ b/packages/mediapipe-task-genai/example/macos/Runner/Configs/AppInfo.xcconfig @@ -0,0 +1,14 @@ +// Application-level settings for the Runner target. +// +// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the +// future. If not, the values below would default to using the project name when this becomes a +// 'flutter create' template. + +// The application's name. By default this is also the title of the Flutter window. +PRODUCT_NAME = example + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = com.example.example + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright © 2024 com.example. All rights reserved. diff --git a/packages/mediapipe-task-genai/example/macos/Runner/Configs/Debug.xcconfig b/packages/mediapipe-task-genai/example/macos/Runner/Configs/Debug.xcconfig new file mode 100644 index 00000000..36b0fd94 --- /dev/null +++ b/packages/mediapipe-task-genai/example/macos/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/packages/mediapipe-task-genai/example/macos/Runner/Configs/Release.xcconfig b/packages/mediapipe-task-genai/example/macos/Runner/Configs/Release.xcconfig new file mode 100644 index 00000000..dff4f495 --- /dev/null +++ b/packages/mediapipe-task-genai/example/macos/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/packages/mediapipe-task-genai/example/macos/Runner/Configs/Warnings.xcconfig b/packages/mediapipe-task-genai/example/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 00000000..42bcbf47 --- /dev/null +++ b/packages/mediapipe-task-genai/example/macos/Runner/Configs/Warnings.xcconfig @@ -0,0 +1,13 @@ +WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_PRAGMA_PACK = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +GCC_WARN_STRICT_SELECTOR_MATCH = YES +CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +GCC_WARN_SHADOW = YES +CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/packages/mediapipe-task-genai/example/macos/Runner/DebugProfile.entitlements b/packages/mediapipe-task-genai/example/macos/Runner/DebugProfile.entitlements new file mode 100644 index 00000000..9cd98f2b --- /dev/null +++ b/packages/mediapipe-task-genai/example/macos/Runner/DebugProfile.entitlements @@ -0,0 +1,18 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.files.downloads.read-only + + com.apple.security.files.user-selected.read-only + + com.apple.security.network.client + + com.apple.security.network.server + + + diff --git a/packages/mediapipe-task-genai/example/macos/Runner/Info.plist b/packages/mediapipe-task-genai/example/macos/Runner/Info.plist new file mode 100644 index 00000000..4789daa6 --- /dev/null +++ b/packages/mediapipe-task-genai/example/macos/Runner/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/packages/mediapipe-task-genai/example/macos/Runner/MainFlutterWindow.swift b/packages/mediapipe-task-genai/example/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 00000000..3cc05eb2 --- /dev/null +++ b/packages/mediapipe-task-genai/example/macos/Runner/MainFlutterWindow.swift @@ -0,0 +1,15 @@ +import Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/packages/mediapipe-task-genai/example/macos/Runner/Release.entitlements b/packages/mediapipe-task-genai/example/macos/Runner/Release.entitlements new file mode 100644 index 00000000..852fa1a4 --- /dev/null +++ b/packages/mediapipe-task-genai/example/macos/Runner/Release.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.security.app-sandbox + + + diff --git a/packages/mediapipe-task-genai/example/macos/RunnerTests/RunnerTests.swift b/packages/mediapipe-task-genai/example/macos/RunnerTests/RunnerTests.swift new file mode 100644 index 00000000..61f3bd1f --- /dev/null +++ b/packages/mediapipe-task-genai/example/macos/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Cocoa +import FlutterMacOS +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/packages/mediapipe-task-genai/example/pubspec.yaml b/packages/mediapipe-task-genai/example/pubspec.yaml new file mode 100644 index 00000000..86bcbb39 --- /dev/null +++ b/packages/mediapipe-task-genai/example/pubspec.yaml @@ -0,0 +1,62 @@ +name: example +description: "A new Flutter project." +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: "none" # Remove this line if you wish to publish to pub.dev + +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# In Android, build-name is used as versionName while build-number used as versionCode. +# Read more about Android versioning at https://developer.android.com/studio/publish/versioning +# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +# In Windows, build-name is used as the major, minor, and patch parts +# of the product and file versions while build-number is used as the build suffix. +version: 1.0.0+1 + +environment: + sdk: ">=3.5.0-6.0.dev <4.0.0" + +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + adaptive_dialog: ^2.1.0 + chat_bubbles: ^1.6.0 + cupertino_icons: ^1.0.6 + flutter: + sdk: flutter + flutter_bloc: ^8.1.5 + flutter_localizations: + sdk: flutter + freezed_annotation: ^2.4.1 + getwidget: ^4.0.0 + http: ^1.2.1 + intl: ^0.19.0 + logging: ^1.2.0 + mediapipe_core: + path: ../../mediapipe-core + mediapipe_genai: + path: ../ + path: ^1.9.0 + path_provider: ^2.1.3 + provider: ^6.1.2 + shimmer: ^3.0.0 + uuid: ^4.4.0 + +dev_dependencies: + build_runner: ^2.4.9 + flutter_lints: ^3.0.0 + flutter_test: + sdk: flutter + freezed: ^2.5.2 + +flutter: + uses-material-design: true diff --git a/packages/mediapipe-task-genai/example/test/chat_message_test.dart b/packages/mediapipe-task-genai/example/test/chat_message_test.dart new file mode 100644 index 00000000..3c44c8ca --- /dev/null +++ b/packages/mediapipe-task-genai/example/test/chat_message_test.dart @@ -0,0 +1,24 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:example/models/models.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +void main() { + group('ChatMessage should', () { + test('have collection equality', () { + final message = ChatMessage.user(''); + final message2 = ChatMessage.llm(''); + expect( + const DeepCollectionEquality().hash([message]), + const DeepCollectionEquality().hash([message]), + ); + expect( + const DeepCollectionEquality().hash([message]), + isNot(equals(const DeepCollectionEquality().hash([message2]))), + ); + }); + }); +} diff --git a/packages/mediapipe-task-genai/example/test/transcript_state_test.dart b/packages/mediapipe-task-genai/example/test/transcript_state_test.dart new file mode 100644 index 00000000..16fe1797 --- /dev/null +++ b/packages/mediapipe-task-genai/example/test/transcript_state_test.dart @@ -0,0 +1,61 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:example/models/models.dart'; +import 'package:example/bloc.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('TranscriptState should', () { + test('have a new hash code after receiving a new message', () { + final initial = TranscriptState.initial(); + final updated = initial.addMessage( + ChatMessage.user('Hello'), + LlmModel.gemma4bCpu, + ); + expect(initial.transcript, isNot(equals(updated.transcript))); + expect( + initial.transcript.hashCode, + isNot(equals(updated.transcript.hashCode)), + ); + expect(initial.hashCode, isNot(equals(updated.hashCode))); + }); + + test('have a new hash code after extending a message', () { + var initial = TranscriptState.initial(); + initial = initial.addMessage( + ChatMessage.llm('Hello'), + LlmModel.gemma4bCpu, + ); + final updated = initial.extendMessage( + ', world!', + index: 0, + model: LlmModel.gemma4bCpu, + first: false, + last: true, + ); + expect(initial.transcript, isNot(equals(updated.transcript))); + expect( + initial.transcript.hashCode, + isNot(equals(updated.transcript.hashCode)), + ); + expect(initial.hashCode, isNot(equals(updated.hashCode))); + }); + + test('have a new hash code after extending a message', () { + var initial = TranscriptState.initial(); + initial = initial.addMessage( + ChatMessage.llm('Hello'), + LlmModel.gemma4bCpu, + ); + final updated = initial.completeMessage(LlmModel.gemma4bCpu); + expect(initial.transcript, isNot(equals(updated.transcript))); + expect( + initial.transcript.hashCode, + isNot(equals(updated.transcript.hashCode)), + ); + expect(initial.hashCode, isNot(equals(updated.hashCode))); + }); + }); +} diff --git a/packages/mediapipe-task-genai/ffigen.yaml b/packages/mediapipe-task-genai/ffigen.yaml new file mode 100644 index 00000000..89c0c9ee --- /dev/null +++ b/packages/mediapipe-task-genai/ffigen.yaml @@ -0,0 +1,23 @@ +name: "MediaPipeGenAiBindings" +description: "Bindings for MediaPipe GenAI tasks" +output: + bindings: "lib/src/io/third_party/mediapipe/generated/mediapipe_genai_bindings.dart" +headers: + entry-points: + - "third_party/mediapipe/tasks/cc/**.h" +preamble: | + /* Copyright 2023 The MediaPipe Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + ==============================================================================*/ +ffi-native: diff --git a/packages/mediapipe-task-genai/lib/interface.dart b/packages/mediapipe-task-genai/lib/interface.dart new file mode 100644 index 00000000..544bbeb0 --- /dev/null +++ b/packages/mediapipe-task-genai/lib/interface.dart @@ -0,0 +1,5 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +export 'src/interface/interface.dart'; diff --git a/packages/mediapipe-task-genai/lib/io.dart b/packages/mediapipe-task-genai/lib/io.dart new file mode 100644 index 00000000..b8ae97da --- /dev/null +++ b/packages/mediapipe-task-genai/lib/io.dart @@ -0,0 +1,5 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +export 'src/io/mediapipe_genai.dart'; diff --git a/packages/mediapipe-task-genai/lib/mediapipe_genai.dart b/packages/mediapipe-task-genai/lib/mediapipe_genai.dart new file mode 100644 index 00000000..c02332ff --- /dev/null +++ b/packages/mediapipe-task-genai/lib/mediapipe_genai.dart @@ -0,0 +1,11 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/// Package containing core dependencies for MediaPipe's text, vision, and +/// audio-based tasks. +library mediapipe_core; + +export 'universal_mediapipe_genai.dart' + if (dart.library.html) 'src/web/mediapipe_genai.dart' + if (dart.library.io) 'src/io/mediapipe_genai.dart'; diff --git a/packages/mediapipe-task-genai/lib/src/interface/interface.dart b/packages/mediapipe-task-genai/lib/src/interface/interface.dart new file mode 100644 index 00000000..c1e2551b --- /dev/null +++ b/packages/mediapipe-task-genai/lib/src/interface/interface.dart @@ -0,0 +1,5 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +export 'tasks/tasks.dart'; diff --git a/packages/mediapipe-task-genai/lib/src/interface/tasks/llm_inference/llm_inference.dart b/packages/mediapipe-task-genai/lib/src/interface/tasks/llm_inference/llm_inference.dart new file mode 100644 index 00000000..ef3ceb26 --- /dev/null +++ b/packages/mediapipe-task-genai/lib/src/interface/tasks/llm_inference/llm_inference.dart @@ -0,0 +1,7 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +export 'llm_inference_engine.dart'; +export 'llm_response_context.dart'; +export 'task_options.dart'; diff --git a/packages/mediapipe-task-genai/lib/src/interface/tasks/llm_inference/llm_inference_engine.dart b/packages/mediapipe-task-genai/lib/src/interface/tasks/llm_inference/llm_inference_engine.dart new file mode 100644 index 00000000..64cac6d2 --- /dev/null +++ b/packages/mediapipe-task-genai/lib/src/interface/tasks/llm_inference/llm_inference_engine.dart @@ -0,0 +1,19 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/// {@template LlmInferenceEngine} +/// Utility to query an LLM with a prompt and receive its response as a stream. +/// {@endtemplate} +abstract class BaseLlmInferenceEngine { + /// {@template generateResponse} + /// Generates a response based on the input text. + /// {@endtemplate} + Stream generateResponse(String text); + + /// {@template sizeInTokens} + /// Runs an invocation of only the tokenization for the LLM, and returns the + /// size (in tokens) of the result. + /// {@endtemplate} + Future sizeInTokens(String text); +} diff --git a/packages/mediapipe-task-genai/lib/src/interface/tasks/llm_inference/llm_response_context.dart b/packages/mediapipe-task-genai/lib/src/interface/tasks/llm_inference/llm_response_context.dart new file mode 100644 index 00000000..f82f89d9 --- /dev/null +++ b/packages/mediapipe-task-genai/lib/src/interface/tasks/llm_inference/llm_response_context.dart @@ -0,0 +1,24 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:mediapipe_core/interface.dart'; + +/// {@template LlmResponseContext} +/// Represents all of or part of an LLM's response to a query. +/// {@endtemplate} +abstract class BaseLlmResponseContext extends TaskResult { + /// The core of the LLM's response from this query. If the asynchronous + /// API is used, this [response] value should be chained with subsequent + /// values until the LLM emits one with [isDone] set to true. + List get responseArray; + + /// Indicates when an LLM is done responding. Only useful when calling the + /// asynchronous methods. + bool get isDone; + + @override + String toString() { + return '$runtimeType(responseArray=$responseArray, isDone: $isDone)'; + } +} diff --git a/packages/mediapipe-task-genai/lib/src/interface/tasks/llm_inference/task_options.dart b/packages/mediapipe-task-genai/lib/src/interface/tasks/llm_inference/task_options.dart new file mode 100644 index 00000000..8499cefa --- /dev/null +++ b/packages/mediapipe-task-genai/lib/src/interface/tasks/llm_inference/task_options.dart @@ -0,0 +1,73 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:mediapipe_core/interface.dart'; + +/// {@template LlmInferenceOptions} +/// Configuration object for a MediaPipe text classifier. +/// +/// See also: +/// * [MediaPipe's LlmInferenceOptions documentation](https://developers.google.com/mediapipe/api/solutions/java/com/google/mediapipe/tasks/genai/llminference/LlmInference.LlmInferenceOptions) +/// {@endtemplate} +abstract class BaseLlmInferenceOptions extends Options { + /// The path that points to the tflite model file to use for inference. + String get modelPath; + + /// Directory path for storing model related tokenizer and cache weights. The + /// user is responsible for providing the directory that can be writable by the + /// program. Used by CPU only. + String get cacheDir; + + /// Path to the LoRA tflite flatbuffer file. Optional (default is empty string). + /// This is only compatible with GPU models. + String get loraPath; + + /// Sequence batch size for encoding. Used by GPU only. Number of input tokens + /// to process at a time for batch processing. Setting this value to 1 means + /// both the encoding and decoding share the same graph of sequence length + /// of 1. Setting this value to 0 means the batch size will be optimized + /// programmatically. + int get sequenceBatchSize; + + /// Number of decode steps per sync. Used by GPU only. The default value is 3. + int get decodeStepsPerSync; + + /// The total length of the kv-cache. + int get maxTokens; + + /// Random seed for sampling tokens. + int get randomSeed; + + /// Randomness when decoding the next token. + double get temperature; + + /// Top K number of tokens to be sampled from for each decoding step. + int get topK; + + @override + List get props => [ + modelPath, + cacheDir, + loraPath, + sequenceBatchSize, + decodeStepsPerSync, + maxTokens, + randomSeed, + temperature, + topK, + ]; + + @override + String toString() => '$runtimeType(' + 'modelPath: $modelPath, ' + 'cacheDir: $cacheDir, ' + 'loraPath: $loraPath, ' + 'sequenceBatchSize: $sequenceBatchSize, ' + 'decodeStepsPerSync: $decodeStepsPerSync, ' + 'maxTokens: $maxTokens, ' + 'randomSeed: $randomSeed, ' + 'temperature: $temperature, ' + 'topK: $topK' + ')'; +} diff --git a/packages/mediapipe-task-genai/lib/src/interface/tasks/tasks.dart b/packages/mediapipe-task-genai/lib/src/interface/tasks/tasks.dart new file mode 100644 index 00000000..e64889b5 --- /dev/null +++ b/packages/mediapipe-task-genai/lib/src/interface/tasks/tasks.dart @@ -0,0 +1 @@ +export 'llm_inference/llm_inference.dart'; diff --git a/packages/mediapipe-task-genai/lib/src/io/mediapipe_genai.dart b/packages/mediapipe-task-genai/lib/src/io/mediapipe_genai.dart new file mode 100644 index 00000000..c1e2551b --- /dev/null +++ b/packages/mediapipe-task-genai/lib/src/io/mediapipe_genai.dart @@ -0,0 +1,5 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +export 'tasks/tasks.dart'; diff --git a/packages/mediapipe-task-genai/lib/src/io/tasks/llm_inference/llm_inference.dart b/packages/mediapipe-task-genai/lib/src/io/tasks/llm_inference/llm_inference.dart new file mode 100644 index 00000000..da07113e --- /dev/null +++ b/packages/mediapipe-task-genai/lib/src/io/tasks/llm_inference/llm_inference.dart @@ -0,0 +1,8 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +export 'llm_inference_engine.dart'; +export 'llm_inference_executor.dart'; +export 'llm_response_context.dart'; +export 'task_options.dart'; diff --git a/packages/mediapipe-task-genai/lib/src/io/tasks/llm_inference/llm_inference_engine.dart b/packages/mediapipe-task-genai/lib/src/io/tasks/llm_inference/llm_inference_engine.dart new file mode 100644 index 00000000..1ad89a29 --- /dev/null +++ b/packages/mediapipe-task-genai/lib/src/io/tasks/llm_inference/llm_inference_engine.dart @@ -0,0 +1,271 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; +import 'dart:io' as io; +import 'dart:isolate'; +import 'package:async/async.dart'; +import 'package:logging/logging.dart'; +import 'package:mediapipe_core/mediapipe_core.dart'; +import 'package:mediapipe_genai/interface.dart'; +import 'package:mediapipe_genai/io.dart'; + +final _log = Logger('LlmInferenceEngine'); + +/// {@macro LlmInferenceEngine} +class LlmInferenceEngine extends BaseLlmInferenceEngine { + /// {@macro LlmInferenceEngine} + LlmInferenceEngine( + this._options, { + this.timeout = const Duration(seconds: 10), + this.maxRetries = 2, + }) : _readyCompleter = Completer() { + _initializeIsolate(); + } + + final LlmInferenceOptions _options; + StreamController? _responseController; + + /// Length of time the Engine is willing to wait for a response from + /// `generateResponse` before assuming that the inference task has failed + /// and should be resumed. + final Duration timeout; + + /// Number of times to invoke the timeout loop before giving up. + final int maxRetries; + + /// Incremented each time [restart] is called, and throws an exception if this + /// equals [maxRetries] in [restart]. + int _numRetries = 0; + + late SendPort _sendPort; + late StreamQueue _events; + Completer _readyCompleter; + Future get _ready => _readyCompleter.future; + + Future _initializeIsolate() { + _createIsolate(_options).then((results) { + _events = results.$1; + _sendPort = results.$2; + _readyCompleter.complete(true); + }); + return _ready; + } + + /// {@macro generateResponse} + /// + /// Creates a [StreamController] for the response, then delegates to + /// [_tryResponse] which adds retry logic to the task. + @override + Stream generateResponse(String text) { + _log.fine('Generating response to "${text.shorten()}"'); + assert( + _responseController == null, + 'Should not call `generateResponse` while previous controller is ' + 'still active.', + ); + _responseController = StreamController(); + _tryResponse(text); + return _responseController!.stream; + } + + /// Inner workhorse for [generateResponse] which assumes a [StreamController] + /// already exists and adds retry logic to the actual call to MediaPipe which + /// is found at [_publishInference]. + void _tryResponse(String text) { + assert( + _responseController != null, + 'Should not call `_retryResponse` without an active controller', + ); + + if (_numRetries > 0) { + _log.info('Retrying for response to "${text.shorten()}"'); + } + _ready.then((bool success) { + if (!success) { + _log.shout('Unable to create inference engine'); + return; + } + final responseTimeout = Timer( + timeout, + () => restart(() => _tryResponse(text)), + ); + _publishInference( + text, + publish: (String value) { + _numRetries = 0; + responseTimeout.cancel(); + _responseController!.add(value); + }, + ); + }); + } + + /// Sends the text to the isolate running the MediaPipe inference engine, + /// listens for responses, and passes them to the [publish] callback. + void _publishInference( + String text, { + required void Function(String) publish, + }) async { + _sendPort.send(_LlmInferenceTask.respond(text)); + while (true) { + final response = await _events.next; + + if (response is LlmResponseContext) { + publish(response.responseArray.join('')); + if (response.isDone) { + _endResponse(); + } + } else if (response is String) { + _log.fine(response); + } else { + throw Exception( + 'Unexpected sizeInTokens result of type ${response.runtimeType} : $response', + ); + } + } + } + + /// Terminates an in-progress query, closing down the stream. + void cancel() {} + + /// Releases all native resources and closes any open streams. + void dispose() { + _endResponse(); + _sendPort.send(null); + } + + void _endResponse() { + _responseController?.close(); + _responseController = null; + } + + /// Powers down the executor and isolate and then powers up another one and + /// invokes the callback once that replacement executor is awake. + /// + /// Throws an exception if [_numRetries] == [maxRetries]. + /// + /// Do not call [dispose] in this method, as that will close the response + /// controller which we must reuse, because whoever originally called + /// [generateResponse] already has its stream and is waiting for a response. + void restart(void Function() callback) { + _log.shout('RESTARTING INFERENCE EXECUTOR - REACHED TIMEOUT OF ' + '${timeout.inSeconds} seconds'); + _sendPort.send(null); + if (_numRetries == maxRetries) { + throw Exception('Reached retry limit of $maxRetries.'); + } + _numRetries++; + if (!_readyCompleter.isCompleted) { + _readyCompleter.complete(false); + } + _readyCompleter = Completer(); + _initializeIsolate().then( + (bool success) { + if (!success) { + _log.shout('Failed to initialize isolate during restart'); + return; + } + callback(); + }, + ); + } + + @override + Future sizeInTokens(String text) async { + _log.fine('Counting tokens of "${text.shorten()}"'); + await _ready; + _sendPort.send(_LlmInferenceTask.countTokens(text)); + while (true) { + final response = await _events.next; + if (response is int) { + return response; + } else if (response is String) { + _log.fine(response); + } else { + throw Exception( + 'Unexpected sizeInTokens result of type ${response.runtimeType} : $response', + ); + } + } + } +} + +Future<(StreamQueue, SendPort)> _createIsolate( + LlmInferenceOptions options, +) async { + final p = ReceivePort(); + await Isolate.spawn( + (SendPort port) => _inferenceService( + port, + options, + ), + p.sendPort, + ); + + final events = StreamQueue(p); + final SendPort sendPort = await events.next; + return (events, sendPort); +} + +Future _inferenceService( + SendPort p, + LlmInferenceOptions options, +) async { + final commandPort = ReceivePort(); + p.send(commandPort.sendPort); + + Logger.root.level = Level.FINEST; + Logger.root.onRecord.listen((record) { + io.stdout.writeln('${record.level.name} [${record.loggerName}]' + '[' + '${record.time.hour.toString()}:' + '${record.time.minute.toString().padLeft(2, "0")}:' + '${record.time.second.toString().padLeft(2, "0")}.' + '${record.time.millisecond.toString().padRight(3, "0")}' + '] ${record.message}'); + }); + + final executor = LlmInferenceExecutor(options); + + await for (final _LlmInferenceTask? message in commandPort) { + if (message != null) { + switch (message.type) { + case _LlmInferenceTaskType._respond: + await for (final response + in executor.generateResponse(message.text)) { + p.send(response); + } + case _LlmInferenceTaskType._countTokens: + p.send(executor.sizeInTokens(message.text)); + } + } else { + break; + } + } + executor.dispose(); + Isolate.exit(); +} + +enum _LlmInferenceTaskType { _respond, _countTokens } + +class _LlmInferenceTask { + _LlmInferenceTask._({ + required this.type, + required this.text, + }); + + factory _LlmInferenceTask.respond(String text) => _LlmInferenceTask._( + type: _LlmInferenceTaskType._respond, + text: text, + ); + + factory _LlmInferenceTask.countTokens(String text) => _LlmInferenceTask._( + type: _LlmInferenceTaskType._respond, + text: text, + ); + + final _LlmInferenceTaskType type; + final String text; +} diff --git a/packages/mediapipe-task-genai/lib/src/io/tasks/llm_inference/llm_inference_executor.dart b/packages/mediapipe-task-genai/lib/src/io/tasks/llm_inference/llm_inference_executor.dart new file mode 100644 index 00000000..ad6c7e36 --- /dev/null +++ b/packages/mediapipe-task-genai/lib/src/io/tasks/llm_inference/llm_inference_executor.dart @@ -0,0 +1,158 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; +import 'dart:ffi'; +import 'package:ffi/ffi.dart'; +import 'package:logging/logging.dart'; +import 'package:mediapipe_core/io.dart'; +import 'package:mediapipe_genai/io.dart'; +import 'package:mediapipe_genai/src/io/third_party/mediapipe/generated/mediapipe_genai_bindings.dart' + as bindings; + +final _log = Logger('LlmInferenceExecutor'); + +/// Shape of the function MediaPipe calls with each additional response chunk +/// from the LLM. +typedef LlmResponseCallback = Void Function( + Pointer, + Pointer, +); + +/// {@template LlmInferenceExecutor} +/// Executes MediaPipe's inference task. +/// +/// {@macro TaskExecutor} +/// {@endtemplate} +class LlmInferenceExecutor extends TaskExecutor { + /// {@macro LlmInferenceExecutor} + LlmInferenceExecutor(super.options); + + StreamController? _responseController; + + @override + String taskName = 'Inference'; + + @override + int closeWorker(Pointer worker, Pointer> error) { + bindings.LlmInferenceEngine_Session_Delete(worker); + return 0; + } + + @override + Pointer createResultsPointer() => + throw UnimplementedError( + 'LlmResponseContext structs should only be created in native code', + ); + + // Holding onto the value created by `createWorker` for disposal. + Pointer? _worker; + + @override + Pointer createWorker( + Pointer options, + Pointer> error, + ) { + final worker = malloc>(); + bindings.LlmInferenceEngine_CreateSession(options, worker, error); + _worker = worker.value.cast(); + return _worker!; + } + + ///{@macro generateResponse} + Stream generateResponse(String text) { + assert(() { + if (_responseController != null) { + throw Exception( + 'Asked for response before completing response from ' + 'previous query, or cancelling previous query.', + ); + } + return true; + }()); + _responseController = StreamController(); + final textPtr = text.copyToNative(); + final callback = NativeCallable.listener( + ( + Pointer context, + Pointer responseContext, + ) { + if (_responseController == null) { + // Short-circuit if the caller has cancelled this query before receiving + // the complete output. + return; + } + // Not often, but also not never, `nullptr` seems to arrive here, which + // breaks everything if not caught and discarded. + if (responseContext == nullptr) { + _log.warning('Discarding unexpected nullptr from PredictAsync'); + return; + } + _responseController!.add( + // Ideally this would pass the raw pointer to the + // LlmResponseContext.native() constructor and rely on + // LlmResponseContext.dispose() for cleanup, but passing pointers + // between threads does not work. + LlmResponseContext( + responseArray: responseContext.ref.response_array + .toDartStrings(responseContext.ref.response_count), + isDone: responseContext.ref.done, + ), + ); + bindings.LlmInferenceEngine_CloseResponseContext(responseContext); + if (responseContext.ref.done) { + malloc.free(textPtr); + _finalizeResponse(); + } + }, + ); + bindings.LlmInferenceEngine_Session_PredictAsync( + worker, + nullptr, + textPtr, + callback.nativeFunction, + ); + return _responseController!.stream; + } + + /// Terminates an in-progress query, closing down the stream. + void cancel() { + assert(() { + if (_responseController != null) { + throw Exception('Attempted to cancel a response that is not open.'); + } + return true; + }()); + _finalizeResponse(); + } + + void _finalizeResponse() { + _responseController!.close(); + _responseController = null; + } + + /// Releases all native resources and closes any open streams. + void dispose() { + if (_worker != null) { + bindings.LlmInferenceEngine_Session_Delete(_worker!); + } + if (_responseController != null) { + _finalizeResponse(); + } + } + + /// {@macro sizeInTokens} + int sizeInTokens(String text) { + final textPtr = text.copyToNative(); + final errorMessageMemory = calloc>(); + final size = bindings.LlmInferenceEngine_Session_SizeInTokens( + worker, + textPtr, + errorMessageMemory, + ); + handleErrorMessage(errorMessageMemory); + return size; + } +} diff --git a/packages/mediapipe-task-genai/lib/src/io/tasks/llm_inference/llm_response_context.dart b/packages/mediapipe-task-genai/lib/src/io/tasks/llm_inference/llm_response_context.dart new file mode 100644 index 00000000..07c60e62 --- /dev/null +++ b/packages/mediapipe-task-genai/lib/src/io/tasks/llm_inference/llm_response_context.dart @@ -0,0 +1,84 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:ffi'; +import 'package:logging/logging.dart'; +import 'package:mediapipe_core/io.dart'; +import 'package:mediapipe_genai/interface.dart'; +import '../../third_party/mediapipe/generated/mediapipe_genai_bindings.dart' + as bindings; + +final _log = Logger('LlmResponseContext'); + +/// {@macro LlmResponseContext} +class LlmResponseContext extends BaseLlmResponseContext with IOTaskResult { + /// {@macro LlmResponseContext.fake} + LlmResponseContext( + {required List responseArray, required bool isDone}) + : _responseArray = responseArray, + _isDone = isDone; + + /// {@template LlmResponseContext.native} + /// Initializes a [LlmResponseContext] instance as a wrapper around native + /// memory. + /// + /// See also: + /// * [LlmInferenceEngine.generateResponse] where this is called. + /// {@endtemplate} + LlmResponseContext.native(this._pointer); + + Pointer? _pointer; + + List? _responseArray; + @override + List get responseArray => _responseArray ??= _getResponseArray(); + List _getResponseArray() { + if (_pointer.isNullOrNullPointer) { + throw Exception( + 'No native memory for LlmResponseContext.responseArray', + ); + } + // Should be able to be removed after b/339661277 + if (_pointer!.ref.response_array == nullptr) { + _log.warning('LlmResponseContext.response_array unexpectedly nullptr'); + return ['']; + } + return _pointer!.ref.response_array.toDartStrings( + _pointer!.ref.response_count, + ); + } + + bool? _isDone; + @override + bool get isDone => _isDone ??= _getIsDone(); + bool _getIsDone() { + if (_pointer.isNullOrNullPointer) { + throw Exception( + 'No native memory for LlmResponseContext.isDone', + ); + } + return _pointer!.ref.done; + } + + @override + void dispose() { + assert(() { + if (isClosed) { + throw Exception( + 'A LlmResponseContext was closed after it had already been closed. ' + 'LlmResponseContext objects should only be closed when they are at' + 'their end of life and will never be used again.', + ); + } + return true; + }()); + if (_pointer != null) { + // Only call the native finalizer if there actually is native memory, + // because tests may verify that faked results are also closed and calling + // this method in that scenario would cause a segfault. + bindings.LlmInferenceEngine_CloseResponseContext(_pointer!); + } + super.dispose(); + } +} diff --git a/packages/mediapipe-task-genai/lib/src/io/tasks/llm_inference/task_options.dart b/packages/mediapipe-task-genai/lib/src/io/tasks/llm_inference/task_options.dart new file mode 100644 index 00000000..46b98460 --- /dev/null +++ b/packages/mediapipe-task-genai/lib/src/io/tasks/llm_inference/task_options.dart @@ -0,0 +1,119 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:ffi'; +import 'dart:math'; +import 'package:ffi/ffi.dart'; +import 'package:mediapipe_core/io.dart'; +import 'package:mediapipe_genai/interface.dart'; + +import '../../third_party/mediapipe/generated/mediapipe_genai_bindings.dart' + as bindings; + +/// {@macro LlmInferenceOptions} +/// +/// This io-friendly implementation is not immutable to track whether the +/// native memory has been created and ultimately released. All values used by +/// pkg:equatable are in fact immutable. +// ignore: must_be_immutable +class LlmInferenceOptions extends BaseLlmInferenceOptions + with TaskOptions { + /// {@macro LlmInferenceOptions.cpu} + LlmInferenceOptions.cpu({ + required this.modelPath, + required this.cacheDir, + required this.maxTokens, + required this.temperature, + required this.topK, + int? randomSeed, + }) : loraPath = '', + sequenceBatchSize = 0, + decodeStepsPerSync = 0, + randomSeed = randomSeed ?? Random().nextInt(1 << 32); + + /// {@macro LlmInferenceOptions.gpu} + LlmInferenceOptions.gpu({ + required this.modelPath, + required this.sequenceBatchSize, + required this.maxTokens, + required this.temperature, + required this.topK, + this.decodeStepsPerSync = 3, + int? randomSeed, + }) : cacheDir = '', + loraPath = '', + randomSeed = randomSeed ?? Random().nextInt(1 << 32); + + Pointer? _pointer; + + @override + final String modelPath; + + @override + final String cacheDir; + + @override + final String loraPath; + + @override + final int sequenceBatchSize; + + @override + final int decodeStepsPerSync; + + @override + final int maxTokens; + + @override + final int randomSeed; + + @override + final double temperature; + + @override + final int topK; + + /// Copies this options object into native memory for use by an engine. + Pointer copyToNative() { + _pointer = malloc(); + _pointer!.ref.model_path = modelPath.copyToNative(); + _pointer!.ref.cache_dir = cacheDir.copyToNative(); + _pointer!.ref.lora_path = loraPath.copyToNative(); + _pointer!.ref.sequence_batch_size = sequenceBatchSize; + _pointer!.ref.num_decode_steps_per_sync = decodeStepsPerSync; + _pointer!.ref.max_tokens = maxTokens; + _pointer!.ref.random_seed = randomSeed; + _pointer!.ref.temperature = temperature; + _pointer!.ref.topk = topK; + return _pointer!; + } + + bool _isClosed = false; + + /// Tracks whether [dispose] has been called. + bool get isClosed => _isClosed; + + /// Releases the native memory behind this options object. + void dispose() { + assert(() { + if (isClosed) { + throw Exception( + 'Attempted to call dispose on an already-disposed LlmInferenceOptions ' + 'object. LlmInferenceOptions should only ever be disposed after they ' + 'are at end-of-life and will never be accessed again.', + ); + } + if (_pointer == null) { + throw Exception( + 'Attempted to call dispose on a LlmInferenceOptions object which ' + 'was never used by an LLM. Did you forget to create your LLM?', + ); + } + return true; + }()); + calloc.free(_pointer!.ref.model_path); + calloc.free(_pointer!); + _isClosed = true; + } +} diff --git a/packages/mediapipe-task-genai/lib/src/io/tasks/tasks.dart b/packages/mediapipe-task-genai/lib/src/io/tasks/tasks.dart new file mode 100644 index 00000000..1f453dd4 --- /dev/null +++ b/packages/mediapipe-task-genai/lib/src/io/tasks/tasks.dart @@ -0,0 +1,5 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +export 'llm_inference/llm_inference.dart'; diff --git a/packages/mediapipe-task-genai/lib/src/io/third_party/mediapipe/generated/mediapipe_genai_bindings.dart b/packages/mediapipe-task-genai/lib/src/io/third_party/mediapipe/generated/mediapipe_genai_bindings.dart new file mode 100644 index 00000000..90a545c0 --- /dev/null +++ b/packages/mediapipe-task-genai/lib/src/io/third_party/mediapipe/generated/mediapipe_genai_bindings.dart @@ -0,0 +1,221 @@ +/* Copyright 2023 The MediaPipe Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +// AUTO GENERATED FILE, DO NOT EDIT. +// +// Generated by `package:ffigen`. +// ignore_for_file: type=lint +import 'dart:ffi' as ffi; + +@ffi.Native)>( + symbol: 'LlmInferenceEngine_CloseResponseContext') +external void LlmInferenceEngine_CloseResponseContext( + ffi.Pointer response_context, +); + +@ffi.Native< + ffi.Int Function( + ffi.Pointer, + ffi.Pointer>, + ffi.Pointer>)>( + symbol: 'LlmInferenceEngine_CreateSession') +external int LlmInferenceEngine_CreateSession( + ffi.Pointer session_config, + ffi.Pointer> session_out, + ffi.Pointer> error_msg, +); + +@ffi.Native)>( + symbol: 'LlmInferenceEngine_Session_Delete') +external void LlmInferenceEngine_Session_Delete( + ffi.Pointer session, +); + +@ffi.Native< + LlmResponseContext Function( + ffi.Pointer, ffi.Pointer)>( + symbol: 'LlmInferenceEngine_Session_PredictSync') +external LlmResponseContext LlmInferenceEngine_Session_PredictSync( + ffi.Pointer session, + ffi.Pointer input, +); + +@ffi.Native< + ffi.Void Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function(ffi.Pointer callback_context, + ffi.Pointer response_context)>>)>( + symbol: 'LlmInferenceEngine_Session_PredictAsync') +external void LlmInferenceEngine_Session_PredictAsync( + ffi.Pointer session, + ffi.Pointer callback_context, + ffi.Pointer input, + ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function(ffi.Pointer callback_context, + ffi.Pointer response_context)>> + callback, +); + +@ffi.Native< + ffi.Int Function(ffi.Pointer, + ffi.Pointer, ffi.Pointer>)>( + symbol: 'LlmInferenceEngine_Session_SizeInTokens') +external int LlmInferenceEngine_Session_SizeInTokens( + ffi.Pointer session, + ffi.Pointer input, + ffi.Pointer> error_msg, +); + +final class __mbstate_t extends ffi.Union { + @ffi.Array.multi([128]) + external ffi.Array __mbstate8; + + @ffi.LongLong() + external int _mbstateL; +} + +final class __darwin_pthread_handler_rec extends ffi.Struct { + external ffi + .Pointer)>> + __routine; + + external ffi.Pointer __arg; + + external ffi.Pointer<__darwin_pthread_handler_rec> __next; +} + +final class _opaque_pthread_attr_t extends ffi.Struct { + @ffi.Long() + external int __sig; + + @ffi.Array.multi([56]) + external ffi.Array __opaque; +} + +final class _opaque_pthread_cond_t extends ffi.Struct { + @ffi.Long() + external int __sig; + + @ffi.Array.multi([40]) + external ffi.Array __opaque; +} + +final class _opaque_pthread_condattr_t extends ffi.Struct { + @ffi.Long() + external int __sig; + + @ffi.Array.multi([8]) + external ffi.Array __opaque; +} + +final class _opaque_pthread_mutex_t extends ffi.Struct { + @ffi.Long() + external int __sig; + + @ffi.Array.multi([56]) + external ffi.Array __opaque; +} + +final class _opaque_pthread_mutexattr_t extends ffi.Struct { + @ffi.Long() + external int __sig; + + @ffi.Array.multi([8]) + external ffi.Array __opaque; +} + +final class _opaque_pthread_once_t extends ffi.Struct { + @ffi.Long() + external int __sig; + + @ffi.Array.multi([8]) + external ffi.Array __opaque; +} + +final class _opaque_pthread_rwlock_t extends ffi.Struct { + @ffi.Long() + external int __sig; + + @ffi.Array.multi([192]) + external ffi.Array __opaque; +} + +final class _opaque_pthread_rwlockattr_t extends ffi.Struct { + @ffi.Long() + external int __sig; + + @ffi.Array.multi([16]) + external ffi.Array __opaque; +} + +final class _opaque_pthread_t extends ffi.Struct { + @ffi.Long() + external int __sig; + + external ffi.Pointer<__darwin_pthread_handler_rec> __cleanup_stack; + + @ffi.Array.multi([8176]) + external ffi.Array __opaque; +} + +final class LlmSessionConfig extends ffi.Struct { + external ffi.Pointer model_path; + + external ffi.Pointer cache_dir; + + @ffi.Size() + external int sequence_batch_size; + + @ffi.Size() + external int num_decode_steps_per_sync; + + @ffi.Size() + external int max_tokens; + + @ffi.Size() + external int topk; + + @ffi.Float() + external double topp; + + @ffi.Float() + external double temperature; + + @ffi.Size() + external int random_seed; + + external ffi.Pointer lora_path; +} + +final class LlmResponseContext extends ffi.Struct { + external ffi.Pointer> response_array; + + @ffi.Int() + external int response_count; + + @ffi.Bool() + external bool done; +} + +typedef LlmInferenceEngine_Session = ffi.Void; + +const int true1 = 1; + +const int false1 = 0; diff --git a/packages/mediapipe-task-genai/lib/universal_mediapipe_genai.dart b/packages/mediapipe-task-genai/lib/universal_mediapipe_genai.dart new file mode 100644 index 00000000..fbbf2c55 --- /dev/null +++ b/packages/mediapipe-task-genai/lib/universal_mediapipe_genai.dart @@ -0,0 +1,81 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:mediapipe_genai/interface.dart'; + +/// {@macro LlmInferenceEngine} +class LlmInferenceEngine extends BaseLlmInferenceEngine { + /// {@macro LlmInferenceEngine} + LlmInferenceEngine(LlmInferenceOptions options); + + @override + Stream generateResponse(String text) => throw UnimplementedError(); + + @override + Future sizeInTokens(String text) => throw UnimplementedError(); + + /// Releases all native resources. + void dispose() => throw UnimplementedError(); +} + +/// {@macro LlmInferenceOptions} +class LlmInferenceOptions extends BaseLlmInferenceOptions { + /// {@template LLmInferenceOptions.cpu} + /// {@macro LlmInferenceOptions} + /// + /// Constructor for inference models using the CPU. + /// {@endtemplate} + factory LlmInferenceOptions.cpu({ + required String modelPath, + required String cacheDir, + required int maxTokens, + required double temperature, + required int topK, + int? randomSeed, + }) => + throw UnimplementedError(); + + /// {@template LLmInferenceOptions.gpu} + /// {@macro LlmInferenceOptions} + /// + /// Constructor for inference models using the GPU. + /// {@endtemplate} + factory LlmInferenceOptions.gpu({ + required String modelPath, + required int sequenceBatchSize, + required int maxTokens, + required double temperature, + required int topK, + int decodeStepsPerSync = 3, + int? randomSeed, + }) => + throw UnimplementedError(); + + @override + String get modelPath => throw UnimplementedError(); + + @override + String get cacheDir => throw UnimplementedError(); + + @override + String get loraPath => throw UnimplementedError(); + + @override + int get sequenceBatchSize => throw UnimplementedError(); + + @override + int get decodeStepsPerSync => throw UnimplementedError(); + + @override + int get maxTokens => throw UnimplementedError(); + + @override + int get randomSeed => throw UnimplementedError(); + + @override + double get temperature => throw UnimplementedError(); + + @override + int get topK => throw UnimplementedError(); +} diff --git a/packages/mediapipe-task-genai/pubspec.yaml b/packages/mediapipe-task-genai/pubspec.yaml new file mode 100644 index 00000000..ebc9d074 --- /dev/null +++ b/packages/mediapipe-task-genai/pubspec.yaml @@ -0,0 +1,31 @@ +name: mediapipe_genai +description: Provides access to MediaPipe's inference-based ODML tasks. +version: 0.0.1 +publish_to: none + +repository: https://github.com/google/flutter-mediapipe +issue_tracker: https://github.com/google/flutter-mediapipe/issues + +environment: + sdk: ">=3.5.0-6.0.dev <4.0.0" + flutter: ">=1.17.0" + +dependencies: + async: ^2.11.0 + equatable: ^2.0.5 + ffi: ^2.1.0 + flutter: + sdk: flutter + http: ^1.1.0 + logging: ^1.2.0 + mediapipe_core: + path: ../mediapipe-core + native_assets_cli: ^0.3.0 + native_toolchain_c: ^0.3.0 + path: ^1.8.3 + +dev_dependencies: + ffigen: ^9.0.1 + flutter_lints: ^3.0.0 + flutter_test: + sdk: flutter diff --git a/packages/mediapipe-task-genai/sdk_downloads.dart b/packages/mediapipe-task-genai/sdk_downloads.dart new file mode 100644 index 00000000..5eb48f7e --- /dev/null +++ b/packages/mediapipe-task-genai/sdk_downloads.dart @@ -0,0 +1,23 @@ +// Generated file. Do not manually edit. +// Used by the flutter toolchain (via build.dart) during compilation of any +// Flutter app using this package. +final Map>> sdkDownloadUrls = { + 'android': { + 'libllm_inference_engine': { + 'arm64': + 'https://storage.googleapis.com/mediapipe-nightly-public/prod/mediapipe/gcp_ubuntu_flutter/release/40/20240419-150307/android_arm64/libllm_inference_engine.so' + } + }, + 'macos': { + 'libllm_inference_engine': { + 'arm64': + 'https://storage.googleapis.com/mediapipe-nightly-public/prod/mediapipe/macos_flutter/release/61/20240508-094837/darwin_arm64/libllm_inference_engine.dylib' + } + }, + 'ios': { + 'libllm_inference_engine': { + 'arm64': + 'https://storage.googleapis.com/mediapipe-nightly-public/prod/mediapipe/macos_flutter/release/61/20240508-094837/ios_arm64/libllm_inference_engine.dylib' + } + } +}; diff --git a/packages/mediapipe-task-genai/test/mediapipe_inference_test.dart b/packages/mediapipe-task-genai/test/mediapipe_inference_test.dart new file mode 100644 index 00000000..ab73b3a2 --- /dev/null +++ b/packages/mediapipe-task-genai/test/mediapipe_inference_test.dart @@ -0,0 +1 @@ +void main() {} diff --git a/packages/mediapipe-task-genai/third_party/mediapipe/tasks/cc/genai/inference/c/llm_inference_engine.h b/packages/mediapipe-task-genai/third_party/mediapipe/tasks/cc/genai/inference/c/llm_inference_engine.h new file mode 100644 index 00000000..a5bd9568 --- /dev/null +++ b/packages/mediapipe-task-genai/third_party/mediapipe/tasks/cc/genai/inference/c/llm_inference_engine.h @@ -0,0 +1,129 @@ +// Copyright 2024 The MediaPipe Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef MEDIAPIPE_TASKS_GENAI_INFERENCE_C_LLM_INFERENCE_ENGINE_H_ +#define MEDIAPIPE_TASKS_GENAI_INFERENCE_C_LLM_INFERENCE_ENGINE_H_ + +#ifdef __cplusplus +#include +#else +#include +#include +#include +#endif + +#ifndef ODML_EXPORT +#define ODML_EXPORT __attribute__((visibility("default"))) +#endif // ODML_EXPORT + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void LlmInferenceEngine_Session; + +// LlmSessionConfig configures how to execute the model. +typedef struct { + // Path to the model artifact. + const char* model_path; + + // Directory path for storing model related tokenizer and cache weights. the + // user is responsible for providing the directory that can be writable by the + // program. + const char* cache_dir; + + // Sequence batch size for encoding. Used by GPU only. Number of input tokens + // to process at a time for batch processing. Setting this value to 1 means + // both the encoding and decoding share the same graph of sequence length + // of 1. Setting this value to 0 means the batch size will be optimized + // programmatically. + size_t sequence_batch_size; + + // Number of decode steps per sync. Used by GPU only. The default value is 3. + size_t num_decode_steps_per_sync; + + // Maximum number of tokens for input and output. + size_t max_tokens; + + // Top K number of tokens to be sampled from for each decoding step. + size_t topk; + + // Maximum cumulative probability over the tokens to sample from in each + // decoding step for top-p / nucleus sampling. + float topp; + + // Randomness when decoding the next token, 0.0f means greedy decoding. + float temperature; + + // Random seed for sampling tokens. + size_t random_seed; + + // Path to the LoRA tflite flatbuffer file. Optional. + // This is only compatible with GPU models. + const char* lora_path; +} LlmSessionConfig; + +// LlmResponseContext is the return type for +// LlmInferenceEngine_Session_PredictSync. +typedef struct { + // An array of string. The size of the array depends on the number of + // responses. + char** response_array; + + // Number of responses. + int response_count; + + // Done all outputs for this session. + bool done; +} LlmResponseContext; + +// Frees all context within the LlmResponseContext. +ODML_EXPORT void LlmInferenceEngine_CloseResponseContext( + LlmResponseContext* response_context); + +// Create a LlmInferenceEngine session for executing a query. +ODML_EXPORT int LlmInferenceEngine_CreateSession( + const LlmSessionConfig* session_config, + LlmInferenceEngine_Session** session_out, char** error_msg); + +// Free the session, will wait until graph is done executing. +ODML_EXPORT void LlmInferenceEngine_Session_Delete( + LlmInferenceEngine_Session* session); + +// Return the generated output in sync mode. +ODML_EXPORT LlmResponseContext LlmInferenceEngine_Session_PredictSync( + LlmInferenceEngine_Session* session, const char* input); + +// Run callback function in async mode. +// The callback will be invoked multiple times until `response_context.done` +// is `true`. You need to invoke `LlmInferenceEngine_CloseResponseContext` after +// each invocation to free memory. +// The callback context can be a pointer to any user defined data structure as +// it is passed to the callback unmodified. +ODML_EXPORT void LlmInferenceEngine_Session_PredictAsync( + LlmInferenceEngine_Session* session, void* callback_context, + const char* input, + void (*callback)(void* callback_context, + LlmResponseContext* response_context)); + +// Tokenizes an input prompt using a pre-existing processor and returns its +// length in tokens. Returns -1 if tokenization fails. +ODML_EXPORT int LlmInferenceEngine_Session_SizeInTokens( + LlmInferenceEngine_Session* session, const char* input, char** error_msg); + +#ifdef __cplusplus +} // extern C +#endif + +#endif // MEDIAPIPE_TASKS_GENAI_INFERENCE_C_LLM_INFERENCE_ENGINE_H_ diff --git a/packages/mediapipe-task-text/build.dart b/packages/mediapipe-task-text/build.dart index b8a757c8..b57697b4 100644 --- a/packages/mediapipe-task-text/build.dart +++ b/packages/mediapipe-task-text/build.dart @@ -35,6 +35,7 @@ Future main(List args) async { log(args.join(' ')); final String targetOs = buildConfig.targetOs.toString(); + log('targetOs: $targetOs'); log('dir.current: ${Directory.current.absolute.path}'); @@ -50,15 +51,15 @@ Future main(List args) async { buildOutput.dependencies.dependencies .add(buildConfig.packageRoot.resolve('sdk_downloads.dart')); - // final archKeys = sdkDownloadUrls[targetOs]!.keys; + final modelName = 'libtext'; final Iterable archKeys; if (buildConfig.dryRun) { - archKeys = sdkDownloadUrls[targetOs]!.keys; + archKeys = sdkDownloadUrls[targetOs]![modelName]!.keys; } else { archKeys = [buildConfig.targetArchitecture.toString()]; } for (final String arch in archKeys) { - final assetUrl = sdkDownloadUrls[targetOs]![arch]!; + final assetUrl = sdkDownloadUrls[targetOs]!['libtext']![arch]!; final downloadFileLocation = buildConfig.outDir.resolve( '${arch}_${assetUrl.split('/').last}', ); @@ -83,7 +84,6 @@ Future main(List args) async { Future downloadAsset(String assetUrl, Uri destinationFile) async { final downloadUri = Uri.parse(assetUrl); final downloadedFile = File(destinationFile.toFilePath()); - print('Saving file to ${downloadedFile.absolute.path}'); final downloadResponse = await http.get(downloadUri); log('Download response: ${downloadResponse.statusCode}'); diff --git a/packages/mediapipe-task-text/example/ios/Flutter/AppFrameworkInfo.plist b/packages/mediapipe-task-text/example/ios/Flutter/AppFrameworkInfo.plist index 9625e105..7c569640 100644 --- a/packages/mediapipe-task-text/example/ios/Flutter/AppFrameworkInfo.plist +++ b/packages/mediapipe-task-text/example/ios/Flutter/AppFrameworkInfo.plist @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 11.0 + 12.0 diff --git a/packages/mediapipe-task-text/example/ios/Podfile b/packages/mediapipe-task-text/example/ios/Podfile index fdcc671e..d97f17e2 100644 --- a/packages/mediapipe-task-text/example/ios/Podfile +++ b/packages/mediapipe-task-text/example/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -# platform :ios, '11.0' +# platform :ios, '12.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/packages/mediapipe-task-text/example/ios/Runner.xcodeproj/project.pbxproj b/packages/mediapipe-task-text/example/ios/Runner.xcodeproj/project.pbxproj index 05db82f9..be6547d5 100644 --- a/packages/mediapipe-task-text/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/mediapipe-task-text/example/ios/Runner.xcodeproj/project.pbxproj @@ -105,7 +105,6 @@ 251E7F79C4C4F22EC81E392F /* Pods-RunnerTests.release.xcconfig */, 8D741F75DA7F833954FC0919 /* Pods-RunnerTests.profile.xcconfig */, ); - name = Pods; path = Pods; sourceTree = ""; }; @@ -216,7 +215,7 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = YES; - LastUpgradeCheck = 1430; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { 331C8080294A63A400263BE5 = { @@ -453,7 +452,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -469,13 +468,16 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 35JKZ342M3; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "MediaPipe Text Example"; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.education"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.example; + PRODUCT_BUNDLE_IDENTIFIER = com.mediapipe.example; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -580,7 +582,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -629,7 +631,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -647,13 +649,16 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 35JKZ342M3; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "MediaPipe Text Example"; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.education"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.example; + PRODUCT_BUNDLE_IDENTIFIER = com.mediapipe.example; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -669,13 +674,16 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 35JKZ342M3; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "MediaPipe Text Example"; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.education"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.example; + PRODUCT_BUNDLE_IDENTIFIER = com.mediapipe.example; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; diff --git a/packages/mediapipe-task-text/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/mediapipe-task-text/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 87131a09..8e3ca5df 100644 --- a/packages/mediapipe-task-text/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/mediapipe-task-text/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ + CADisableMinimumFrameDurationOnPhone + CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName @@ -24,6 +26,8 @@ $(FLUTTER_BUILD_NUMBER) LSRequiresIPhoneOS + UIApplicationSupportsIndirectInputEvents + UILaunchStoryboardName LaunchScreen UIMainStoryboardFile @@ -41,9 +45,5 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight - CADisableMinimumFrameDurationOnPhone - - UIApplicationSupportsIndirectInputEvents - diff --git a/packages/mediapipe-task-text/example/macos/Runner/AppDelegate.swift b/packages/mediapipe-task-text/example/macos/Runner/AppDelegate.swift index d53ef643..8e02df28 100644 --- a/packages/mediapipe-task-text/example/macos/Runner/AppDelegate.swift +++ b/packages/mediapipe-task-text/example/macos/Runner/AppDelegate.swift @@ -1,7 +1,7 @@ import Cocoa import FlutterMacOS -@NSApplicationMain +@main class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true diff --git a/packages/mediapipe-task-text/lib/src/io/tasks/text_classification/text_classifier_executor.dart b/packages/mediapipe-task-text/lib/src/io/tasks/text_classification/text_classifier_executor.dart index 2064198d..8f849e2b 100644 --- a/packages/mediapipe-task-text/lib/src/io/tasks/text_classification/text_classifier_executor.dart +++ b/packages/mediapipe-task-text/lib/src/io/tasks/text_classification/text_classifier_executor.dart @@ -12,9 +12,11 @@ import 'package:mediapipe_text/src/io/third_party/mediapipe/generated/mediapipe_ final _log = Logger('TextTaskExecutor'); +/// {@template TextClassifierExecutor} /// Executes MediaPipe's "classifyText" task. /// /// {@macro TaskExecutor} +/// {@endtemplate} class TextClassifierExecutor extends TaskExecutor< bindings.TextClassifierOptions, TextClassifierOptions, diff --git a/packages/mediapipe-task-text/lib/src/io/tasks/text_embedding/text_embedder.dart b/packages/mediapipe-task-text/lib/src/io/tasks/text_embedding/text_embedder.dart index 5bc4fe44..8d8e20f4 100644 --- a/packages/mediapipe-task-text/lib/src/io/tasks/text_embedding/text_embedder.dart +++ b/packages/mediapipe-task-text/lib/src/io/tasks/text_embedding/text_embedder.dart @@ -60,7 +60,6 @@ class TextEmbedder extends BaseTextEmbedder { @override Future cosineSimilarity(Embedding a, Embedding b) async { await _ready; - (a as core_io.Embedding).pointer; _sendPort.send(_EmbedderTask.cosineSimilarity(a, b)); while (true) { final response = await _events.next; diff --git a/packages/mediapipe-task-text/lib/src/io/tasks/text_embedding/text_embedder_result.dart b/packages/mediapipe-task-text/lib/src/io/tasks/text_embedding/text_embedder_result.dart index 58ffd0bf..0af16e75 100644 --- a/packages/mediapipe-task-text/lib/src/io/tasks/text_embedding/text_embedder_result.dart +++ b/packages/mediapipe-task-text/lib/src/io/tasks/text_embedding/text_embedder_result.dart @@ -11,7 +11,8 @@ import '../../third_party/mediapipe/generated/mediapipe_text_bindings.dart' /// {@macro TextEmbedderResult} class TextEmbedderResult extends BaseEmbedderResult with IOTaskResult { /// {@macro TextEmbedderResult.fake} - TextEmbedderResult({required Iterable embeddings}); + TextEmbedderResult({required Iterable embeddings}) + : _embeddings = embeddings; /// {@template TextEmbedderResult.native} /// Initializes a [TextEmbedderResult] instance as a wrapper around native @@ -30,7 +31,7 @@ class TextEmbedderResult extends BaseEmbedderResult with IOTaskResult { Iterable _getEmbeddings() { if (_pointer.isNullOrNullPointer) { throw Exception( - 'No native memory for TextClassifierResult.classifications', + 'No native memory for TextEmbedderResult.embeddings', ); } return Embedding.fromNativeArray( diff --git a/packages/mediapipe-task-text/sdk_downloads.dart b/packages/mediapipe-task-text/sdk_downloads.dart index 41f61c2a..97e13a30 100644 --- a/packages/mediapipe-task-text/sdk_downloads.dart +++ b/packages/mediapipe-task-text/sdk_downloads.dart @@ -1,13 +1,25 @@ // Generated file. Do not manually edit. -final Map> sdkDownloadUrls = { +// Used by the flutter toolchain (via build.dart) during compilation of any +// Flutter app using this package. +final Map>> sdkDownloadUrls = { 'android': { - 'arm64': - 'https://storage.googleapis.com/mediapipe-nightly-public/prod/mediapipe/gcp_ubuntu_flutter/release/26/20240118-142736/android_arm64/libtext.so' + 'libtext': { + 'arm64': + 'https://storage.googleapis.com/mediapipe-nightly-public/prod/mediapipe/gcp_ubuntu_flutter/release/40/20240419-150307/android_arm64/libtext.so' + } }, 'macos': { - 'arm64': - 'https://storage.googleapis.com/mediapipe-nightly-public/prod/mediapipe/macos_flutter/release/9/20240118-123515/darwin_arm64/libtext.dylib', - 'x64': - 'https://storage.googleapis.com/mediapipe-nightly-public/prod/mediapipe/macos_flutter/release/9/20240118-123515/darwin_x86_64/libtext.dylib' + 'libtext': { + 'arm64': + 'https://storage.googleapis.com/mediapipe-nightly-public/prod/mediapipe/macos_flutter/release/61/20240508-094837/darwin_arm64/libtext.dylib', + 'x64': + 'https://storage.googleapis.com/mediapipe-nightly-public/prod/mediapipe/macos_flutter/release/61/20240508-094837/darwin_x86_64/libtext.dylib' + } + }, + 'ios': { + 'libtext': { + 'arm64': + 'https://storage.googleapis.com/mediapipe-nightly-public/prod/mediapipe/macos_flutter/release/61/20240508-094837/ios_arm64/libtext.dylib' + } } }; diff --git a/packages/mediapipe-task-vision/sdk_downloads.dart b/packages/mediapipe-task-vision/sdk_downloads.dart index d01418ed..04cf6181 100644 --- a/packages/mediapipe-task-vision/sdk_downloads.dart +++ b/packages/mediapipe-task-vision/sdk_downloads.dart @@ -1,13 +1,25 @@ // Generated file. Do not manually edit. -final Map> sdkDownloadUrls = { +// Used by the flutter toolchain (via build.dart) during compilation of any +// Flutter app using this package. +final Map>> sdkDownloadUrls = { 'android': { - 'arm64': - 'https://storage.googleapis.com/mediapipe-nightly-public/prod/mediapipe/gcp_ubuntu_flutter/release/26/20240118-142736/android_arm64/libvision.so' + 'libvision': { + 'arm64': + 'https://storage.googleapis.com/mediapipe-nightly-public/prod/mediapipe/gcp_ubuntu_flutter/release/40/20240419-150307/android_arm64/libvision.so' + } }, 'macos': { - 'arm64': - 'https://storage.googleapis.com/mediapipe-nightly-public/prod/mediapipe/macos_flutter/release/9/20240118-123515/darwin_arm64/libvision.dylib', - 'x64': - 'https://storage.googleapis.com/mediapipe-nightly-public/prod/mediapipe/macos_flutter/release/9/20240118-123515/darwin_x86_64/libvision.dylib' + 'libvision': { + 'arm64': + 'https://storage.googleapis.com/mediapipe-nightly-public/prod/mediapipe/macos_flutter/release/61/20240508-094837/darwin_arm64/libvision.dylib', + 'x64': + 'https://storage.googleapis.com/mediapipe-nightly-public/prod/mediapipe/macos_flutter/release/61/20240508-094837/darwin_x86_64/libvision.dylib' + } + }, + 'ios': { + 'libvision': { + 'arm64': + 'https://storage.googleapis.com/mediapipe-nightly-public/prod/mediapipe/macos_flutter/release/61/20240508-094837/ios_arm64/libvision.dylib' + } } }; diff --git a/tool/builder/lib/sdks_finder.dart b/tool/builder/lib/sdks_finder.dart index cce52df4..231d4d65 100644 --- a/tool/builder/lib/sdks_finder.dart +++ b/tool/builder/lib/sdks_finder.dart @@ -21,24 +21,35 @@ final _log = Logger('SDKsFinder'); /// Format is: /// { /// : { -/// : , -/// ... +/// : { +/// : , +/// ... +/// }, /// }, /// ... /// } -typedef _FlatResults = Map>; +typedef _FlatResults = Map>>; /// Container for the three flavors of MediaPipe tasks. enum MediaPipeSdk { + genai, libaudio, libtext, libvision; String toPackageDir() => switch (this) { + MediaPipeSdk.genai => 'mediapipe-task-genai', MediaPipeSdk.libaudio => 'mediapipe-task-audio', MediaPipeSdk.libtext => 'mediapipe-task-text', MediaPipeSdk.libvision => 'mediapipe-task-vision', }; + + String binaryName() => switch (this) { + MediaPipeSdk.genai => 'libllm_inference_engine', + MediaPipeSdk.libaudio => MediaPipeSdk.libaudio.name, + MediaPipeSdk.libtext => MediaPipeSdk.libtext.name, + MediaPipeSdk.libvision => MediaPipeSdk.libvision.name, + }; } /// Scans the GCS buckets where MediaPipe SDKs are stored, identifies the latest @@ -89,6 +100,7 @@ class SdksFinderCommand extends Command with RepoFinderMixin { final _finders = <_OsFinder>[ _OsFinder(OS.android), _OsFinder(OS.macOS), + _OsFinder(OS.iOS), // TODO: Add other values as their support is ready ]; @@ -114,7 +126,9 @@ class SdksFinderCommand extends Command with RepoFinderMixin { _log.fine('Writing data to "${file.absolute.path}"'); var encoder = JsonEncoder.withIndent(' '); file.writeAsStringSync('''// Generated file. Do not manually edit. -final Map> sdkDownloadUrls = ${encoder.convert(results).replaceAll('"', "'")}; +// Used by the flutter toolchain (via build.dart) during compilation of any +// Flutter app using this package. +final Map>> sdkDownloadUrls = ${encoder.convert(results).replaceAll('"', "'")}; '''); io.Process.start('dart', ['format', file.absolute.path]); } @@ -180,7 +194,7 @@ class _OsFinder { /// [SdksFinderCommand._bucketName]. static const _gcsFolderPaths = { OS.android: 'gcp_ubuntu_flutter', - OS.iOS: null, + OS.iOS: 'macos_flutter', OS.macOS: 'macos_flutter', }; @@ -197,7 +211,9 @@ class _OsFinder { OS.android: { 'android_arm64': Architecture.arm64, }, - OS.iOS: {}, + OS.iOS: { + 'ios_arm64': Architecture.arm64, + }, OS.macOS: { 'darwin_arm64': Architecture.arm64, 'darwin_x86_64': Architecture.x64, @@ -211,10 +227,13 @@ class _OsFinder { /// Scans the appropriate GCS location for all build Ids for the given OS and /// returns the highest integer found. - Future _getBuildNumber(String path) async { - int highestBuildNumber = 0; - - for (final folder in await _gsUtil(path)) { + /// + /// Returns a future iterator, instead of a stream, because it's one async + /// gap up front and then we want synchronous iterator behavior across the + /// build ids. + Iterable _getBuildNumbers(List paths) sync* { + List buildIds = []; + for (final folder in paths) { late int buildId; try { // Grab last chunk, since we're looking for a folder of the @@ -224,12 +243,13 @@ class _OsFinder { // Probably the `{id}_$folder$` directory continue; } - if (buildId > highestBuildNumber) { - highestBuildNumber = buildId; - } + buildIds.add(buildId); + } + + buildIds.sort(); + for (final buildId in buildIds.reversed) { + yield buildId; } - _log.fine('Highest build number for $os is $highestBuildNumber'); - return highestBuildNumber; } /// Extracts the date within a build directory, which is where the final @@ -289,7 +309,7 @@ class _OsFinder { /// Additionally, checks whether that file actually exists and returns the /// String value if it does, or `null` if it does not. Future _getAndCheckFullPath(String path, MediaPipeSdk sdk) async { - final pathToCheck = '$path/${sdk.name}.${_sdkExtensions[os]!}'; + final pathToCheck = '$path/${sdk.binaryName()}.${_sdkExtensions[os]!}'; final output = await _gsUtil(pathToCheck); if (output.isEmpty) { return null; @@ -300,30 +320,36 @@ class _OsFinder { Stream<_SdkLocation> find() async* { _log.info('Finding SDKs for $os'); String path = folderPath; - final buildNumber = await _getBuildNumber(path); - path = '$path/$buildNumber'; - _log.finest('$os :: build number :: $path'); - final buildDate = await _getDateOfBuildNumber(path); - path = '$path/$buildDate'; - _log.finest('$os :: date :: $path'); - - await for (final String archPath in _getArchitectectures(path)) { - String pathWithArch = '$path/$archPath'; - _log.finest('$os :: $archPath :: $pathWithArch'); - for (final sdk in MediaPipeSdk.values) { - final maybeFinalPath = await _getAndCheckFullPath(pathWithArch, sdk); - _log.finest('$os :: maybeFinalPath :: $maybeFinalPath'); - if (maybeFinalPath != null) { - _log.fine('Found "$maybeFinalPath"'); - yield _SdkLocation( - os: os, - arch: targetFolders[os]![archPath]!, - sdk: sdk, - fullPath: '${SdksFinderCommand._gcsPrefix}/' - '${SdksFinderCommand._bucketName}/$maybeFinalPath', - ); + for (final buildNumber in _getBuildNumbers(await _gsUtil(path))) { + path = '$folderPath/$buildNumber'; + _log.finest('$os :: build number :: $path'); + final buildDate = await _getDateOfBuildNumber(path); + path = '$path/$buildDate'; + _log.finest('$os :: date :: $path'); + + bool buildNumberFoundSdks = false; + await for (final String archPath in _getArchitectectures(path)) { + String pathWithArch = '$path/$archPath'; + _log.finest('$os :: $archPath :: $pathWithArch'); + for (final sdk in MediaPipeSdk.values) { + final maybeFinalPath = await _getAndCheckFullPath(pathWithArch, sdk); + _log.finest('$os :: maybeFinalPath :: $maybeFinalPath'); + if (maybeFinalPath != null) { + _log.info('Found "$maybeFinalPath"'); + buildNumberFoundSdks = true; + yield _SdkLocation( + os: os, + arch: targetFolders[os]![archPath]!, + sdk: sdk, + fullPath: '${SdksFinderCommand._gcsPrefix}/' + '${SdksFinderCommand._bucketName}/$maybeFinalPath', + ); + } } } + if (buildNumberFoundSdks) { + return; + } } } } @@ -392,10 +418,15 @@ class _SdkLocations { _FlatResults toMap(MediaPipeSdk sdk) { if (!_locations.containsKey(sdk)) return {}; - final _FlatResults results = >{}; + final _FlatResults results = >>{}; for (_SdkLocation loc in _locations[sdk]!) { - results.setDefault(loc.os.toString(), {}); - results[loc.os.toString()]![loc.arch.toString()] = loc.fullPath; + results.setDefault(loc.os.toString(), >{}); + results[loc.os.toString()]!.setDefault( + loc.sdk.binaryName(), + {}, + ); + results[loc.os.toString()]![loc.sdk.binaryName()]![loc.arch.toString()] = + loc.fullPath; } return results; diff --git a/tool/builder/lib/sync_headers.dart b/tool/builder/lib/sync_headers.dart index faac50f0..8bcc9334 100644 --- a/tool/builder/lib/sync_headers.dart +++ b/tool/builder/lib/sync_headers.dart @@ -20,10 +20,12 @@ final core = 'mediapipe/tasks/c/core'; final tc = 'mediapipe/tasks/c/text/text_classifier'; final te = 'mediapipe/tasks/c/text/text_embedder'; final ld = 'mediapipe/tasks/c/text/language_detector'; +final inference = 'mediapipe/tasks/cc/genai/inference/c'; /// google/flutter-mediapipe package paths final corePackage = 'packages/mediapipe-core/third_party'; final textPackage = 'packages/mediapipe-task-text/third_party'; +final inferencePackage = 'packages/mediapipe-task-genai/third_party'; /// First string is its relative location in both repositories, /// Second string is its package location in `google/flutter-mediapipe`, @@ -39,6 +41,7 @@ List<(String, String, String, Function(io.File)?)> headerPaths = [ (tc, textPackage, 'text_classifier.h', relativeIncludes), (te, textPackage, 'text_embedder.h', relativeIncludes), (ld, textPackage, 'language_detector.h', relativeIncludes), + (inference, inferencePackage, 'llm_inference_engine.h', null), ]; /// Command to copy all necessary C header files into this repository.