├── .gitignore
├── .metadata
├── .vscode
└── launch.json
├── CHANGELOG.md
├── LICENSE
├── README.md
├── README
└── screenshot.png
├── analysis_options.yaml
├── check-docs.sh
├── deploy-demo.sh
├── example
├── .firebase
│ └── hosting.YnVpbGQvd2Vi.cache
├── .gitignore
├── .metadata
├── analysis_options.yaml
├── android
│ ├── .gitignore
│ ├── app
│ │ ├── build.gradle
│ │ └── src
│ │ │ ├── debug
│ │ │ └── AndroidManifest.xml
│ │ │ ├── main
│ │ │ ├── AndroidManifest.xml
│ │ │ ├── kotlin
│ │ │ │ └── com
│ │ │ │ │ └── example
│ │ │ │ │ └── example
│ │ │ │ │ └── MainActivity.kt
│ │ │ └── res
│ │ │ │ ├── drawable-v21
│ │ │ │ └── launch_background.xml
│ │ │ │ ├── drawable
│ │ │ │ └── launch_background.xml
│ │ │ │ ├── mipmap-hdpi
│ │ │ │ └── ic_launcher.png
│ │ │ │ ├── mipmap-mdpi
│ │ │ │ └── ic_launcher.png
│ │ │ │ ├── mipmap-xhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ │ ├── mipmap-xxhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ │ ├── mipmap-xxxhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ │ ├── values-night
│ │ │ │ └── styles.xml
│ │ │ │ └── values
│ │ │ │ └── styles.xml
│ │ │ └── profile
│ │ │ └── AndroidManifest.xml
│ ├── build.gradle
│ ├── gradle.properties
│ ├── gradle
│ │ └── wrapper
│ │ │ └── gradle-wrapper.properties
│ └── settings.gradle
├── assets
│ ├── README.md
│ ├── halloween-bg.png
│ └── recipes_default.json
├── ios
│ ├── .gitignore
│ ├── Flutter
│ │ ├── AppFrameworkInfo.plist
│ │ ├── Debug.xcconfig
│ │ └── Release.xcconfig
│ ├── Podfile
│ ├── Podfile.lock
│ ├── Runner.xcodeproj
│ │ ├── project.pbxproj
│ │ ├── project.xcworkspace
│ │ │ ├── contents.xcworkspacedata
│ │ │ └── xcshareddata
│ │ │ │ ├── IDEWorkspaceChecks.plist
│ │ │ │ └── WorkspaceSettings.xcsettings
│ │ └── xcshareddata
│ │ │ └── xcschemes
│ │ │ └── Runner.xcscheme
│ ├── Runner.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ ├── IDEWorkspaceChecks.plist
│ │ │ └── WorkspaceSettings.xcsettings
│ ├── Runner
│ │ ├── AppDelegate.swift
│ │ ├── Assets.xcassets
│ │ │ ├── AppIcon.appiconset
│ │ │ │ ├── Contents.json
│ │ │ │ ├── Icon-App-1024x1024@1x.png
│ │ │ │ ├── Icon-App-20x20@1x.png
│ │ │ │ ├── Icon-App-20x20@2x.png
│ │ │ │ ├── Icon-App-20x20@3x.png
│ │ │ │ ├── Icon-App-29x29@1x.png
│ │ │ │ ├── Icon-App-29x29@2x.png
│ │ │ │ ├── Icon-App-29x29@3x.png
│ │ │ │ ├── Icon-App-40x40@1x.png
│ │ │ │ ├── Icon-App-40x40@2x.png
│ │ │ │ ├── Icon-App-40x40@3x.png
│ │ │ │ ├── Icon-App-60x60@2x.png
│ │ │ │ ├── Icon-App-60x60@3x.png
│ │ │ │ ├── Icon-App-76x76@1x.png
│ │ │ │ ├── Icon-App-76x76@2x.png
│ │ │ │ └── Icon-App-83.5x83.5@2x.png
│ │ │ └── LaunchImage.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ ├── LaunchImage.png
│ │ │ │ ├── LaunchImage@2x.png
│ │ │ │ ├── LaunchImage@3x.png
│ │ │ │ └── README.md
│ │ ├── Base.lproj
│ │ │ ├── LaunchScreen.storyboard
│ │ │ └── Main.storyboard
│ │ ├── Info.plist
│ │ └── Runner-Bridging-Header.h
│ └── RunnerTests
│ │ └── RunnerTests.swift
├── lib
│ ├── cupertino
│ │ └── cupertino.dart
│ ├── custom_styles
│ │ └── custom_styles.dart
│ ├── dark_mode
│ │ └── dark_mode.dart
│ ├── dark_style.dart
│ ├── demo
│ │ ├── api_key_page.dart
│ │ └── demo.dart
│ ├── echo
│ │ └── echo.dart
│ ├── gemini
│ │ └── gemini.dart
│ ├── history
│ │ └── history.dart
│ ├── logging
│ │ └── logging.dart
│ ├── main.dart
│ ├── recipes
│ │ ├── data
│ │ │ ├── recipe_data.dart
│ │ │ ├── recipe_repository.dart
│ │ │ └── settings.dart
│ │ ├── pages
│ │ │ ├── edit_recipe_page.dart
│ │ │ ├── home_page.dart
│ │ │ └── split_or_tabs.dart
│ │ ├── recipes.dart
│ │ └── views
│ │ │ ├── recipe_content_view.dart
│ │ │ ├── recipe_list_view.dart
│ │ │ ├── recipe_response_view.dart
│ │ │ ├── recipe_view.dart
│ │ │ ├── search_box.dart
│ │ │ └── settings_drawer.dart
│ ├── styles
│ │ └── styles.dart
│ ├── suggestions
│ │ └── suggestions.dart
│ ├── vertex
│ │ └── vertex.dart
│ └── welcome
│ │ └── welcome.dart
├── macos
│ ├── .gitignore
│ ├── Flutter
│ │ ├── Flutter-Debug.xcconfig
│ │ ├── Flutter-Release.xcconfig
│ │ └── GeneratedPluginRegistrant.swift
│ ├── Podfile
│ ├── Podfile.lock
│ ├── Runner.xcodeproj
│ │ ├── project.pbxproj
│ │ ├── project.xcworkspace
│ │ │ └── xcshareddata
│ │ │ │ └── IDEWorkspaceChecks.plist
│ │ └── xcshareddata
│ │ │ └── xcschemes
│ │ │ └── Runner.xcscheme
│ ├── Runner.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
│ ├── Runner
│ │ ├── AppDelegate.swift
│ │ ├── Assets.xcassets
│ │ │ └── AppIcon.appiconset
│ │ │ │ ├── Contents.json
│ │ │ │ ├── app_icon_1024.png
│ │ │ │ ├── app_icon_128.png
│ │ │ │ ├── app_icon_16.png
│ │ │ │ ├── app_icon_256.png
│ │ │ │ ├── app_icon_32.png
│ │ │ │ ├── app_icon_512.png
│ │ │ │ └── app_icon_64.png
│ │ ├── Base.lproj
│ │ │ └── MainMenu.xib
│ │ ├── Configs
│ │ │ ├── AppInfo.xcconfig
│ │ │ ├── Debug.xcconfig
│ │ │ ├── Release.xcconfig
│ │ │ └── Warnings.xcconfig
│ │ ├── DebugProfile.entitlements
│ │ ├── Info.plist
│ │ ├── MainFlutterWindow.swift
│ │ └── Release.entitlements
│ └── RunnerTests
│ │ └── RunnerTests.swift
├── pubspec.lock
├── pubspec.yaml
└── web
│ ├── favicon.png
│ ├── icons
│ ├── Icon-192.png
│ ├── Icon-512.png
│ ├── Icon-maskable-192.png
│ └── Icon-maskable-512.png
│ ├── index.html
│ └── manifest.json
├── font_svg
├── README.md
├── reply_24dp_5F6368_FILL0_wght400_GRAD0_opsz24.svg
├── spark-icon.svg
└── submit-icon.svg
├── lib
├── flutter_ai_toolkit.dart
├── fonts
│ └── FatIcons.ttf
└── src
│ ├── chat_view_model
│ ├── chat_view_model.dart
│ ├── chat_view_model_client.dart
│ └── chat_view_model_provider.dart
│ ├── dialogs
│ ├── adaptive_dialog.dart
│ ├── adaptive_dialog_action.dart
│ ├── adaptive_snack_bar
│ │ ├── adaptive_snack_bar.dart
│ │ └── cupertino_snack_bar.dart
│ └── image_preview_dialog.dart
│ ├── llm_exception.dart
│ ├── platform_helper
│ ├── platform_helper.dart
│ ├── platform_helper_io.dart
│ └── platform_helper_web.dart
│ ├── providers
│ ├── implementations
│ │ ├── echo_provider.dart
│ │ ├── gemini_provider.dart
│ │ └── vertex_provider.dart
│ ├── interface
│ │ ├── attachments.dart
│ │ ├── chat_message.dart
│ │ ├── llm_provider.dart
│ │ └── message_origin.dart
│ └── providers.dart
│ ├── styles
│ ├── action_button_style.dart
│ ├── action_button_type.dart
│ ├── chat_input_style.dart
│ ├── file_attachment_style.dart
│ ├── llm_chat_view_style.dart
│ ├── llm_message_style.dart
│ ├── styles.dart
│ ├── suggestion_style.dart
│ ├── tookit_icons.dart
│ ├── toolkit_colors.dart
│ ├── toolkit_text_styles.dart
│ └── user_message_style.dart
│ ├── utility.dart
│ └── views
│ ├── action_button
│ ├── action_button.dart
│ └── action_button_bar.dart
│ ├── adaptive_progress_indicator.dart
│ ├── attachment_view
│ ├── attachment_view.dart
│ ├── file_attatchment_view.dart
│ └── image_attachment_view.dart
│ ├── chat_history_view.dart
│ ├── chat_input
│ ├── attachments_action_bar.dart
│ ├── attachments_view.dart
│ ├── chat_input.dart
│ ├── chat_suggestion_view.dart
│ ├── editing_indicator.dart
│ ├── input_button.dart
│ ├── input_state.dart
│ └── removable_attachment.dart
│ ├── chat_message_view
│ ├── adaptive_copy_text.dart
│ ├── hovering_buttons.dart
│ ├── llm_message_view.dart
│ └── user_message_view.dart
│ ├── chat_text_field.dart
│ ├── jumping_dots_progress_indicator
│ ├── jumping_dot.dart
│ └── jumping_dots_progress_indicator.dart
│ ├── llm_chat_view
│ ├── llm_chat_view.dart
│ └── llm_response.dart
│ └── response_builder.dart
└── pubspec.yaml
/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .buildlog/
9 | .history
10 | .svn/
11 | migrate_working_dir/
12 |
13 | # IntelliJ related
14 | *.iml
15 | *.ipr
16 | *.iws
17 | .idea/
18 |
19 | # The .vscode folder contains launch configuration and tasks you configure in
20 | # VS Code which you may wish to be included in version control, so this line
21 | # is commented out by default.
22 | #.vscode/
23 |
24 | # Flutter/Dart/Pub related
25 | # Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
26 | /pubspec.lock
27 | **/doc/api/
28 | .dart_tool/
29 | build/
30 | example/.env
31 | example/lib/firebase_options.dart
32 | example/firebase.json
33 | example/.firebaserc
34 | example/android/app/google-services.json
35 | example/ios/Runner/GoogleService-Info.plist
36 | example/macos/Runner/GoogleService-Info.plist
37 | example/lib/echo.dart
38 | .flutter-plugins
39 | .flutter-plugins-dependencies
40 | .vscode/settings.json
--------------------------------------------------------------------------------
/.metadata:
--------------------------------------------------------------------------------
1 | # This file tracks properties of this Flutter project.
2 | # Used by Flutter tool to assess capabilities and perform upgrades etc.
3 | #
4 | # This file should be version controlled and should not be manually edited.
5 |
6 | version:
7 | revision: "761747bfc538b5af34aa0d3fac380f1bc331ec49"
8 | channel: "stable"
9 |
10 | project_type: package
11 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | {
5 | "name": "main",
6 | "cwd": "example",
7 | "request": "launch",
8 | "type": "dart",
9 | "program": "lib/main.dart",
10 | },
11 | {
12 | "name": "gemini",
13 | "cwd": "example",
14 | "request": "launch",
15 | "type": "dart",
16 | "program": "lib/gemini/gemini.dart",
17 | },
18 | {
19 | "name": "vertex",
20 | "cwd": "example",
21 | "request": "launch",
22 | "type": "dart",
23 | "program": "lib/vertex/vertex.dart",
24 | },
25 | {
26 | "name": "demo",
27 | "cwd": "example",
28 | "request": "launch",
29 | "type": "dart",
30 | "program": "lib/demo/demo.dart",
31 | },
32 | {
33 | "name": "welcome",
34 | "cwd": "example",
35 | "request": "launch",
36 | "type": "dart",
37 | "program": "lib/welcome/welcome.dart",
38 | },
39 | {
40 | "name": "cupertino",
41 | "cwd": "example",
42 | "request": "launch",
43 | "type": "dart",
44 | "program": "lib/cupertino/cupertino.dart",
45 | },
46 | {
47 | "name": "custom styles",
48 | "cwd": "example",
49 | "request": "launch",
50 | "type": "dart",
51 | "program": "lib/custom_styles/custom_styles.dart",
52 | },
53 | {
54 | "name": "dark mode",
55 | "cwd": "example",
56 | "request": "launch",
57 | "type": "dart",
58 | "program": "lib/dark_mode/dark_mode.dart",
59 | },
60 | {
61 | "name": "history",
62 | "cwd": "example",
63 | "request": "launch",
64 | "type": "dart",
65 | "program": "lib/history/history.dart",
66 | },
67 | {
68 | "name": "suggestions",
69 | "cwd": "example",
70 | "request": "launch",
71 | "type": "dart",
72 | "program": "lib/suggestions/suggestions.dart",
73 | },
74 | {
75 | "name": "logging",
76 | "cwd": "example",
77 | "request": "launch",
78 | "type": "dart",
79 | "program": "lib/logging/logging.dart",
80 | },
81 | {
82 | "name": "recipes",
83 | "cwd": "example",
84 | "request": "launch",
85 | "type": "dart",
86 | "program": "lib/recipes/recipes.dart",
87 | },
88 | ]
89 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2024, the Flutter AI Toolkit project authors. All rights reserved.
2 | Redistribution and use in source and binary forms, with or without
3 | modification, are permitted provided that the following conditions are
4 | met:
5 |
6 | * Redistributions of source code must retain the above copyright
7 | notice, this list of conditions and the following disclaimer.
8 | * Redistributions in binary form must reproduce the above
9 | copyright notice, this list of conditions and the following
10 | disclaimer in the documentation and/or other materials provided
11 | with the distribution.
12 | * Neither the name of Google Inc. nor the names of its
13 | contributors may be used to endorse or promote products derived
14 | from this software without specific prior written permission.
15 |
16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
/README/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/csells/flutter_ai_toolkit/e4efee359ce5ba8c44f0a0bcd8936e9367af326a/README/screenshot.png
--------------------------------------------------------------------------------
/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | include: package:all_lint_rules_community/all.yaml
2 |
3 | analyzer:
4 | exclude:
5 | - "**/*.g.dart"
6 | - "**/*.freezed.dart"
7 | - "test/.test_coverage.dart"
8 | - "bin/cache/**"
9 | - "lib/generated_plugin_registrant.dart"
10 | errors:
11 | # without ignore here, we cause import of all_lint_rules to warn, because
12 | # some rules conflict; instead, we're explicitly enabling even conflicting
13 | # rules and are fixing the conflicts in this file
14 | included_file_warning: ignore
15 | unintended_html_in_doc_comment: ignore
16 |
17 | linter:
18 | rules:
19 | avoid_types_on_closure_parameters: true # no; prefer use of final instead
20 | prefer_double_quotes: false # Dart prefers single quotes (for some reason)
21 | unnecessary_final: false # love final!
22 | always_specify_types: false # no; prefer use of final instead
23 | prefer_final_parameters: false # I like the sentiment, but too much typing!
24 | prefer_asserts_with_message: false # too lazy for this...
25 | require_trailing_commas: false # not good for things all on one line
26 | public_member_api_docs: true # except for public libs
27 | avoid_classes_with_only_static_members: false # need this; no namespaces
28 | always_put_control_body_on_new_line: false # single line is nice when we can
29 | always_use_package_imports: false # prefer relative imports for local files
30 | avoid_annotating_with_dynamic: false # be explicit about dynamic
31 | avoid_redundant_argument_values: false # sometimes it's nice to be explicit
32 | one_member_abstracts: false # interfaces can have a single method
33 | flutter_style_todos: false # I'm too lazy for this...
34 | diagnostic_describe_all_properties: false # too annoying for StatefulWidget
35 | library_private_types_in_public_api: false # too annoying for StatefulWidget
36 | cascade_invocations: false # not a fan...
--------------------------------------------------------------------------------
/check-docs.sh:
--------------------------------------------------------------------------------
1 | dart doc --dry-run
2 |
--------------------------------------------------------------------------------
/deploy-demo.sh:
--------------------------------------------------------------------------------
1 | cd example
2 | rm -rf build/web
3 | flutter build web --release --target lib/demo/demo.dart
4 | firebase deploy
5 | cd ..
6 |
--------------------------------------------------------------------------------
/example/.firebase/hosting.YnVpbGQvd2Vi.cache:
--------------------------------------------------------------------------------
1 | manifest.json,1722130836723,f81e4554dc7f05633a2c5597416813859de5ace688342db41b201d42790fb8a7
2 | flutter.js,1726080818000,dec659847e4e16b505a257eb15bc32ef814f99a319e44e15b9c56294de0c9ecd
3 | favicon.png,1716580864393,fcc7c4545d5b62ad01682589e6fdc7ea03d0a3b42069963c815c344b632eb5cf
4 | icons/Icon-maskable-512.png,1716580886213,e7983524dc70254adc61764657d7e03d19284de8da586b5818d737bc08c6d14e
5 | icons/Icon-maskable-192.png,1716580886213,dd96c123fdf6817cdf7e63d9693bcc246bac2e3782a41a6952fa41c0617c5573
6 | icons/Icon-512.png,1716580864394,7a31ce91e554f1941158ca46f31c7f3f2b7c8c129229ea74a8fae1affe335033
7 | icons/Icon-192.png,1716580864394,d2e0131bb7851eb9d98f7885edb5ae4b4d6b7a6c7addf8a25b9b712b39274c0f
8 | canvaskit/skwasm.worker.js,1726081188000,ff1a6b2c254c1954106d62ce10432547fa6d6a7d99c0aaf9ea6144a23ef0c09b
9 | canvaskit/skwasm.wasm,1726081188000,204bb6c7deaa41ccfdb811d09107bcacd7162a848d8e9faa45e46390fe0dda9a
10 | canvaskit/skwasm.js.symbols,1726081188000,ea615fdcd1320bad0f3fb6b3e5bcb0dd3a4d8480c221af6a84455860775987c1
11 | canvaskit/skwasm.js,1726081188000,3da3d8d5b168f8f2cba2513cba2c65a9bfa650723545145cffa76a102a678e9d
12 | canvaskit/canvaskit.wasm,1726081100000,2e91b313aa59675be755df469bb88efdfa2be8cdccc4e49bf743f9f22d16f933
13 | canvaskit/canvaskit.js.symbols,1726081098000,7687fed87ac2c6f61e5d991be1e3f8c0191e4271128c73a5afea2f28562ad0b8
14 | canvaskit/canvaskit.js,1726081100000,95f051f3c2845fb04127fd56e1fa3e52a69d271eea561869e97bfe4374548109
15 | canvaskit/chromium/canvaskit.wasm,1726081122000,d865adf21902388e4d4af54a5e430479e5ef37ac660649017db1877b29976a08
16 | canvaskit/chromium/canvaskit.js.symbols,1726081120000,9d7b8e9cc146e9ad2b68af9f0c8092b144a9aac73666941b468b8b2fd36cdb27
17 | canvaskit/chromium/canvaskit.js,1726081122000,c448a9b3e29d0dad724aa33a554695e7f1257c58b28baca305177fd18e62e411
18 | assets/packages/record_web/assets/js/record.worklet.js,1726247519128,bd510fc16afe17c0cfc943194267c8d2af8a4b88fa63b525926c95d319e544c7
19 | assets/packages/record_web/assets/js/record.fixwebmduration.js,1726247519130,77e2fe77324499420d0a882273998e7574fa885c3a7d944a8e69382e5daaab08
20 | assets/assets/recipes_default.json,1727751782273,1f90b422a85d1e7a708fd4d255d8b7db1b6206bf002813cb380d6cce0fbb4bd8
21 | assets/assets/halloween-bg.png,1729875831180,aa155fdcd4249179b89ca8e6dbfbff642a622534635e9cc6dce5c099d7445b7e
22 | version.json,1730136001205,8e7012f17ec662cfb721c69523e82a27ac216e8e3c973b248b847e9a118cf603
23 | index.html,1730135983527,4c1651c60b2cd671eb40ebf9e77cd7d7f1894a90b21182fa149ead727d85095f
24 | assets/FontManifest.json,1730136001277,d1784484a5fbf123cc38d6fb83f618e2f855cfc9a47c4b87c34565736fb1025f
25 | flutter_service_worker.js,1730136002322,09a340c85d8522734c4db9521a920665aef469eec44a7162064304be50c817ee
26 | assets/AssetManifest.json,1730136001277,5ee990889da9f2ce04fb308d72dfb0a4df0a2a8c4cfe808dcc784e0cea9560ea
27 | assets/AssetManifest.bin.json,1730136001277,599a0b1e65a657294c65f94df02a01c115e4f2ef9a651ce3bbadad941866dc9d
28 | assets/AssetManifest.bin,1730136001277,71fc78fcaf72348505ddacbfc2fc03fb686e543376e18388ecdca2509e70245d
29 | flutter_bootstrap.js,1730135983509,257003d38cfc778893260c7ce9a69c191aa5c23c05402fb287677767cc049188
30 | assets/packages/flutter_ai_toolkit/lib/fonts/FatIcons.ttf,1730136002032,59c80e8640bde5d1fc73af52961bb52ed3f08fddd3af8f8fe172880442cc33b7
31 | assets/packages/cupertino_icons/assets/CupertinoIcons.ttf,1730136002030,a9dec9e47fcee105fc5f7ea79904e588215596ef681f1ba97034cd0829c0554b
32 | assets/shaders/ink_sparkle.frag,1730136001332,80c6e65c75f1de434b1b22dba61e96ad82dba0f2fc5e8b3b59c2def46d794354
33 | assets/fonts/MaterialIcons-Regular.otf,1730136002034,d1e5ecfde56f17e87cc9b0e73792a40eadf95ed0372e336e13038744c8dc09cf
34 | assets/NOTICES,1730136001278,b7c968ec8b44aa16ccd1a0138cfee73251738e425cae9fcc8fbdca9051337fc6
35 | main.dart.js,1730136001025,34484a94aff99c34bb6608f95d1a7e3d39b2bd66184d520341ff826ace776120
36 |
--------------------------------------------------------------------------------
/example/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .buildlog/
9 | .history
10 | .svn/
11 | migrate_working_dir/
12 |
13 | # IntelliJ related
14 | *.iml
15 | *.ipr
16 | *.iws
17 | .idea/
18 |
19 | # The .vscode folder contains launch configuration and tasks you configure in
20 | # VS Code which you may wish to be included in version control, so this line
21 | # is commented out by default.
22 | #.vscode/
23 |
24 | # Flutter/Dart/Pub related
25 | **/doc/api/
26 | **/ios/Flutter/.last_build_id
27 | .dart_tool/
28 | .flutter-plugins
29 | .flutter-plugins-dependencies
30 | .pub-cache/
31 | .pub/
32 | /build/
33 |
34 | # Symbolication related
35 | app.*.symbols
36 |
37 | # Obfuscation related
38 | app.*.map.json
39 |
40 | # Android Studio will place build artifacts here
41 | /android/app/debug
42 | /android/app/profile
43 | /android/app/release
44 |
45 | # custom
46 | gemini_api_key.dart
47 | firebase_options.dart
--------------------------------------------------------------------------------
/example/.metadata:
--------------------------------------------------------------------------------
1 | # This file tracks properties of this Flutter project.
2 | # Used by Flutter tool to assess capabilities and perform upgrades etc.
3 | #
4 | # This file should be version controlled and should not be manually edited.
5 |
6 | version:
7 | revision: "2663184aa79047d0a33a14a3b607954f8fdd8730"
8 | channel: "stable"
9 |
10 | project_type: app
11 |
12 | # Tracks metadata for the flutter migrate command
13 | migration:
14 | platforms:
15 | - platform: root
16 | create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730
17 | base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730
18 | - platform: android
19 | create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730
20 | base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730
21 |
22 | # User provided section
23 |
24 | # List of Local paths (relative to this file) that should be
25 | # ignored by the migrate tool.
26 | #
27 | # Files that are not part of the templates will be ignored by default.
28 | unmanaged_files:
29 | - 'lib/main.dart'
30 | - 'ios/Runner.xcodeproj/project.pbxproj'
31 |
--------------------------------------------------------------------------------
/example/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | # This file configures the analyzer, which statically analyzes Dart code to
2 | # check for errors, warnings, and lints.
3 | #
4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled
5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
6 | # invoked from the command line by running `flutter analyze`.
7 |
8 | # The following line activates a set of recommended lints for Flutter apps,
9 | # packages, and plugins designed to encourage good coding practices.
10 | analyzer:
11 | errors:
12 | library_private_types_in_public_api: ignore
13 | include: package:flutter_lints/flutter.yaml
14 |
15 | linter:
16 | # The lint rules applied to this project can be customized in the
17 | # section below to disable rules from the `package:flutter_lints/flutter.yaml`
18 | # included above or to enable additional rules. A list of all available lints
19 | # and their documentation is published at https://dart.dev/lints.
20 | #
21 | # Instead of disabling a lint rule for the entire project in the
22 | # section below, it can also be suppressed for a single line of code
23 | # or a specific dart file by using the `// ignore: name_of_lint` and
24 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file
25 | # producing the lint.
26 | rules:
27 | # avoid_print: false # Uncomment to disable the `avoid_print` rule
28 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
29 |
30 | # Additional information about this file can be found at
31 | # https://dart.dev/guides/language/analysis-options
32 |
--------------------------------------------------------------------------------
/example/android/.gitignore:
--------------------------------------------------------------------------------
1 | gradle-wrapper.jar
2 | /.gradle
3 | /captures/
4 | /gradlew
5 | /gradlew.bat
6 | /local.properties
7 | GeneratedPluginRegistrant.java
8 |
9 | # Remember to never publicly share your keystore.
10 | # See https://flutter.dev/to/reference-keystore
11 | key.properties
12 | **/*.keystore
13 | **/*.jks
14 |
--------------------------------------------------------------------------------
/example/android/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id "com.android.application"
3 | // START: FlutterFire Configuration
4 | id 'com.google.gms.google-services'
5 | // END: FlutterFire Configuration
6 | id "kotlin-android"
7 | // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
8 | id "dev.flutter.flutter-gradle-plugin"
9 | }
10 |
11 | android {
12 | namespace = "com.example.example"
13 | compileSdk = flutter.compileSdkVersion
14 | ndkVersion = "25.1.8937393"
15 |
16 | compileOptions {
17 | sourceCompatibility = JavaVersion.VERSION_1_8
18 | targetCompatibility = JavaVersion.VERSION_1_8
19 | }
20 |
21 | kotlinOptions {
22 | jvmTarget = JavaVersion.VERSION_1_8
23 | }
24 |
25 | defaultConfig {
26 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
27 | applicationId = "com.example.example"
28 | // You can update the following values to match your application needs.
29 | // For more information, see: https://flutter.dev/to/review-gradle-config.
30 | minSdk = 23 // flutter.minSdkVersion
31 | targetSdk = flutter.targetSdkVersion
32 | versionCode = flutter.versionCode
33 | versionName = flutter.versionName
34 | }
35 |
36 | buildTypes {
37 | release {
38 | // TODO: Add your own signing config for the release build.
39 | // Signing with the debug keys for now, so `flutter run --release` works.
40 | signingConfig = signingConfigs.debug
41 | }
42 | }
43 | }
44 |
45 | flutter {
46 | source = "../.."
47 | }
48 |
--------------------------------------------------------------------------------
/example/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/example/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
16 |
17 |
18 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.example.example
2 |
3 | import io.flutter.embedding.android.FlutterActivity
4 |
5 | class MainActivity: FlutterActivity()
6 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/drawable-v21/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/csells/flutter_ai_toolkit/e4efee359ce5ba8c44f0a0bcd8936e9367af326a/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/csells/flutter_ai_toolkit/e4efee359ce5ba8c44f0a0bcd8936e9367af326a/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/csells/flutter_ai_toolkit/e4efee359ce5ba8c44f0a0bcd8936e9367af326a/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/csells/flutter_ai_toolkit/e4efee359ce5ba8c44f0a0bcd8936e9367af326a/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/csells/flutter_ai_toolkit/e4efee359ce5ba8c44f0a0bcd8936e9367af326a/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/values-night/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/example/android/app/src/profile/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/example/android/build.gradle:
--------------------------------------------------------------------------------
1 | allprojects {
2 | repositories {
3 | google()
4 | mavenCentral()
5 | }
6 | }
7 |
8 | rootProject.buildDir = "../build"
9 | subprojects {
10 | project.buildDir = "${rootProject.buildDir}/${project.name}"
11 | }
12 | subprojects {
13 | project.evaluationDependsOn(":app")
14 | }
15 |
16 | tasks.register("clean", Delete) {
17 | delete rootProject.buildDir
18 | }
19 |
--------------------------------------------------------------------------------
/example/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError
2 | android.useAndroidX=true
3 | android.enableJetifier=true
4 |
--------------------------------------------------------------------------------
/example/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | zipStoreBase=GRADLE_USER_HOME
4 | zipStorePath=wrapper/dists
5 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-all.zip
--------------------------------------------------------------------------------
/example/android/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | def flutterSdkPath = {
3 | def properties = new Properties()
4 | file("local.properties").withInputStream { properties.load(it) }
5 | def flutterSdkPath = properties.getProperty("flutter.sdk")
6 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
7 | return flutterSdkPath
8 | }()
9 |
10 | includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
11 |
12 | repositories {
13 | google()
14 | mavenCentral()
15 | gradlePluginPortal()
16 | }
17 | }
18 |
19 | plugins {
20 | id "dev.flutter.flutter-plugin-loader" version "1.0.0"
21 | id "com.android.application" version "8.3.2" apply false
22 | // START: FlutterFire Configuration
23 | id "com.google.gms.google-services" version "4.3.15" apply false
24 | // END: FlutterFire Configuration
25 | id "org.jetbrains.kotlin.android" version "1.8.22" apply false
26 | }
27 |
28 | include ":app"
29 |
--------------------------------------------------------------------------------
/example/assets/README.md:
--------------------------------------------------------------------------------
1 | The following assets have been generated by the Gemini LLM and are being used royalty-free in this project:
2 | - halloween-bg.png
3 | - recipes_default.json
--------------------------------------------------------------------------------
/example/assets/halloween-bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/csells/flutter_ai_toolkit/e4efee359ce5ba8c44f0a0bcd8936e9367af326a/example/assets/halloween-bg.png
--------------------------------------------------------------------------------
/example/ios/.gitignore:
--------------------------------------------------------------------------------
1 | **/dgph
2 | *.mode1v3
3 | *.mode2v3
4 | *.moved-aside
5 | *.pbxuser
6 | *.perspectivev3
7 | **/*sync/
8 | .sconsign.dblite
9 | .tags*
10 | **/.vagrant/
11 | **/DerivedData/
12 | Icon?
13 | **/Pods/
14 | **/.symlinks/
15 | profile
16 | xcuserdata
17 | **/.generated/
18 | Flutter/App.framework
19 | Flutter/Flutter.framework
20 | Flutter/Flutter.podspec
21 | Flutter/Generated.xcconfig
22 | Flutter/ephemeral/
23 | Flutter/app.flx
24 | Flutter/app.zip
25 | Flutter/flutter_assets/
26 | Flutter/flutter_export_environment.sh
27 | ServiceDefinitions.json
28 | Runner/GeneratedPluginRegistrant.*
29 |
30 | # Exceptions to above rules.
31 | !default.mode1v3
32 | !default.mode2v3
33 | !default.pbxuser
34 | !default.perspectivev3
35 |
--------------------------------------------------------------------------------
/example/ios/Flutter/AppFrameworkInfo.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | App
9 | CFBundleIdentifier
10 | io.flutter.flutter.app
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | App
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1.0
23 | MinimumOSVersion
24 | 13.0
25 |
26 |
27 |
--------------------------------------------------------------------------------
/example/ios/Flutter/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/example/ios/Flutter/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/example/ios/Podfile:
--------------------------------------------------------------------------------
1 | # Uncomment this line to define a global platform for your project
2 | platform :ios, '13.0'
3 |
4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true'
6 |
7 | project 'Runner', {
8 | 'Debug' => :debug,
9 | 'Profile' => :release,
10 | 'Release' => :release,
11 | }
12 |
13 | def flutter_root
14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
15 | unless File.exist?(generated_xcode_build_settings_path)
16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
17 | end
18 |
19 | File.foreach(generated_xcode_build_settings_path) do |line|
20 | matches = line.match(/FLUTTER_ROOT\=(.*)/)
21 | return matches[1].strip if matches
22 | end
23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
24 | end
25 |
26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
27 |
28 | flutter_ios_podfile_setup
29 |
30 | target 'Runner' do
31 | use_frameworks!
32 | use_modular_headers!
33 |
34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
35 | target 'RunnerTests' do
36 | inherit! :search_paths
37 | end
38 | end
39 |
40 | post_install do |installer|
41 | installer.pods_project.targets.each do |target|
42 | flutter_additional_ios_build_settings(target)
43 | end
44 | end
45 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/example/ios/Runner/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import Flutter
2 | import UIKit
3 |
4 | @main
5 | @objc class AppDelegate: FlutterAppDelegate {
6 | override func application(
7 | _ application: UIApplication,
8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
9 | ) -> Bool {
10 | GeneratedPluginRegistrant.register(with: self)
11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "20x20",
5 | "idiom" : "iphone",
6 | "filename" : "Icon-App-20x20@2x.png",
7 | "scale" : "2x"
8 | },
9 | {
10 | "size" : "20x20",
11 | "idiom" : "iphone",
12 | "filename" : "Icon-App-20x20@3x.png",
13 | "scale" : "3x"
14 | },
15 | {
16 | "size" : "29x29",
17 | "idiom" : "iphone",
18 | "filename" : "Icon-App-29x29@1x.png",
19 | "scale" : "1x"
20 | },
21 | {
22 | "size" : "29x29",
23 | "idiom" : "iphone",
24 | "filename" : "Icon-App-29x29@2x.png",
25 | "scale" : "2x"
26 | },
27 | {
28 | "size" : "29x29",
29 | "idiom" : "iphone",
30 | "filename" : "Icon-App-29x29@3x.png",
31 | "scale" : "3x"
32 | },
33 | {
34 | "size" : "40x40",
35 | "idiom" : "iphone",
36 | "filename" : "Icon-App-40x40@2x.png",
37 | "scale" : "2x"
38 | },
39 | {
40 | "size" : "40x40",
41 | "idiom" : "iphone",
42 | "filename" : "Icon-App-40x40@3x.png",
43 | "scale" : "3x"
44 | },
45 | {
46 | "size" : "60x60",
47 | "idiom" : "iphone",
48 | "filename" : "Icon-App-60x60@2x.png",
49 | "scale" : "2x"
50 | },
51 | {
52 | "size" : "60x60",
53 | "idiom" : "iphone",
54 | "filename" : "Icon-App-60x60@3x.png",
55 | "scale" : "3x"
56 | },
57 | {
58 | "size" : "20x20",
59 | "idiom" : "ipad",
60 | "filename" : "Icon-App-20x20@1x.png",
61 | "scale" : "1x"
62 | },
63 | {
64 | "size" : "20x20",
65 | "idiom" : "ipad",
66 | "filename" : "Icon-App-20x20@2x.png",
67 | "scale" : "2x"
68 | },
69 | {
70 | "size" : "29x29",
71 | "idiom" : "ipad",
72 | "filename" : "Icon-App-29x29@1x.png",
73 | "scale" : "1x"
74 | },
75 | {
76 | "size" : "29x29",
77 | "idiom" : "ipad",
78 | "filename" : "Icon-App-29x29@2x.png",
79 | "scale" : "2x"
80 | },
81 | {
82 | "size" : "40x40",
83 | "idiom" : "ipad",
84 | "filename" : "Icon-App-40x40@1x.png",
85 | "scale" : "1x"
86 | },
87 | {
88 | "size" : "40x40",
89 | "idiom" : "ipad",
90 | "filename" : "Icon-App-40x40@2x.png",
91 | "scale" : "2x"
92 | },
93 | {
94 | "size" : "76x76",
95 | "idiom" : "ipad",
96 | "filename" : "Icon-App-76x76@1x.png",
97 | "scale" : "1x"
98 | },
99 | {
100 | "size" : "76x76",
101 | "idiom" : "ipad",
102 | "filename" : "Icon-App-76x76@2x.png",
103 | "scale" : "2x"
104 | },
105 | {
106 | "size" : "83.5x83.5",
107 | "idiom" : "ipad",
108 | "filename" : "Icon-App-83.5x83.5@2x.png",
109 | "scale" : "2x"
110 | },
111 | {
112 | "size" : "1024x1024",
113 | "idiom" : "ios-marketing",
114 | "filename" : "Icon-App-1024x1024@1x.png",
115 | "scale" : "1x"
116 | }
117 | ],
118 | "info" : {
119 | "version" : 1,
120 | "author" : "xcode"
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/csells/flutter_ai_toolkit/e4efee359ce5ba8c44f0a0bcd8936e9367af326a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/csells/flutter_ai_toolkit/e4efee359ce5ba8c44f0a0bcd8936e9367af326a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/csells/flutter_ai_toolkit/e4efee359ce5ba8c44f0a0bcd8936e9367af326a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/csells/flutter_ai_toolkit/e4efee359ce5ba8c44f0a0bcd8936e9367af326a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/csells/flutter_ai_toolkit/e4efee359ce5ba8c44f0a0bcd8936e9367af326a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/csells/flutter_ai_toolkit/e4efee359ce5ba8c44f0a0bcd8936e9367af326a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/csells/flutter_ai_toolkit/e4efee359ce5ba8c44f0a0bcd8936e9367af326a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/csells/flutter_ai_toolkit/e4efee359ce5ba8c44f0a0bcd8936e9367af326a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/csells/flutter_ai_toolkit/e4efee359ce5ba8c44f0a0bcd8936e9367af326a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/csells/flutter_ai_toolkit/e4efee359ce5ba8c44f0a0bcd8936e9367af326a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/csells/flutter_ai_toolkit/e4efee359ce5ba8c44f0a0bcd8936e9367af326a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/csells/flutter_ai_toolkit/e4efee359ce5ba8c44f0a0bcd8936e9367af326a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/csells/flutter_ai_toolkit/e4efee359ce5ba8c44f0a0bcd8936e9367af326a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/csells/flutter_ai_toolkit/e4efee359ce5ba8c44f0a0bcd8936e9367af326a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/csells/flutter_ai_toolkit/e4efee359ce5ba8c44f0a0bcd8936e9367af326a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "LaunchImage.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "LaunchImage@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "LaunchImage@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/csells/flutter_ai_toolkit/e4efee359ce5ba8c44f0a0bcd8936e9367af326a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/csells/flutter_ai_toolkit/e4efee359ce5ba8c44f0a0bcd8936e9367af326a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/csells/flutter_ai_toolkit/e4efee359ce5ba8c44f0a0bcd8936e9367af326a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md:
--------------------------------------------------------------------------------
1 | # Launch Screen Assets
2 |
3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory.
4 |
5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
--------------------------------------------------------------------------------
/example/ios/Runner/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/example/ios/Runner/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/example/ios/Runner/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CADisableMinimumFrameDurationOnPhone
6 |
7 | CFBundleDevelopmentRegion
8 | $(DEVELOPMENT_LANGUAGE)
9 | CFBundleDisplayName
10 | Example
11 | CFBundleExecutable
12 | $(EXECUTABLE_NAME)
13 | CFBundleIdentifier
14 | $(PRODUCT_BUNDLE_IDENTIFIER)
15 | CFBundleInfoDictionaryVersion
16 | 6.0
17 | CFBundleName
18 | example
19 | CFBundlePackageType
20 | APPL
21 | CFBundleShortVersionString
22 | $(FLUTTER_BUILD_NAME)
23 | CFBundleSignature
24 | ????
25 | CFBundleVersion
26 | $(FLUTTER_BUILD_NUMBER)
27 | LSRequiresIPhoneOS
28 |
29 | NSCameraUsageDescription
30 | $(PRODUCT_NAME) would like to access your camera.
31 | NSMicrophoneUsageDescription
32 | $(PRODUCT_NAME) would like to access your microphone.
33 | NSPhotoLibraryUsageDescription
34 | $(PRODUCT_NAME) would like access to your photos.
35 | UIApplicationSupportsIndirectInputEvents
36 |
37 | UILaunchStoryboardName
38 | LaunchScreen
39 | UIMainStoryboardFile
40 | Main
41 | UISupportedInterfaceOrientations
42 |
43 | UIInterfaceOrientationPortrait
44 | UIInterfaceOrientationLandscapeLeft
45 | UIInterfaceOrientationLandscapeRight
46 |
47 | UISupportedInterfaceOrientations~ipad
48 |
49 | UIInterfaceOrientationPortrait
50 | UIInterfaceOrientationPortraitUpsideDown
51 | UIInterfaceOrientationLandscapeLeft
52 | UIInterfaceOrientationLandscapeRight
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/example/ios/Runner/Runner-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | #import "GeneratedPluginRegistrant.h"
2 |
--------------------------------------------------------------------------------
/example/ios/RunnerTests/RunnerTests.swift:
--------------------------------------------------------------------------------
1 | import Flutter
2 | import UIKit
3 | import XCTest
4 |
5 | class RunnerTests: XCTestCase {
6 |
7 | func testExample() {
8 | // If you add code to the Runner application, consider adding tests here.
9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest.
10 | }
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/example/lib/cupertino/cupertino.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2024 The Flutter Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | import 'package:flutter/cupertino.dart';
6 | import 'package:flutter_ai_toolkit/flutter_ai_toolkit.dart';
7 | import 'package:google_generative_ai/google_generative_ai.dart';
8 |
9 | import '../gemini_api_key.dart';
10 |
11 | void main() => runApp(const App());
12 |
13 | class App extends StatelessWidget {
14 | static const title = 'Example: Cupertino';
15 |
16 | const App({super.key});
17 |
18 | @override
19 | Widget build(BuildContext context) => const CupertinoApp(
20 | title: title,
21 | home: ChatPage(),
22 | );
23 | }
24 |
25 | class ChatPage extends StatelessWidget {
26 | const ChatPage({super.key});
27 |
28 | @override
29 | Widget build(BuildContext context) => CupertinoPageScaffold(
30 | navigationBar: CupertinoNavigationBar(
31 | middle: Text(App.title),
32 | ),
33 | child: LlmChatView(
34 | provider: GeminiProvider(
35 | model: GenerativeModel(
36 | model: 'gemini-1.5-flash',
37 | apiKey: geminiApiKey,
38 | ),
39 | ),
40 | ),
41 | );
42 | }
43 |
--------------------------------------------------------------------------------
/example/lib/dark_mode/dark_mode.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2024 The Flutter Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | import 'package:flutter/material.dart';
6 | import 'package:flutter_ai_toolkit/flutter_ai_toolkit.dart';
7 | import 'package:google_generative_ai/google_generative_ai.dart';
8 |
9 | import '../gemini_api_key.dart';
10 | import '../dark_style.dart';
11 |
12 | void main() => runApp(const App());
13 |
14 | class App extends StatelessWidget {
15 | static const title = 'Example: Dark Mode';
16 | static final themeMode = ValueNotifier(ThemeMode.dark);
17 |
18 | const App({super.key});
19 |
20 | @override
21 | Widget build(BuildContext context) => ValueListenableBuilder(
22 | valueListenable: themeMode,
23 | builder: (
24 | BuildContext context,
25 | ThemeMode mode,
26 | Widget? child,
27 | ) =>
28 | MaterialApp(
29 | title: title,
30 | theme: ThemeData.light(),
31 | darkTheme: ThemeData.dark(),
32 | themeMode: mode,
33 | home: ChatPage(),
34 | debugShowCheckedModeBanner: false,
35 | ),
36 | );
37 | }
38 |
39 | class ChatPage extends StatefulWidget {
40 | const ChatPage({super.key});
41 |
42 | @override
43 | State createState() => _ChatPageState();
44 | }
45 |
46 | class _ChatPageState extends State {
47 | final _provider = GeminiProvider(
48 | model: GenerativeModel(
49 | model: 'gemini-1.5-flash',
50 | apiKey: geminiApiKey,
51 | ),
52 | );
53 |
54 | final _lightStyle = LlmChatViewStyle.defaultStyle();
55 | final _darkStyle = darkChatViewStyle();
56 |
57 | @override
58 | Widget build(BuildContext context) => Scaffold(
59 | appBar: AppBar(
60 | title: const Text(App.title),
61 | actions: [
62 | IconButton(
63 | onPressed: () => App.themeMode.value =
64 | App.themeMode.value == ThemeMode.light
65 | ? ThemeMode.dark
66 | : ThemeMode.light,
67 | tooltip: App.themeMode.value == ThemeMode.light
68 | ? 'Dark Mode'
69 | : 'Light Mode',
70 | icon: const Icon(Icons.brightness_4_outlined),
71 | ),
72 | ],
73 | ),
74 | body: LlmChatView(
75 | provider: _provider,
76 | style:
77 | App.themeMode.value == ThemeMode.dark ? _darkStyle : _lightStyle,
78 | ),
79 | );
80 | }
81 |
--------------------------------------------------------------------------------
/example/lib/demo/api_key_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter/services.dart';
3 | import 'package:gap/gap.dart';
4 | import 'package:url_launcher/url_launcher.dart';
5 |
6 | class GeminiApiKeyPage extends StatefulWidget {
7 | const GeminiApiKeyPage({
8 | required this.title,
9 | required this.onApiKey,
10 | super.key,
11 | });
12 |
13 | final String title;
14 | final void Function(String apiKey) onApiKey;
15 |
16 | @override
17 | State createState() => _GeminiApiKeyPageState();
18 | }
19 |
20 | class _GeminiApiKeyPageState extends State {
21 | static final url = Uri.parse('https://aistudio.google.com/app/apikey');
22 | final _controller = TextEditingController();
23 |
24 | @override
25 | void dispose() {
26 | _controller.dispose();
27 | super.dispose();
28 | }
29 |
30 | @override
31 | Widget build(BuildContext context) => Scaffold(
32 | appBar: AppBar(title: Text(widget.title)),
33 | body: Center(
34 | child: ValueListenableBuilder(
35 | valueListenable: _controller,
36 | builder: (context, value, child) => Column(
37 | children: [
38 | const Text('To run this sample, you need a Gemini API key.\n'
39 | 'Get your Gemini API Key from the following URL:'),
40 | GestureDetector(
41 | onTap: () => launchUrl(url, webOnlyWindowName: '_blank'),
42 | child: MouseRegion(
43 | cursor: SystemMouseCursors.click,
44 | child: Text(
45 | url.toString(),
46 | style: const TextStyle(
47 | color: Colors.blue,
48 | decoration: TextDecoration.underline,
49 | ),
50 | ),
51 | ),
52 | ),
53 | GestureDetector(
54 | onTap: _copyUrl,
55 | child: const MouseRegion(
56 | cursor: SystemMouseCursors.click,
57 | child: Text('(or copy the URL above by tapping HERE)'),
58 | ),
59 | ),
60 | const Gap(16),
61 | const Text('Paste your API key here:'),
62 | SizedBox(
63 | width: 300,
64 | child: TextField(
65 | controller: _controller,
66 | decoration: InputDecoration(
67 | labelText: 'Gemini API Key',
68 | errorText: _isValidApiKey()
69 | ? null
70 | : 'API key must be 39 characters',
71 | ),
72 | onSubmitted: _isValidApiKey()
73 | ? (apiKey) => widget.onApiKey(apiKey)
74 | : null,
75 | ),
76 | ),
77 | const Gap(16),
78 | ElevatedButton(
79 | onPressed: _isValidApiKey()
80 | ? () => widget.onApiKey(_controller.text)
81 | : null,
82 | child: const Text('Submit'),
83 | ),
84 | ],
85 | ),
86 | ),
87 | ),
88 | );
89 |
90 | bool _isValidApiKey() => _controller.text.length == 39;
91 |
92 | void _copyUrl() {
93 | Clipboard.setData(ClipboardData(text: url.toString()));
94 | ScaffoldMessenger.of(context).showSnackBar(
95 | const SnackBar(content: Text('Copied URL to clipboard')),
96 | );
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/example/lib/echo/echo.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2024 The Flutter Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | import 'package:flutter/material.dart';
6 | import 'package:flutter_ai_toolkit/flutter_ai_toolkit.dart';
7 |
8 | void main() => runApp(App());
9 |
10 | class App extends StatefulWidget {
11 | static const title = 'Example: Echo Test';
12 |
13 | const App({super.key});
14 |
15 | @override
16 | State createState() => _AppState();
17 | }
18 |
19 | class _AppState extends State {
20 | final _provider = EchoProvider();
21 |
22 | @override
23 | Widget build(BuildContext context) => MaterialApp(
24 | title: App.title,
25 | home: Scaffold(
26 | appBar: AppBar(title: const Text(App.title)),
27 | body: LlmChatView(provider: _provider),
28 | ),
29 | );
30 | }
31 |
--------------------------------------------------------------------------------
/example/lib/gemini/gemini.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2024 The Flutter Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | import 'package:flutter/material.dart';
6 | import 'package:flutter_ai_toolkit/flutter_ai_toolkit.dart';
7 | import 'package:google_generative_ai/google_generative_ai.dart';
8 |
9 | import '../gemini_api_key.dart';
10 |
11 | void main() => runApp(const App());
12 |
13 | class App extends StatelessWidget {
14 | static const title = 'Example: Google Gemini AI';
15 |
16 | const App({super.key});
17 |
18 | @override
19 | Widget build(BuildContext context) => const MaterialApp(
20 | title: title,
21 | home: ChatPage(),
22 | );
23 | }
24 |
25 | class ChatPage extends StatelessWidget {
26 | const ChatPage({super.key});
27 |
28 | @override
29 | Widget build(BuildContext context) => Scaffold(
30 | appBar: AppBar(title: const Text(App.title)),
31 | body: LlmChatView(
32 | provider: GeminiProvider(
33 | model: GenerativeModel(
34 | model: 'gemini-1.5-flash',
35 | apiKey: geminiApiKey,
36 | ),
37 | ),
38 | ),
39 | );
40 | }
41 |
--------------------------------------------------------------------------------
/example/lib/logging/logging.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2024 The Flutter Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | import 'package:flutter/material.dart';
6 | import 'package:flutter_ai_toolkit/flutter_ai_toolkit.dart';
7 | import 'package:google_generative_ai/google_generative_ai.dart';
8 |
9 | import '../gemini_api_key.dart';
10 |
11 | void main() => runApp(const App());
12 |
13 | class App extends StatelessWidget {
14 | static const title = 'Example: Logging';
15 |
16 | const App({super.key});
17 |
18 | @override
19 | Widget build(BuildContext context) => MaterialApp(
20 | title: title,
21 | home: ChatPage(),
22 | );
23 | }
24 |
25 | class ChatPage extends StatelessWidget {
26 | ChatPage({super.key});
27 | final _provider = GeminiProvider(
28 | model: GenerativeModel(
29 | model: 'gemini-1.5-flash',
30 | apiKey: geminiApiKey,
31 | ),
32 | );
33 |
34 | @override
35 | Widget build(BuildContext context) {
36 | return Scaffold(
37 | appBar: AppBar(title: const Text(App.title)),
38 | body: LlmChatView(
39 | provider: _provider,
40 | messageSender: _logMessage,
41 | ),
42 | );
43 | }
44 |
45 | Stream _logMessage(
46 | String prompt, {
47 | required Iterable attachments,
48 | }) async* {
49 | // log the message and attachments
50 | debugPrint('# Sending Message');
51 | debugPrint('## Prompt\n$prompt');
52 | debugPrint('## Attachments\n${attachments.map((a) => a.toString())}');
53 |
54 | // forward the message on to the provider
55 | final response = _provider.sendMessageStream(
56 | prompt,
57 | attachments: attachments,
58 | );
59 |
60 | // log the response
61 | final text = response.join();
62 | debugPrint('## Response\n$text');
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/example/lib/main.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2024 The Flutter Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | import 'package:flutter/material.dart';
6 | import 'package:flutter_ai_toolkit/flutter_ai_toolkit.dart';
7 | import 'package:google_generative_ai/google_generative_ai.dart';
8 |
9 | import '../gemini_api_key.dart';
10 |
11 | void main() => runApp(const App());
12 |
13 | class App extends StatelessWidget {
14 | static const title = 'Example: Google Gemini AI';
15 |
16 | const App({super.key});
17 |
18 | @override
19 | Widget build(BuildContext context) => const MaterialApp(
20 | title: title,
21 | home: ChatPage(),
22 | );
23 | }
24 |
25 | class ChatPage extends StatelessWidget {
26 | const ChatPage({super.key});
27 |
28 | @override
29 | Widget build(BuildContext context) => Scaffold(
30 | appBar: AppBar(title: const Text(App.title)),
31 | body: LlmChatView(
32 | provider: GeminiProvider(
33 | model: GenerativeModel(
34 | model: 'gemini-1.5-flash',
35 | apiKey: geminiApiKey,
36 | ),
37 | ),
38 | ),
39 | );
40 | }
41 |
--------------------------------------------------------------------------------
/example/lib/recipes/data/recipe_data.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2024 The Flutter Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | import 'dart:convert';
6 |
7 | import 'package:uuid/uuid.dart';
8 |
9 | class Recipe {
10 | Recipe({
11 | required this.id,
12 | required this.title,
13 | required this.description,
14 | required this.ingredients,
15 | required this.instructions,
16 | this.tags = const [],
17 | this.notes = '',
18 | });
19 |
20 | Recipe.empty(String id)
21 | : this(
22 | id: id,
23 | title: '',
24 | description: '',
25 | ingredients: [],
26 | instructions: [],
27 | tags: [],
28 | notes: '',
29 | );
30 |
31 | factory Recipe.fromJson(Map json) => Recipe(
32 | id: json['id'] ?? const Uuid().v4(),
33 | title: json['title'],
34 | description: json['description'],
35 | ingredients: List.from(json['ingredients']),
36 | instructions: List.from(json['instructions']),
37 | tags: json['tags'] == null ? [] : List.from(json['tags']),
38 | notes: json['notes'] ?? '',
39 | );
40 |
41 | final String id;
42 | final String title;
43 | final String description;
44 | final List ingredients;
45 | final List instructions;
46 | final List tags;
47 | final String notes;
48 |
49 | Map toJson() => {
50 | 'id': id,
51 | 'title': title,
52 | 'description': description,
53 | 'ingredients': ingredients,
54 | 'instructions': instructions,
55 | 'tags': tags,
56 | 'notes': notes,
57 | };
58 |
59 | static Future> loadFrom(String json) async {
60 | final jsonList = jsonDecode(json) as List;
61 | return [for (final json in jsonList) Recipe.fromJson(json)];
62 | }
63 |
64 | @override
65 | String toString() => '''# $title
66 | $description
67 |
68 | ## Ingredients
69 | ${ingredients.join('\n')}
70 |
71 | ## Instructions
72 | ${instructions.join('\n')}
73 | ''';
74 | }
75 |
76 | class RecipeEmbedding {
77 | RecipeEmbedding({
78 | required this.id,
79 | required this.embedding,
80 | });
81 |
82 | factory RecipeEmbedding.fromJson(Map json) =>
83 | RecipeEmbedding(
84 | id: json['id'],
85 | embedding: List.from(json['embedding']),
86 | );
87 |
88 | final String id;
89 | final List embedding;
90 |
91 | static Future> loadFrom(String json) async {
92 | final jsonList = jsonDecode(json) as List;
93 | return [for (final json in jsonList) RecipeEmbedding.fromJson(json)];
94 | }
95 |
96 | Map toJson() => {
97 | 'id': id,
98 | 'embedding': embedding,
99 | };
100 | }
101 |
--------------------------------------------------------------------------------
/example/lib/recipes/data/recipe_repository.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2024 The Flutter Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | import 'dart:async';
6 | import 'dart:convert';
7 | import 'dart:io' as io;
8 |
9 | import 'package:flutter/foundation.dart';
10 | import 'package:flutter/services.dart' show rootBundle;
11 | import 'package:path/path.dart' as path;
12 | import 'package:path_provider/path_provider.dart' as pp;
13 |
14 | import 'recipe_data.dart';
15 |
16 | class RecipeRepository {
17 | static const newRecipeID = '__NEW_RECIPE__';
18 | static const _fileName = 'recipes.json';
19 |
20 | static const _assetFileName = 'assets/recipes_default.json';
21 |
22 | static List? _recipes;
23 | static final items = ValueNotifier>([]);
24 |
25 | static Future init() async {
26 | assert(_recipes == null, 'call init() only once');
27 | _recipes = await _loadRecipes();
28 | items.value = _recipes!;
29 | }
30 |
31 | static Iterable get recipes {
32 | assert(_recipes != null, 'call init() first');
33 | return _recipes!;
34 | }
35 |
36 | static Recipe getRecipe(String recipeId) {
37 | assert(_recipes != null, 'call init() first');
38 | if (recipeId == newRecipeID) return Recipe.empty(newRecipeID);
39 | return _recipes!.singleWhere((r) => r.id == recipeId);
40 | }
41 |
42 | static Future addNewRecipe(Recipe newRecipe) async {
43 | assert(_recipes != null, 'call init() first');
44 | _recipes!.add(newRecipe);
45 | await _saveRecipes();
46 | }
47 |
48 | static Future updateRecipe(Recipe recipe) async {
49 | assert(_recipes != null, 'call init() first');
50 | final i = _recipes!.indexWhere((r) => r.id == recipe.id);
51 | assert(i >= 0);
52 | _recipes![i] = recipe;
53 | await _saveRecipes();
54 | }
55 |
56 | static Future deleteRecipe(Recipe recipe) async {
57 | assert(_recipes != null, 'call init() first');
58 | final removed = _recipes!.remove(recipe);
59 | assert(removed);
60 | await _saveRecipes();
61 | }
62 |
63 | static Future get _recipeFile async {
64 | final directory = await pp.getApplicationSupportDirectory();
65 | return io.File(path.join(directory.path, _fileName));
66 | }
67 |
68 | static Future> _loadRecipes() async {
69 | // seed empty recipe file w/ sample recipes; note: we're not loading from a
70 | // file on the web; all recipes are in memory for the sessions only
71 | late final String contents;
72 | if (!kIsWeb) {
73 | final recipeFile = await _recipeFile;
74 | contents = await recipeFile.exists()
75 | ? await recipeFile.readAsString()
76 | : await rootBundle.loadString(_assetFileName);
77 | } else {
78 | contents = await rootBundle.loadString(_assetFileName);
79 | }
80 |
81 | final jsonList = json.decode(contents) as List;
82 | return jsonList.map((json) => Recipe.fromJson(json)).toList();
83 | }
84 |
85 | static Future _saveRecipes() async {
86 | // note: we're not saving to a file on the web; all recipes are in memory
87 | // for the sessions only
88 | if (!kIsWeb) {
89 | final file = await _recipeFile;
90 | final jsonString = json.encode(recipes.map((r) => r.toJson()).toList());
91 | await file.writeAsString(jsonString);
92 | }
93 |
94 | // notify listeners that the recipes have changed
95 | items.value = [];
96 | items.value = _recipes!;
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/example/lib/recipes/data/settings.dart:
--------------------------------------------------------------------------------
1 | import 'package:shared_preferences/shared_preferences.dart';
2 |
3 | class Settings {
4 | Settings._();
5 |
6 | static SharedPreferencesWithCache? _prefs;
7 |
8 | static Future init() async {
9 | assert(_prefs == null, 'call Settings.init() exactly once');
10 | _prefs = await SharedPreferencesWithCache.create(
11 | cacheOptions: SharedPreferencesWithCacheOptions(),
12 | );
13 | }
14 |
15 | static String get foodPreferences {
16 | assert(_prefs != null, 'call Settings.init() exactly once');
17 | return _prefs!.getString('foodPreferences') ?? '';
18 | }
19 |
20 | static Future setFoodPreferences(String value) async {
21 | assert(_prefs != null, 'call Settings.init() exactly once');
22 | await _prefs!.setString('foodPreferences', value);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/example/lib/recipes/pages/split_or_tabs.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:split_view/split_view.dart';
3 |
4 | class SplitOrTabs extends StatefulWidget {
5 | const SplitOrTabs({
6 | required this.tabs,
7 | required this.children,
8 | super.key,
9 | });
10 | final List tabs;
11 | final List children;
12 |
13 | @override
14 | State createState() => _SplitOrTabsState();
15 | }
16 |
17 | class _SplitOrTabsState extends State
18 | with SingleTickerProviderStateMixin {
19 | late TabController _tabController;
20 |
21 | @override
22 | void initState() {
23 | super.initState();
24 | _tabController = TabController(length: widget.tabs.length, vsync: this);
25 | }
26 |
27 | @override
28 | void dispose() {
29 | _tabController.dispose();
30 | super.dispose();
31 | }
32 |
33 | @override
34 | Widget build(BuildContext context) => MediaQuery.of(context).size.width > 600
35 | ? SplitView(
36 | viewMode: SplitViewMode.Horizontal,
37 | gripColor: Colors.transparent,
38 | indicator: SplitIndicator(
39 | viewMode: SplitViewMode.Horizontal,
40 | color: Colors.grey,
41 | ),
42 | gripColorActive: Colors.transparent,
43 | activeIndicator: SplitIndicator(
44 | viewMode: SplitViewMode.Horizontal,
45 | isActive: true,
46 | color: Colors.black,
47 | ),
48 | children: widget.children,
49 | )
50 | : Column(
51 | children: [
52 | TabBar(
53 | controller: _tabController,
54 | tabs: widget.tabs,
55 | ),
56 | Expanded(
57 | child: TabBarView(
58 | controller: _tabController,
59 | children: widget.children,
60 | ),
61 | ),
62 | ],
63 | );
64 | }
65 |
--------------------------------------------------------------------------------
/example/lib/recipes/recipes.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2024 The Flutter Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | import 'package:flutter/material.dart';
6 | import 'package:go_router/go_router.dart';
7 |
8 | import 'data/recipe_repository.dart';
9 | import 'data/settings.dart';
10 | import 'pages/edit_recipe_page.dart';
11 | import 'pages/home_page.dart';
12 |
13 | void main() async {
14 | WidgetsFlutterBinding.ensureInitialized();
15 | await Settings.init();
16 | await RecipeRepository.init();
17 | runApp(App());
18 | }
19 |
20 | class App extends StatelessWidget {
21 | App({super.key});
22 |
23 | final _router = GoRouter(
24 | routes: [
25 | GoRoute(
26 | name: 'home',
27 | path: '/',
28 | builder: (BuildContext context, _) => const HomePage(),
29 | routes: [
30 | GoRoute(
31 | name: 'edit',
32 | path: 'edit/:recipe',
33 | builder: (context, state) {
34 | final recipeId = state.pathParameters['recipe']!;
35 | final recipe = RecipeRepository.getRecipe(recipeId);
36 | return EditRecipePage(recipe: recipe);
37 | },
38 | ),
39 | ],
40 | ),
41 | ],
42 | );
43 |
44 | @override
45 | Widget build(BuildContext context) =>
46 | MaterialApp.router(routerConfig: _router);
47 | }
48 |
--------------------------------------------------------------------------------
/example/lib/recipes/views/recipe_content_view.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2024 The Flutter Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | import 'package:flutter/material.dart';
6 | import 'package:gap/gap.dart';
7 |
8 | import '../data/recipe_data.dart';
9 |
10 | class RecipeContentView extends StatelessWidget {
11 | const RecipeContentView({
12 | super.key,
13 | required this.recipe,
14 | });
15 |
16 | final Recipe recipe;
17 | static const mobileBreakpoint = 600;
18 |
19 | @override
20 | Widget build(BuildContext context) => Padding(
21 | padding: const EdgeInsets.all(16),
22 | child: LayoutBuilder(
23 | builder: (context, constraints) =>
24 | constraints.maxWidth < mobileBreakpoint
25 | ? SingleChildScrollView(
26 | child: Column(
27 | crossAxisAlignment: CrossAxisAlignment.start,
28 | children: [
29 | _RecipeIngredientsView(recipe),
30 | const Gap(16),
31 | _RecipeInstructionsView(recipe),
32 | ],
33 | ),
34 | )
35 | : Row(
36 | crossAxisAlignment: CrossAxisAlignment.start,
37 | children: [
38 | Expanded(child: _RecipeIngredientsView(recipe)),
39 | const Gap(16),
40 | Expanded(child: _RecipeInstructionsView(recipe)),
41 | ],
42 | ),
43 | ),
44 | );
45 | }
46 |
47 | class _RecipeIngredientsView extends StatelessWidget {
48 | const _RecipeIngredientsView(this.recipe);
49 | final Recipe recipe;
50 |
51 | @override
52 | Widget build(BuildContext context) => Column(
53 | crossAxisAlignment: CrossAxisAlignment.start,
54 | children: [
55 | Text(
56 | 'Ingredients:🍎',
57 | style: Theme.of(context).textTheme.titleMedium,
58 | ),
59 | ...[
60 | for (final ingredient in recipe.ingredients) Text('• $ingredient')
61 | ],
62 | ],
63 | );
64 | }
65 |
66 | class _RecipeInstructionsView extends StatelessWidget {
67 | const _RecipeInstructionsView(this.recipe);
68 | final Recipe recipe;
69 |
70 | @override
71 | Widget build(BuildContext context) => Column(
72 | crossAxisAlignment: CrossAxisAlignment.start,
73 | children: [
74 | Text(
75 | 'Instructions:🥧',
76 | style: Theme.of(context).textTheme.titleMedium,
77 | ),
78 | ...[
79 | for (final entry in recipe.instructions.asMap().entries)
80 | Text('${entry.key + 1}. ${entry.value}')
81 | ],
82 | ],
83 | );
84 | }
85 |
--------------------------------------------------------------------------------
/example/lib/recipes/views/recipe_list_view.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2024 The Flutter Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | import 'package:flutter/material.dart';
6 | import 'package:go_router/go_router.dart';
7 |
8 | import '../data/recipe_data.dart';
9 | import '../data/recipe_repository.dart';
10 | import 'recipe_view.dart';
11 |
12 | class RecipeListView extends StatefulWidget {
13 | final String searchText;
14 |
15 | const RecipeListView({super.key, required this.searchText});
16 |
17 | @override
18 | _RecipeListViewState createState() => _RecipeListViewState();
19 | }
20 |
21 | class _RecipeListViewState extends State {
22 | final _expanded = {};
23 |
24 | Iterable _filteredRecipes(Iterable recipes) => recipes
25 | .where((recipe) =>
26 | recipe.title
27 | .toLowerCase()
28 | .contains(widget.searchText.toLowerCase()) ||
29 | recipe.description
30 | .toLowerCase()
31 | .contains(widget.searchText.toLowerCase()) ||
32 | recipe.tags.any((tag) =>
33 | tag.toLowerCase().contains(widget.searchText.toLowerCase())))
34 | .toList()
35 | ..sort((a, b) => a.title.toLowerCase().compareTo(b.title.toLowerCase()));
36 |
37 | @override
38 | Widget build(BuildContext context) =>
39 | ValueListenableBuilder?>(
40 | valueListenable: RecipeRepository.items,
41 | builder: (context, recipes, child) {
42 | if (recipes == null) {
43 | return const Center(child: CircularProgressIndicator());
44 | }
45 |
46 | final displayedRecipes = _filteredRecipes(recipes).toList();
47 | return ListView.builder(
48 | itemCount: displayedRecipes.length,
49 | itemBuilder: (context, index) {
50 | final recipe = displayedRecipes[index];
51 | final recipeId = recipe.id;
52 | return RecipeView(
53 | key: ValueKey(recipeId),
54 | recipe: recipe,
55 | expanded: _expanded[recipeId] == true,
56 | onExpansionChanged: (expanded) =>
57 | _onExpand(recipe.id, expanded),
58 | onEdit: () => _onEdit(recipe),
59 | onDelete: () => _onDelete(recipe),
60 | );
61 | },
62 | );
63 | },
64 | );
65 |
66 | void _onExpand(String recipeId, bool expanded) =>
67 | _expanded[recipeId] = expanded;
68 |
69 | void _onEdit(Recipe recipe) => context.goNamed(
70 | 'edit',
71 | pathParameters: {'recipe': recipe.id},
72 | );
73 |
74 | void _onDelete(Recipe recipe) async {
75 | final shouldDelete = await showDialog(
76 | context: context,
77 | builder: (context) => AlertDialog(
78 | title: const Text('Delete Recipe'),
79 | content: Text(
80 | 'Are you sure you want to delete the recipe "${recipe.title}"?',
81 | ),
82 | actions: [
83 | TextButton(
84 | onPressed: () => Navigator.pop(context, false),
85 | child: const Text('Cancel'),
86 | ),
87 | TextButton(
88 | onPressed: () => Navigator.pop(context, true),
89 | child: const Text('Delete'),
90 | ),
91 | ],
92 | ),
93 | );
94 |
95 | if (shouldDelete == true) await RecipeRepository.deleteRecipe(recipe);
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/example/lib/recipes/views/recipe_response_view.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 |
3 | import 'package:flutter/material.dart';
4 | import 'package:flutter_markdown/flutter_markdown.dart';
5 | import 'package:gap/gap.dart';
6 |
7 | import '../data/recipe_data.dart';
8 | import '../data/recipe_repository.dart';
9 | import 'recipe_content_view.dart';
10 |
11 | class RecipeResponseView extends StatelessWidget {
12 | const RecipeResponseView(this.response, {super.key});
13 |
14 | final String response;
15 |
16 | @override
17 | Widget build(BuildContext context) {
18 | final children = [];
19 | String? finalText;
20 |
21 | // created with the response from the LLM as the response streams in, so
22 | // many not be a complete response yet
23 | try {
24 | final map = jsonDecode(response);
25 | final recipesWithText = map['recipes'] as List;
26 | finalText = map['text'] as String?;
27 |
28 | for (final recipeWithText in recipesWithText) {
29 | // extract the text before the recipe
30 | final text = recipeWithText['text'] as String?;
31 | if (text != null && text.isNotEmpty) {
32 | children.add(MarkdownBody(data: text));
33 | }
34 |
35 | // extract the recipe
36 | final json = recipeWithText['recipe'] as Map;
37 | final recipe = Recipe.fromJson(json);
38 | children.add(const Gap(16));
39 | children.add(Column(
40 | crossAxisAlignment: CrossAxisAlignment.start,
41 | children: [
42 | Text(recipe.title, style: Theme.of(context).textTheme.titleLarge),
43 | Text(recipe.description),
44 | RecipeContentView(recipe: recipe),
45 | ],
46 | ));
47 |
48 | // add a button to add the recipe to the list
49 | children.add(const Gap(16));
50 | children.add(OutlinedButton(
51 | onPressed: () => RecipeRepository.addNewRecipe(recipe),
52 | child: const Text('Add Recipe'),
53 | ));
54 | children.add(const Gap(16));
55 | }
56 | } catch (e) {
57 | debugPrint('Error parsing response: $e');
58 | }
59 |
60 | if (children.isEmpty) {
61 | try {
62 | final map = jsonDecode(response);
63 | finalText = map['text'] as String?;
64 | } catch (e) {
65 | debugPrint('Error parsing response: $e');
66 | finalText = response;
67 | }
68 | }
69 |
70 | // add the remaining text
71 | if (finalText != null && finalText.isNotEmpty) {
72 | children.add(MarkdownBody(data: finalText));
73 | }
74 |
75 | return Column(
76 | crossAxisAlignment: CrossAxisAlignment.start,
77 | children: children,
78 | );
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/example/lib/recipes/views/recipe_view.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2024 The Flutter Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | import 'package:flutter/material.dart';
6 | import 'package:gap/gap.dart';
7 |
8 | import '../data/recipe_data.dart';
9 | import 'recipe_content_view.dart';
10 |
11 | class RecipeView extends StatelessWidget {
12 | const RecipeView({
13 | required this.recipe,
14 | required this.expanded,
15 | required this.onExpansionChanged,
16 | required this.onEdit,
17 | required this.onDelete,
18 | super.key,
19 | });
20 |
21 | final Recipe recipe;
22 | final bool expanded;
23 | final ValueChanged? onExpansionChanged;
24 | final Function() onEdit;
25 | final Function() onDelete;
26 |
27 | @override
28 | Widget build(BuildContext context) => Card(
29 | child: Column(
30 | children: [
31 | ExpansionTile(
32 | title: Text(recipe.title),
33 | subtitle: Text(recipe.description),
34 | initiallyExpanded: expanded,
35 | onExpansionChanged: onExpansionChanged,
36 | children: [
37 | RecipeContentView(recipe: recipe),
38 | Padding(
39 | padding: const EdgeInsets.all(8.0),
40 | child: OverflowBar(
41 | spacing: 8,
42 | alignment: MainAxisAlignment.end,
43 | children: [
44 | ElevatedButton(
45 | onPressed: onDelete,
46 | child: const Text('Delete'),
47 | ),
48 | OutlinedButton(
49 | onPressed: onEdit,
50 | child: const Text('Edit'),
51 | ),
52 | ],
53 | ),
54 | ),
55 | const Gap(16),
56 | ],
57 | ),
58 | ],
59 | ),
60 | );
61 | }
62 |
--------------------------------------------------------------------------------
/example/lib/recipes/views/search_box.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2024 The Flutter Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | import 'package:flutter/material.dart';
6 |
7 | class SearchBox extends StatefulWidget {
8 | final Function(String) onSearchChanged;
9 |
10 | const SearchBox({super.key, required this.onSearchChanged});
11 |
12 | @override
13 | _SearchBoxState createState() => _SearchBoxState();
14 | }
15 |
16 | class _SearchBoxState extends State
17 | with AutomaticKeepAliveClientMixin {
18 | @override
19 | bool get wantKeepAlive => true;
20 |
21 | final TextEditingController _searchController = TextEditingController();
22 |
23 | @override
24 | void dispose() {
25 | _searchController.dispose();
26 | super.dispose();
27 | }
28 |
29 | @override
30 | Widget build(BuildContext context) {
31 | super.build(context);
32 | return Padding(
33 | padding: const EdgeInsets.all(8),
34 | child: TextField(
35 | controller: _searchController,
36 | decoration: const InputDecoration(
37 | labelText: 'Search recipes',
38 | border: OutlineInputBorder(),
39 | suffixIcon: Icon(Icons.search),
40 | ),
41 | onChanged: widget.onSearchChanged,
42 | ),
43 | );
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/example/lib/recipes/views/settings_drawer.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | import '../data/settings.dart';
4 |
5 | class SettingsDrawer extends StatelessWidget {
6 | SettingsDrawer({super.key, required this.onSave});
7 | final VoidCallback onSave;
8 |
9 | final controller = TextEditingController(
10 | text: Settings.foodPreferences,
11 | );
12 |
13 | @override
14 | Widget build(BuildContext context) => Drawer(
15 | child: ListView(
16 | children: [
17 | const DrawerHeader(child: Text('Food Preferences')),
18 | Padding(
19 | padding: const EdgeInsets.all(8.0),
20 | child: TextField(
21 | controller: controller,
22 | maxLines: 5,
23 | decoration: const InputDecoration(
24 | hintText: 'Enter your food preferences...',
25 | border: OutlineInputBorder(
26 | borderSide: BorderSide(width: 1),
27 | ),
28 | enabledBorder: OutlineInputBorder(
29 | borderSide: BorderSide(width: 1),
30 | ),
31 | focusedBorder: OutlineInputBorder(
32 | borderSide: BorderSide(width: 1),
33 | ),
34 | ),
35 | ),
36 | ),
37 | Align(
38 | alignment: Alignment.centerRight,
39 | child: Padding(
40 | padding: const EdgeInsets.all(8.0),
41 | child: OverflowBar(
42 | spacing: 8,
43 | children: [
44 | ElevatedButton(
45 | child: const Text('Cancel'),
46 | onPressed: () {
47 | Navigator.of(context).pop();
48 | },
49 | ),
50 | OutlinedButton(
51 | child: const Text('Save'),
52 | onPressed: () {
53 | Settings.setFoodPreferences(controller.text);
54 | Navigator.of(context).pop();
55 | onSave();
56 | },
57 | ),
58 | ],
59 | ),
60 | ),
61 | ),
62 | ],
63 | ),
64 | );
65 | }
66 |
--------------------------------------------------------------------------------
/example/lib/suggestions/suggestions.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2024 The Flutter Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | import 'package:flutter/material.dart';
6 | import 'package:flutter_ai_toolkit/flutter_ai_toolkit.dart';
7 | import 'package:google_generative_ai/google_generative_ai.dart';
8 |
9 | import '../gemini_api_key.dart';
10 |
11 | void main() => runApp(const App());
12 |
13 | class App extends StatelessWidget {
14 | static const title = 'Example: Suggestions';
15 |
16 | const App({super.key});
17 |
18 | @override
19 | Widget build(BuildContext context) => MaterialApp(
20 | title: title,
21 | home: ChatPage(),
22 | debugShowCheckedModeBanner: false,
23 | );
24 | }
25 |
26 | class ChatPage extends StatefulWidget {
27 | const ChatPage({super.key});
28 |
29 | @override
30 | State createState() => _ChatPageState();
31 | }
32 |
33 | class _ChatPageState extends State {
34 | final _provider = GeminiProvider(
35 | model: GenerativeModel(
36 | model: 'gemini-1.5-flash',
37 | apiKey: geminiApiKey,
38 | ),
39 | );
40 |
41 | @override
42 | Widget build(BuildContext context) => Scaffold(
43 | appBar: AppBar(
44 | title: const Text(App.title),
45 | actions: [
46 | IconButton(
47 | onPressed: _clearHistory,
48 | icon: const Icon(Icons.history),
49 | ),
50 | ],
51 | ),
52 | body: LlmChatView(
53 | provider: _provider,
54 | suggestions: const [
55 | 'Tell me a joke.',
56 | 'Write me a limerick.',
57 | 'Perform a haiku.',
58 | ],
59 | ),
60 | );
61 |
62 | void _clearHistory() => _provider.history = [];
63 | }
64 |
--------------------------------------------------------------------------------
/example/lib/vertex/vertex.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2024 The Flutter Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | import 'package:firebase_core/firebase_core.dart';
6 | import 'package:firebase_vertexai/firebase_vertexai.dart';
7 | import 'package:flutter/material.dart';
8 | import 'package:flutter_ai_toolkit/flutter_ai_toolkit.dart';
9 |
10 | // from `flutterfire config`: https://firebase.google.com/docs/flutter/setup
11 | import '../firebase_options.dart';
12 |
13 | void main() async {
14 | WidgetsFlutterBinding.ensureInitialized();
15 | await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
16 | runApp(const App());
17 | }
18 |
19 | class App extends StatelessWidget {
20 | static const title = 'Example: Firebase Vertex AI';
21 |
22 | const App({super.key});
23 | @override
24 | Widget build(BuildContext context) => const MaterialApp(
25 | title: title,
26 | home: ChatPage(),
27 | );
28 | }
29 |
30 | class ChatPage extends StatelessWidget {
31 | const ChatPage({super.key});
32 |
33 | @override
34 | Widget build(BuildContext context) => Scaffold(
35 | appBar: AppBar(title: const Text(App.title)),
36 | body: LlmChatView(
37 | provider: VertexProvider(
38 | model: FirebaseVertexAI.instance.generativeModel(
39 | model: 'gemini-1.5-flash',
40 | ),
41 | ),
42 | ),
43 | );
44 | }
45 |
--------------------------------------------------------------------------------
/example/lib/welcome/welcome.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2024 The Flutter Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | import 'package:flutter/material.dart';
6 | import 'package:flutter_ai_toolkit/flutter_ai_toolkit.dart';
7 | import 'package:google_generative_ai/google_generative_ai.dart';
8 |
9 | import '../gemini_api_key.dart';
10 |
11 | void main() => runApp(const App());
12 |
13 | class App extends StatelessWidget {
14 | static const title = 'Example: Welcome Message';
15 |
16 | const App({super.key});
17 |
18 | @override
19 | Widget build(BuildContext context) => const MaterialApp(
20 | title: title,
21 | home: ChatPage(),
22 | );
23 | }
24 |
25 | class ChatPage extends StatelessWidget {
26 | const ChatPage({super.key});
27 |
28 | @override
29 | Widget build(BuildContext context) => Scaffold(
30 | appBar: AppBar(title: const Text(App.title)),
31 | body: LlmChatView(
32 | welcomeMessage: 'Hello and welcome to the Flutter AI Toolkit!',
33 | provider: GeminiProvider(
34 | model: GenerativeModel(
35 | model: 'gemini-1.5-flash',
36 | apiKey: geminiApiKey,
37 | ),
38 | ),
39 | ),
40 | );
41 | }
42 |
--------------------------------------------------------------------------------
/example/macos/.gitignore:
--------------------------------------------------------------------------------
1 | # Flutter-related
2 | **/Flutter/ephemeral/
3 | **/Pods/
4 |
5 | # Xcode-related
6 | **/dgph
7 | **/xcuserdata/
8 |
--------------------------------------------------------------------------------
/example/macos/Flutter/Flutter-Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
2 | #include "ephemeral/Flutter-Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/example/macos/Flutter/Flutter-Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
2 | #include "ephemeral/Flutter-Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/example/macos/Flutter/GeneratedPluginRegistrant.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Generated file. Do not edit.
3 | //
4 |
5 | import FlutterMacOS
6 | import Foundation
7 |
8 | import file_selector_macos
9 | import firebase_app_check
10 | import firebase_auth
11 | import firebase_core
12 | import path_provider_foundation
13 | import record_darwin
14 | import shared_preferences_foundation
15 | import url_launcher_macos
16 |
17 | func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
18 | FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
19 | FLTFirebaseAppCheckPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAppCheckPlugin"))
20 | FLTFirebaseAuthPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAuthPlugin"))
21 | FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin"))
22 | PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
23 | RecordPlugin.register(with: registry.registrar(forPlugin: "RecordPlugin"))
24 | SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
25 | UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
26 | }
27 |
--------------------------------------------------------------------------------
/example/macos/Podfile:
--------------------------------------------------------------------------------
1 | platform :osx, '10.15'
2 |
3 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
4 | ENV['COCOAPODS_DISABLE_STATS'] = 'true'
5 |
6 | project 'Runner', {
7 | 'Debug' => :debug,
8 | 'Profile' => :release,
9 | 'Release' => :release,
10 | }
11 |
12 | def flutter_root
13 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__)
14 | unless File.exist?(generated_xcode_build_settings_path)
15 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first"
16 | end
17 |
18 | File.foreach(generated_xcode_build_settings_path) do |line|
19 | matches = line.match(/FLUTTER_ROOT\=(.*)/)
20 | return matches[1].strip if matches
21 | end
22 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\""
23 | end
24 |
25 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
26 |
27 | flutter_macos_podfile_setup
28 |
29 | target 'Runner' do
30 | use_frameworks!
31 | use_modular_headers!
32 |
33 | flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__))
34 | target 'RunnerTests' do
35 | inherit! :search_paths
36 | end
37 | end
38 |
39 | post_install do |installer|
40 | installer.pods_project.targets.each do |target|
41 | flutter_additional_macos_build_settings(target)
42 | end
43 | end
44 |
--------------------------------------------------------------------------------
/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/example/macos/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/example/macos/Runner/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import Cocoa
2 | import FlutterMacOS
3 |
4 | @main
5 | class AppDelegate: FlutterAppDelegate {
6 | override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
7 | return true
8 | }
9 |
10 | override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool {
11 | return true
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "16x16",
5 | "idiom" : "mac",
6 | "filename" : "app_icon_16.png",
7 | "scale" : "1x"
8 | },
9 | {
10 | "size" : "16x16",
11 | "idiom" : "mac",
12 | "filename" : "app_icon_32.png",
13 | "scale" : "2x"
14 | },
15 | {
16 | "size" : "32x32",
17 | "idiom" : "mac",
18 | "filename" : "app_icon_32.png",
19 | "scale" : "1x"
20 | },
21 | {
22 | "size" : "32x32",
23 | "idiom" : "mac",
24 | "filename" : "app_icon_64.png",
25 | "scale" : "2x"
26 | },
27 | {
28 | "size" : "128x128",
29 | "idiom" : "mac",
30 | "filename" : "app_icon_128.png",
31 | "scale" : "1x"
32 | },
33 | {
34 | "size" : "128x128",
35 | "idiom" : "mac",
36 | "filename" : "app_icon_256.png",
37 | "scale" : "2x"
38 | },
39 | {
40 | "size" : "256x256",
41 | "idiom" : "mac",
42 | "filename" : "app_icon_256.png",
43 | "scale" : "1x"
44 | },
45 | {
46 | "size" : "256x256",
47 | "idiom" : "mac",
48 | "filename" : "app_icon_512.png",
49 | "scale" : "2x"
50 | },
51 | {
52 | "size" : "512x512",
53 | "idiom" : "mac",
54 | "filename" : "app_icon_512.png",
55 | "scale" : "1x"
56 | },
57 | {
58 | "size" : "512x512",
59 | "idiom" : "mac",
60 | "filename" : "app_icon_1024.png",
61 | "scale" : "2x"
62 | }
63 | ],
64 | "info" : {
65 | "version" : 1,
66 | "author" : "xcode"
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/csells/flutter_ai_toolkit/e4efee359ce5ba8c44f0a0bcd8936e9367af326a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png
--------------------------------------------------------------------------------
/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/csells/flutter_ai_toolkit/e4efee359ce5ba8c44f0a0bcd8936e9367af326a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png
--------------------------------------------------------------------------------
/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/csells/flutter_ai_toolkit/e4efee359ce5ba8c44f0a0bcd8936e9367af326a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png
--------------------------------------------------------------------------------
/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/csells/flutter_ai_toolkit/e4efee359ce5ba8c44f0a0bcd8936e9367af326a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png
--------------------------------------------------------------------------------
/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/csells/flutter_ai_toolkit/e4efee359ce5ba8c44f0a0bcd8936e9367af326a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png
--------------------------------------------------------------------------------
/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/csells/flutter_ai_toolkit/e4efee359ce5ba8c44f0a0bcd8936e9367af326a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png
--------------------------------------------------------------------------------
/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/csells/flutter_ai_toolkit/e4efee359ce5ba8c44f0a0bcd8936e9367af326a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png
--------------------------------------------------------------------------------
/example/macos/Runner/Configs/AppInfo.xcconfig:
--------------------------------------------------------------------------------
1 | // Application-level settings for the Runner target.
2 | //
3 | // This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the
4 | // future. If not, the values below would default to using the project name when this becomes a
5 | // 'flutter create' template.
6 |
7 | // The application's name. By default this is also the title of the Flutter window.
8 | PRODUCT_NAME = example
9 |
10 | // The application's bundle identifier
11 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example
12 |
13 | // The copyright displayed in application information
14 | PRODUCT_COPYRIGHT = Copyright © 2024 com.example. All rights reserved.
15 |
--------------------------------------------------------------------------------
/example/macos/Runner/Configs/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include "../../Flutter/Flutter-Debug.xcconfig"
2 | #include "Warnings.xcconfig"
3 |
--------------------------------------------------------------------------------
/example/macos/Runner/Configs/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include "../../Flutter/Flutter-Release.xcconfig"
2 | #include "Warnings.xcconfig"
3 |
--------------------------------------------------------------------------------
/example/macos/Runner/Configs/Warnings.xcconfig:
--------------------------------------------------------------------------------
1 | WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings
2 | GCC_WARN_UNDECLARED_SELECTOR = YES
3 | CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES
4 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE
5 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES
6 | CLANG_WARN_PRAGMA_PACK = YES
7 | CLANG_WARN_STRICT_PROTOTYPES = YES
8 | CLANG_WARN_COMMA = YES
9 | GCC_WARN_STRICT_SELECTOR_MATCH = YES
10 | CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES
11 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES
12 | GCC_WARN_SHADOW = YES
13 | CLANG_WARN_UNREACHABLE_CODE = YES
14 |
--------------------------------------------------------------------------------
/example/macos/Runner/DebugProfile.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.cs.allow-jit
8 |
9 | com.apple.security.network.server
10 |
11 | com.apple.security.network.client
12 |
13 | com.apple.security.files.user-selected.read-only
14 |
15 | com.apple.security.device.audio-input
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/example/macos/Runner/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIconFile
10 |
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | $(FLUTTER_BUILD_NAME)
21 | CFBundleVersion
22 | $(FLUTTER_BUILD_NUMBER)
23 | LSMinimumSystemVersion
24 | $(MACOSX_DEPLOYMENT_TARGET)
25 | NSHumanReadableCopyright
26 | $(PRODUCT_COPYRIGHT)
27 | NSMainNibFile
28 | MainMenu
29 | NSPrincipalClass
30 | NSApplication
31 | NSMicrophoneUsageDescription
32 | $(PRODUCT_NAME) would like to access your microphone.
33 |
34 |
35 |
--------------------------------------------------------------------------------
/example/macos/Runner/MainFlutterWindow.swift:
--------------------------------------------------------------------------------
1 | import Cocoa
2 | import FlutterMacOS
3 |
4 | class MainFlutterWindow: NSWindow {
5 | override func awakeFromNib() {
6 | let flutterViewController = FlutterViewController()
7 | let windowFrame = self.frame
8 | self.contentViewController = flutterViewController
9 | self.setFrame(windowFrame, display: true)
10 |
11 | RegisterGeneratedPlugins(registry: flutterViewController)
12 |
13 | super.awakeFromNib()
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/example/macos/Runner/Release.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.network.client
8 |
9 | com.apple.security.files.user-selected.read-only
10 |
11 | com.apple.security.device.audio-input
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/example/macos/RunnerTests/RunnerTests.swift:
--------------------------------------------------------------------------------
1 | import Cocoa
2 | import FlutterMacOS
3 | import XCTest
4 |
5 | class RunnerTests: XCTestCase {
6 |
7 | func testExample() {
8 | // If you add code to the Runner application, consider adding tests here.
9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest.
10 | }
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/example/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: flutter_ai_toolkit_example
2 | description: "Sample apps showing off various features of the Flutter AI Toolkit."
3 | publish_to: 'none'
4 | version: 0.6.5
5 |
6 | environment:
7 | sdk: ^3.4.4
8 |
9 | dependencies:
10 | flutter:
11 | sdk: flutter
12 | flutter_ai_toolkit:
13 | path: ..
14 | cupertino_icons: ^1.0.8
15 | google_generative_ai: ^0.4.3
16 | firebase_core: ^3.4.0
17 | firebase_vertexai: ^1.0.1
18 | shared_preferences: ^2.3.2
19 | url_launcher: ^6.3.0
20 | gap: ^3.0.1
21 | go_router: ^14.2.8
22 | uuid: ^4.5.1
23 | path: ^1.9.0
24 | path_provider: ^2.1.4
25 | flutter_markdown: ^0.7.4+1
26 | google_fonts: ^6.2.1
27 | future_builder_ex: ^4.0.0
28 | split_view: ^3.2.1
29 |
30 | dev_dependencies:
31 | flutter_test:
32 | sdk: flutter
33 | flutter_lints: ^5.0.0
34 |
35 | flutter:
36 | uses-material-design: true
37 | assets:
38 | - assets/recipes_default.json
39 | - assets/halloween-bg.png
40 |
--------------------------------------------------------------------------------
/example/web/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/csells/flutter_ai_toolkit/e4efee359ce5ba8c44f0a0bcd8936e9367af326a/example/web/favicon.png
--------------------------------------------------------------------------------
/example/web/icons/Icon-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/csells/flutter_ai_toolkit/e4efee359ce5ba8c44f0a0bcd8936e9367af326a/example/web/icons/Icon-192.png
--------------------------------------------------------------------------------
/example/web/icons/Icon-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/csells/flutter_ai_toolkit/e4efee359ce5ba8c44f0a0bcd8936e9367af326a/example/web/icons/Icon-512.png
--------------------------------------------------------------------------------
/example/web/icons/Icon-maskable-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/csells/flutter_ai_toolkit/e4efee359ce5ba8c44f0a0bcd8936e9367af326a/example/web/icons/Icon-maskable-192.png
--------------------------------------------------------------------------------
/example/web/icons/Icon-maskable-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/csells/flutter_ai_toolkit/e4efee359ce5ba8c44f0a0bcd8936e9367af326a/example/web/icons/Icon-maskable-512.png
--------------------------------------------------------------------------------
/example/web/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | example
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/example/web/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "example",
3 | "short_name": "example",
4 | "start_url": ".",
5 | "display": "standalone",
6 | "background_color": "#0175C2",
7 | "theme_color": "#0175C2",
8 | "description": "A new Flutter project.",
9 | "orientation": "portrait-primary",
10 | "prefer_related_applications": false,
11 | "icons": [
12 | {
13 | "src": "icons/Icon-192.png",
14 | "sizes": "192x192",
15 | "type": "image/png"
16 | },
17 | {
18 | "src": "icons/Icon-512.png",
19 | "sizes": "512x512",
20 | "type": "image/png"
21 | },
22 | {
23 | "src": "icons/Icon-maskable-192.png",
24 | "sizes": "192x192",
25 | "type": "image/png",
26 | "purpose": "maskable"
27 | },
28 | {
29 | "src": "icons/Icon-maskable-512.png",
30 | "sizes": "512x512",
31 | "type": "image/png",
32 | "purpose": "maskable"
33 | }
34 | ]
35 | }
36 |
--------------------------------------------------------------------------------
/font_svg/README.md:
--------------------------------------------------------------------------------
1 | `lib/fonts/FatIcons.ttf` is generated at [fluttericon.com](https://www.fluttericon.com/) from the following files:
2 | - `font_svg/spark-icon.svg`
3 | - `font_svg/submit-icon.svg`
4 |
5 | SVG sources:
6 | - `font_svg/submit-icon.svg` was generated by flipping the svg in `reply_24dp_5F6368_FILL0_wght400_GRAD0_opsz24.svg` horizontally
7 |
8 | - `font_svg/reply_24dp_5F6368_FILL0_wght400_GRAD0_opsz24.svg` was generated by exporting the SVG from [the SVG of Google Symbols "reply" character](https://fonts.google.com/icons?icon.query=reply)
9 |
10 | - `font_svg/spark-icon.svg` was purchased at [the Noun Project](https://thenounproject.com/icon/spark-6645136/)
11 |
12 | Material icons:
13 | The following icons will pulled in from Material and packaged into the FatIcons.ttf so that Material itself wasn't required for a Cupertino app:
14 | - Icons.add
15 | - Icons.attach_file
16 | - Icons.stop
17 | - Icons.mic
18 | - Icons.close
19 | - Icons.camera_alt
20 | - Icons.image
21 | - Icons.edit
22 | - Icons.copy
23 |
24 | more info:
25 | https://stackoverflow.com/a/75657218
26 |
--------------------------------------------------------------------------------
/font_svg/reply_24dp_5F6368_FILL0_wght400_GRAD0_opsz24.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/font_svg/spark-icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
5 |
--------------------------------------------------------------------------------
/font_svg/submit-icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/lib/flutter_ai_toolkit.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2024 The Flutter Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | /// A library for integrating AI-powered chat functionality into Flutter
6 | /// applications.
7 | ///
8 | /// This library provides a set of tools and widgets to easily incorporate AI
9 | /// language models into your Flutter app, enabling interactive chat experiences
10 | /// with various AI providers.
11 | ///
12 | /// Key components:
13 | /// - LLM providers: Interfaces and implementations for different AI services.
14 | /// - Chat UI: Ready-to-use widgets for displaying chat interfaces.
15 | library;
16 |
17 | export 'src/providers/interface/chat_message.dart';
18 | export 'src/providers/providers.dart';
19 | export 'src/styles/styles.dart';
20 | export 'src/views/llm_chat_view/llm_chat_view.dart';
21 |
--------------------------------------------------------------------------------
/lib/fonts/FatIcons.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/csells/flutter_ai_toolkit/e4efee359ce5ba8c44f0a0bcd8936e9367af326a/lib/fonts/FatIcons.ttf
--------------------------------------------------------------------------------
/lib/src/chat_view_model/chat_view_model.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2024 The Flutter Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | import 'package:flutter/foundation.dart';
6 |
7 | import '../providers/interface/llm_provider.dart';
8 | import '../styles/llm_chat_view_style.dart';
9 | import '../views/response_builder.dart';
10 |
11 | @immutable
12 |
13 | /// A view model class for managing chat interactions and configurations.
14 | ///
15 | /// This class encapsulates the core data and functionality needed for the chat
16 | /// interface, including the LLM provider, style configuration, welcome message,
17 | /// response builder, and message sender.
18 | class ChatViewModel {
19 | /// Creates a new [ChatViewModel] instance.
20 | ///
21 | /// [provider] is the required [LlmProvider] for handling LLM interactions.
22 | /// [style] is the optional [LlmChatViewStyle] for customizing the chat view's
23 | /// appearance. [welcomeMessage] is an optional message displayed when the
24 | /// chat interface is first opened. [responseBuilder] is an optional builder
25 | /// for customizing chat responses. [messageSender] is an optional
26 | /// [LlmStreamGenerator] for sending messages.
27 | const ChatViewModel({
28 | required this.provider,
29 | required this.style,
30 | required this.welcomeMessage,
31 | required this.responseBuilder,
32 | required this.messageSender,
33 | });
34 |
35 | /// The LLM provider for the chat interface.
36 | ///
37 | /// This provider is responsible for managing interactions with the language
38 | /// model, including sending and receiving messages.
39 | final LlmProvider provider;
40 |
41 | /// The style configuration for the chat view.
42 | ///
43 | /// Defines visual properties like colors, decorations, and layout parameters
44 | /// for the chat interface. If null, default styling will be applied.
45 | final LlmChatViewStyle? style;
46 |
47 | /// The welcome message to display in the chat interface.
48 | ///
49 | /// This message is shown to users when they first open the chat interface,
50 | /// providing a friendly introduction or prompt.
51 | final String? welcomeMessage;
52 |
53 | /// The builder for the chat response.
54 | ///
55 | /// This builder allows for customization of how chat responses are rendered
56 | /// in the interface, enabling tailored presentation of messages.
57 | final ResponseBuilder? responseBuilder;
58 |
59 | /// The message sender for the chat interface.
60 | ///
61 | /// This optional generator is used to send messages to the LLM, allowing for
62 | /// asynchronous communication and response handling.
63 | final LlmStreamGenerator? messageSender;
64 |
65 | // The following is needed to support the
66 | // ChatViewModelProvider.updateShouldNotify implementation
67 | @override
68 | bool operator ==(Object other) =>
69 | identical(this, other) ||
70 | (other is ChatViewModel &&
71 | other.provider == provider &&
72 | other.style == style &&
73 | other.welcomeMessage == welcomeMessage &&
74 | other.responseBuilder == responseBuilder &&
75 | other.messageSender == messageSender);
76 |
77 | // the following is best practices when overriding operator ==
78 | @override
79 | int get hashCode => Object.hash(
80 | provider,
81 | style,
82 | welcomeMessage,
83 | responseBuilder,
84 | messageSender,
85 | );
86 | }
87 |
--------------------------------------------------------------------------------
/lib/src/chat_view_model/chat_view_model_client.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2024 The Flutter Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | import 'package:flutter/widgets.dart';
6 |
7 | import 'chat_view_model.dart';
8 | import 'chat_view_model_provider.dart';
9 |
10 | /// A widget that provides access to a [ChatViewModel] and builds its child
11 | /// using a builder function.
12 | ///
13 | /// This widget is typically used in conjunction with [ChatViewModelProvider]
14 | /// to access the [ChatViewModel] from the widget tree.
15 | @immutable
16 | class ChatViewModelClient extends StatelessWidget {
17 | /// Creates a [ChatViewModelClient].
18 | ///
19 | /// The [builder] argument must not be null.
20 | const ChatViewModelClient({
21 | required this.builder,
22 | this.child,
23 | super.key,
24 | });
25 |
26 | /// A function that builds a widget tree based on the current [ChatViewModel].
27 | ///
28 | /// This function is called with the current [BuildContext], the
29 | /// [ChatViewModel] obtained from the nearest [ChatViewModelProvider]
30 | /// ancestor, and the optional [child].
31 | final Widget Function(
32 | BuildContext context, ChatViewModel viewModel, Widget? child) builder;
33 |
34 | /// An optional child widget that can be passed to the [builder] function.
35 | ///
36 | /// This is useful when part of the widget subtree does not depend on the
37 | /// [ChatViewModel] and can be shared across multiple builds.
38 | final Widget? child;
39 |
40 | @override
41 | Widget build(BuildContext context) =>
42 | builder(context, ChatViewModelProvider.of(context), child);
43 | }
44 |
--------------------------------------------------------------------------------
/lib/src/chat_view_model/chat_view_model_provider.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2024 The Flutter Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | import 'package:flutter/widgets.dart';
6 |
7 | import 'chat_view_model.dart';
8 |
9 | /// A provider widget that makes a [ChatViewModel] available to its descendants.
10 | ///
11 | /// This widget uses the [InheritedWidget] mechanism to efficiently propagate
12 | /// the [ChatViewModel] down the widget tree.
13 | @immutable
14 | class ChatViewModelProvider extends InheritedWidget {
15 | /// Creates a [ChatViewModelProvider].
16 | ///
17 | /// The [child] and [viewModel] arguments must not be null.
18 | const ChatViewModelProvider({
19 | required super.child,
20 | required this.viewModel,
21 | super.key,
22 | });
23 |
24 | /// The [ChatViewModel] to be made available to descendants.
25 | final ChatViewModel viewModel;
26 |
27 | /// Retrieves the [ChatViewModel] from the closest [ChatViewModelProvider]
28 | /// ancestor in the widget tree.
29 | ///
30 | /// This method will assert if no [ChatViewModelProvider] is found in the
31 | /// widget's ancestors.
32 | ///
33 | /// [context] must not be null.
34 | static ChatViewModel of(BuildContext context) {
35 | final viewModel = maybeOf(context);
36 | assert(viewModel != null, 'No ChatViewModelProvider found in context');
37 | return viewModel!;
38 | }
39 |
40 | /// Retrieves the [ChatViewModel] from the closest [ChatViewModelProvider]
41 | /// ancestor in the widget tree, if one exists.
42 | ///
43 | /// Returns null if no [ChatViewModelProvider] is found.
44 | ///
45 | /// [context] must not be null.
46 | static ChatViewModel? maybeOf(BuildContext context) => context
47 | .dependOnInheritedWidgetOfExactType()
48 | ?.viewModel;
49 |
50 | @override
51 | bool updateShouldNotify(ChatViewModelProvider oldWidget) =>
52 | viewModel != oldWidget.viewModel;
53 | }
54 |
--------------------------------------------------------------------------------
/lib/src/dialogs/adaptive_dialog.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2024 The Flutter Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | import 'package:flutter/cupertino.dart'
6 | show CupertinoAlertDialog, showCupertinoDialog;
7 | import 'package:flutter/material.dart' show AlertDialog, showDialog;
8 | import 'package:flutter/widgets.dart';
9 |
10 | import '../utility.dart';
11 |
12 | /// A utility class for showing adaptive dialogs that match the current platform
13 | /// style.
14 | @immutable
15 | class AdaptiveAlertDialog {
16 | /// Shows an adaptive dialog with the given [content] widget as content.
17 | ///
18 | /// This method automatically chooses between a Cupertino-style dialog for iOS
19 | /// and a Material-style dialog for other platforms.
20 | ///
21 | /// Parameters:
22 | /// * [context]: The build context in which to show the dialog.
23 | /// * [child]: The widget to display as the dialog's content.
24 | ///
25 | /// Returns a [Future] that completes with the result value when the dialog is
26 | /// dismissed.
27 | static Future show({
28 | required BuildContext context,
29 | required Widget content,
30 | bool barrierDismissible = false,
31 | List actions = const [],
32 | }) =>
33 | isCupertinoApp(context)
34 | ? showCupertinoDialog(
35 | context: context,
36 | barrierDismissible: barrierDismissible,
37 | builder: (context) => CupertinoAlertDialog(
38 | content: content,
39 | actions: actions,
40 | ),
41 | )
42 | : showDialog(
43 | context: context,
44 | barrierDismissible: barrierDismissible,
45 | builder: (context) => AlertDialog(
46 | content: content,
47 | actions: actions,
48 | ),
49 | );
50 | }
51 |
--------------------------------------------------------------------------------
/lib/src/dialogs/adaptive_dialog_action.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2024 The Flutter Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | import 'package:flutter/cupertino.dart' show CupertinoDialogAction;
6 | import 'package:flutter/material.dart' show TextButton;
7 | import 'package:flutter/widgets.dart';
8 |
9 | import '../utility.dart';
10 |
11 | /// A button that adapts its appearance based on the design language, either
12 | /// Material or Cupertino.
13 | ///
14 | /// The [AdaptiveDialogAction] widget is designed to provide a consistent user
15 | /// experience across different platforms while adhering to platform-specific
16 | /// design guidelines.
17 | @immutable
18 | class AdaptiveDialogAction extends StatelessWidget {
19 | /// Creates an adaptive dialog action.
20 | ///
21 | /// The [onPressed] and [child] arguments must not be null.
22 | const AdaptiveDialogAction({
23 | required this.onPressed,
24 | required this.child,
25 | super.key,
26 | });
27 |
28 | /// The callback that is called when the button is tapped or pressed.
29 | final VoidCallback onPressed;
30 |
31 | /// The widget below this widget in the tree.
32 | ///
33 | /// Typically a [Text] widget.
34 | final Widget child;
35 |
36 | @override
37 | Widget build(BuildContext context) => isCupertinoApp(context)
38 | ? CupertinoDialogAction(
39 | onPressed: onPressed,
40 | child: child,
41 | )
42 | : TextButton(onPressed: onPressed, child: child);
43 | }
44 |
--------------------------------------------------------------------------------
/lib/src/dialogs/adaptive_snack_bar/adaptive_snack_bar.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2024 The Flutter Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | import 'package:flutter/material.dart' show ScaffoldMessenger, SnackBar;
6 | import 'package:flutter/widgets.dart';
7 |
8 | import '../../utility.dart';
9 | import 'cupertino_snack_bar.dart';
10 |
11 | /// A utility class for showing adaptive snack bars in Flutter applications.
12 | ///
13 | /// This class provides a static method to display snack bars that adapt to the
14 | /// current application environment, showing either a Material Design snack bar
15 | /// or a Cupertino-style snack bar based on the app's context.
16 | @immutable
17 | class AdaptiveSnackBar {
18 | /// Shows an adaptive snack bar with the given message.
19 | ///
20 | /// This method determines whether the app is using Cupertino or Material
21 | /// design and displays an appropriate snack bar.
22 | ///
23 | /// Parameters:
24 | /// * [context]: The build context in which to show the snack bar.
25 | /// * [message]: The text message to display in the snack bar.
26 | static void show(BuildContext context, String message) {
27 | if (isCupertinoApp(context)) {
28 | _showCupertinoSnackBar(context: context, message: message);
29 | } else {
30 | ScaffoldMessenger.of(context)
31 | .showSnackBar(SnackBar(content: Text(message)));
32 | }
33 | }
34 |
35 | static void _showCupertinoSnackBar({
36 | required BuildContext context,
37 | required String message,
38 | int durationMillis = 4000,
39 | }) {
40 | const animationDurationMillis = 200;
41 | final overlayEntry = OverlayEntry(
42 | builder: (context) => CupertinoSnackBar(
43 | message: message,
44 | animationDurationMillis: animationDurationMillis,
45 | waitDurationMillis: durationMillis,
46 | ),
47 | );
48 | Future.delayed(
49 | Duration(milliseconds: durationMillis + 2 * animationDurationMillis),
50 | overlayEntry.remove,
51 | );
52 | Overlay.of(context).insert(overlayEntry);
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/lib/src/dialogs/adaptive_snack_bar/cupertino_snack_bar.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2024 The Flutter Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | import 'package:flutter/cupertino.dart';
6 |
7 | /// A widget that displays a Cupertino-style snack bar.
8 | ///
9 | /// This widget creates an animated snack bar that slides up from the bottom of
10 | /// the screen, displays a message for a specified duration, and then slides
11 | /// back down.
12 | ///
13 | /// The snack bar uses Cupertino styling to match iOS design guidelines.
14 | @immutable
15 | class CupertinoSnackBar extends StatefulWidget {
16 | /// Creates a [CupertinoSnackBar].
17 | ///
18 | /// All parameters are required:
19 | /// * [message] is the text to display in the snack bar.
20 | /// * [animationDurationMillis] defines how long the slide animations take.
21 | /// * [waitDurationMillis] sets how long the snack bar stays visible before
22 | /// dismissing.
23 | const CupertinoSnackBar({
24 | required this.message,
25 | required this.animationDurationMillis,
26 | required this.waitDurationMillis,
27 | super.key,
28 | });
29 |
30 | /// The message to display in the snack bar.
31 | final String message;
32 |
33 | /// The duration of the slide-in and slide-out animations in milliseconds.
34 | final int animationDurationMillis;
35 |
36 | /// The duration for which the snack bar remains visible in milliseconds.
37 | final int waitDurationMillis;
38 |
39 | @override
40 | State createState() => _CupertinoSnackBarState();
41 | }
42 |
43 | class _CupertinoSnackBarState extends State {
44 | bool show = false;
45 |
46 | @override
47 | void initState() {
48 | super.initState();
49 | Future.microtask(() => setState(() => show = true));
50 | Future.delayed(
51 | Duration(
52 | milliseconds: widget.waitDurationMillis,
53 | ),
54 | () {
55 | if (mounted) {
56 | setState(() => show = false);
57 | }
58 | },
59 | );
60 | }
61 |
62 | @override
63 | Widget build(BuildContext context) => AnimatedPositioned(
64 | bottom: show ? 8.0 : -50.0,
65 | left: 8,
66 | right: 8,
67 | curve: show ? Curves.linearToEaseOut : Curves.easeInToLinear,
68 | duration: Duration(milliseconds: widget.animationDurationMillis),
69 | child: CupertinoPopupSurface(
70 | child: Padding(
71 | padding: const EdgeInsets.symmetric(
72 | horizontal: 8,
73 | vertical: 8,
74 | ),
75 | child: Text(
76 | widget.message,
77 | style: const TextStyle(
78 | fontSize: 14,
79 | color: CupertinoColors.secondaryLabel,
80 | ),
81 | textAlign: TextAlign.center,
82 | ),
83 | ),
84 | ),
85 | );
86 | }
87 |
--------------------------------------------------------------------------------
/lib/src/dialogs/image_preview_dialog.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2024 The Flutter Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | import 'package:flutter/widgets.dart';
6 |
7 | import '../providers/interface/attachments.dart';
8 |
9 | /// Displays a dialog to preview the image when the user taps on an attached
10 | /// image.
11 | @immutable
12 | class ImagePreviewDialog extends StatelessWidget {
13 | /// Shows the [ImagePreviewDialog] for the given [attachment].
14 | const ImagePreviewDialog(this.attachment, {super.key});
15 |
16 | /// The image file attachment to be previewed in the dialog.
17 | final ImageFileAttachment attachment;
18 |
19 | @override
20 | Widget build(BuildContext context) => Padding(
21 | padding: const EdgeInsets.all(8),
22 | child: Center(
23 | child: Image.memory(attachment.bytes, fit: BoxFit.contain),
24 | ),
25 | );
26 | }
27 |
--------------------------------------------------------------------------------
/lib/src/llm_exception.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2024 The Flutter Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | import 'package:flutter/foundation.dart';
6 |
7 | /// Exception class for LLM-related errors.
8 | ///
9 | /// This class is used to represent exceptions that occur during
10 | /// LLM (Language Learning Model) operations.
11 | @immutable
12 | abstract class LlmException implements Exception {
13 | /// Creates a new [LlmException] with the given error [message].
14 | ///
15 | /// The [message] parameter is a string describing the error that occurred.
16 | const LlmException._([this.message = '']);
17 |
18 | /// The message describing the error that occurred.
19 | final String message;
20 |
21 | @override
22 | String toString() => 'LlmException: $message';
23 | }
24 |
25 | /// Exception thrown when an LLM operation is cancelled.
26 | ///
27 | /// This exception is used to indicate that an LLM operation was
28 | /// intentionally cancelled, typically by user action or a timeout.
29 | @immutable
30 | class LlmCancelException extends LlmException {
31 | /// Creates a new [LlmCancelException].
32 | const LlmCancelException() : super._();
33 |
34 | @override
35 | String toString() => 'LlmCancelException';
36 | }
37 |
38 | /// Exception thrown when an LLM operation fails.
39 | ///
40 | /// This exception is used to represent failures in LLM operations
41 | /// that are not due to cancellation, such as network errors or
42 | /// invalid responses from the LLM provider.
43 | @immutable
44 | class LlmFailureException extends LlmException {
45 | /// Creates a new [LlmFailureException] with the given error [message].
46 | ///
47 | /// The [message] parameter is a string describing the failure that occurred.
48 | const LlmFailureException([super.message]) : super._();
49 |
50 | @override
51 | String toString() => 'LlmFailureException: $message';
52 | }
53 |
--------------------------------------------------------------------------------
/lib/src/platform_helper/platform_helper.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2024 The Flutter Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | export 'platform_helper_web.dart'
6 | if (dart.library.io) 'platform_helper_io.dart';
7 |
--------------------------------------------------------------------------------
/lib/src/platform_helper/platform_helper_io.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2024 The Flutter Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | import 'dart:io';
6 |
7 | import 'package:flutter/widgets.dart';
8 | import 'package:image_picker/image_picker.dart';
9 |
10 | /// Deletes a file from the file system.
11 | ///
12 | /// This method takes an [XFile] object and deletes the corresponding file
13 | /// from the file system. It uses the [File] class from dart:io to perform
14 | /// the deletion.
15 | ///
16 | /// Parameters:
17 | /// - file: An [XFile] object representing the file to be deleted.
18 | ///
19 | /// Returns:
20 | /// A [Future] that completes when the file has been deleted.
21 | ///
22 | /// Throws:
23 | /// - [FileSystemException] if the file cannot be deleted.
24 | Future deleteFile(XFile file) async {
25 | await File(file.path).delete();
26 | }
27 |
28 | /// Checks if the device can take a photo.
29 | ///
30 | /// This method returns `true` if the device supports taking photos using
31 | /// the camera, and `false` otherwise. It uses the [ImagePicker] class
32 | /// to check for camera support.
33 | ///
34 | /// Returns:
35 | /// A [bool] indicating whether the device can take a photo.
36 | bool canTakePhoto() => ImagePicker().supportsImageSource(ImageSource.camera);
37 |
38 | /// Opens a dialog to take a photo using the device's camera.
39 | ///
40 | /// This method displays a camera interface to the user, allowing them to
41 | /// capture a photo. The captured photo is returned as an [XFile] object.
42 | ///
43 | /// Parameters:
44 | /// - context: The build context in which to show the camera dialog.
45 | ///
46 | /// Returns:
47 | /// A [Future] that completes with an [XFile] object representing the
48 | /// captured photo, or `null` if the photo capture was canceled.
49 | Future takePhoto(BuildContext context) =>
50 | ImagePicker().pickImage(source: ImageSource.camera);
51 |
--------------------------------------------------------------------------------
/lib/src/platform_helper/platform_helper_web.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2024 The Flutter Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | import 'package:cross_file/cross_file.dart';
6 | import 'package:flutter/widgets.dart';
7 | import 'package:flutter_picture_taker/flutter_picture_taker.dart';
8 |
9 | /// Deletes a file from the file system.
10 | ///
11 | /// This method is a no-op on web platforms, as web browsers do not have
12 | /// direct access to the file system. The method is provided for API
13 | /// compatibility with non-web platforms.
14 | ///
15 | /// Parameters:
16 | /// - file: An [XFile] object representing the file to be deleted.
17 | /// This parameter is ignored on web platforms.
18 | ///
19 | /// Returns:
20 | /// A [Future] that completes immediately, as no actual deletion occurs.
21 | Future deleteFile(XFile file) async {}
22 |
23 | /// Checks if the device can take a photo.
24 | ///
25 | /// This method always returns `true` on web platforms, as the capability
26 | /// to take a photo is assumed to be available via the flutter_picture_taker
27 | /// package.
28 | ///
29 | /// Returns:
30 | /// A [bool] indicating whether the device can take a photo.
31 | bool canTakePhoto() => true;
32 |
33 | /// Opens a dialog to take a photo using the device's camera.
34 | ///
35 | /// This method displays a camera interface to the user, allowing them to
36 | /// capture a photo. The captured photo is returned as an [XFile] object.
37 | ///
38 | /// Parameters:
39 | /// - context: The build context in which to show the camera dialog.
40 | ///
41 | /// Returns:
42 | /// A [Future] that completes with an [XFile] object representing the
43 | /// captured photo, or `null` if the photo capture was canceled.
44 | Future takePhoto(BuildContext context) =>
45 | showStillCameraDialog(context);
46 |
--------------------------------------------------------------------------------
/lib/src/providers/implementations/echo_provider.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2024 The Flutter Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | import 'package:flutter/foundation.dart';
6 |
7 | import '../../llm_exception.dart';
8 | import '../interface/attachments.dart';
9 | import '../interface/chat_message.dart';
10 | import '../interface/llm_provider.dart';
11 |
12 | /// A simple LLM provider that echoes the input prompt and attachment
13 | /// information.
14 | ///
15 | /// This provider is primarily used for testing and debugging purposes.
16 | class EchoProvider extends LlmProvider with ChangeNotifier {
17 | /// Creates an [EchoProvider] instance with an optional chat history.
18 | ///
19 | /// The [history] parameter is an optional iterable of [ChatMessage] objects
20 | /// representing the chat history. If provided, it will be converted to a list
21 | /// and stored internally. If not provided, an empty list will be used.
22 | EchoProvider({Iterable? history})
23 | : _history = List.from(history ?? []);
24 |
25 | final List _history;
26 |
27 | @override
28 | Stream generateStream(
29 | String prompt, {
30 | Iterable attachments = const [],
31 | }) async* {
32 | if (prompt == 'FAILFAST') throw const LlmFailureException('Failing fast!');
33 |
34 | await Future.delayed(const Duration(milliseconds: 1000));
35 | yield '# Echo\n';
36 |
37 | switch (prompt) {
38 | case 'CANCEL':
39 | throw const LlmCancelException();
40 | case 'FAIL':
41 | throw const LlmFailureException('User requested failure');
42 | }
43 |
44 | await Future.delayed(const Duration(milliseconds: 1000));
45 | yield prompt;
46 |
47 | yield '\n\n# Attachments\n${attachments.map((a) => a.toString())}';
48 | }
49 |
50 | @override
51 | Stream sendMessageStream(
52 | String prompt, {
53 | Iterable attachments = const [],
54 | }) async* {
55 | final userMessage = ChatMessage.user(prompt, attachments);
56 | final llmMessage = ChatMessage.llm();
57 | _history.addAll([userMessage, llmMessage]);
58 | final response = generateStream(prompt, attachments: attachments);
59 |
60 | // don't write this code if you're targeting the web until this is fixed:
61 | // https://github.com/dart-lang/sdk/issues/47764
62 | // await for (final chunk in chunks) {
63 | // llmMessage.append(chunk);
64 | // yield chunk;
65 | // }
66 | yield* response.map((chunk) {
67 | llmMessage.append(chunk);
68 | return chunk;
69 | });
70 |
71 | notifyListeners();
72 | }
73 |
74 | @override
75 | Iterable get history => _history;
76 |
77 | @override
78 | set history(Iterable history) {
79 | _history.clear();
80 | _history.addAll(history);
81 | notifyListeners();
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/lib/src/providers/interface/llm_provider.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2024 The Flutter Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | import 'package:flutter/foundation.dart';
6 |
7 | import 'attachments.dart';
8 | import 'chat_message.dart';
9 |
10 | /// An abstract class representing a Language Model (LLM) provider.
11 | ///
12 | /// This class defines the interface for interacting with different LLM
13 | /// services. Implementations of this class should provide the logic for
14 | /// generating text responses based on input prompts and optional attachments.
15 | abstract class LlmProvider implements Listenable {
16 | /// Generates a stream of text based on the given prompt and attachments.
17 | /// This method does not interact with a chat or build on any chat history.
18 | ///
19 | /// [prompt] is the input text to generate a response for.
20 | /// [attachments] is an optional iterable of [Attachment] objects to include
21 | /// with the prompt. These can be images, files, or links that provide
22 | /// additional context for the LLM.
23 | ///
24 | /// Returns a [Stream] of [String] containing the generated text chunks. This
25 | /// allows for streaming responses as they are generated by the LLM.
26 | Stream generateStream(
27 | String prompt, {
28 | Iterable attachments,
29 | });
30 |
31 | /// Generates a stream of text based on the given prompt and attachments.
32 | /// Interacts with a chat and builds on the history of the chat associated
33 | /// with the provider.
34 | ///
35 | /// This method should be implemented to interact with the specific LLM
36 | /// service and generate text responses.
37 | ///
38 | /// [prompt] is the input text to generate a response for. [attachments] is an
39 | /// optional iterable of [Attachment] objects to include with the prompt.
40 | /// These can be images, files, or links that provide additional context for
41 | /// the LLM.
42 | ///
43 | /// Returns a [Stream] of [String] containing the generated text chunks. This
44 | /// allows for streaming responses as they are generated by the LLM.
45 | Stream sendMessageStream(
46 | String prompt, {
47 | Iterable attachments,
48 | });
49 |
50 | /// Returns an iterable of [ChatMessage] objects representing the chat
51 | /// history.
52 | ///
53 | /// This getter provides access to the conversation history maintained by the
54 | /// LLM provider. The history typically includes both user messages and LLM
55 | /// responses in chronological order.
56 | ///
57 | /// Returns an [Iterable] of [ChatMessage] objects.
58 | Iterable get history;
59 |
60 | /// Sets the chat history to the provided messages.
61 | ///
62 | /// This setter allows updating the conversation history maintained by the LLM
63 | /// provider. The provided [history] replaces the existing history with a new
64 | /// set of messages.
65 | ///
66 | /// [history] is an [Iterable] of [ChatMessage] objects representing the new
67 | /// chat history.
68 | set history(Iterable history);
69 | }
70 |
71 | /// A function that generates a stream of text based on a prompt and
72 | /// attachments.
73 | typedef LlmStreamGenerator = Stream Function(
74 | String prompt, {
75 | required Iterable attachments,
76 | });
77 |
--------------------------------------------------------------------------------
/lib/src/providers/interface/message_origin.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2024 The Flutter Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | /// Represents the origin of a chat message.
6 | enum MessageOrigin {
7 | /// Indicates that the message originated from the user.
8 | user,
9 |
10 | /// Indicates that the message originated from the LLM.
11 | llm;
12 |
13 | /// Checks if the message origin is from the user.
14 | bool get isUser => this == MessageOrigin.user;
15 |
16 | /// Checks if the message origin is from the LLM.
17 | bool get isLlm => this == MessageOrigin.llm;
18 | }
19 |
--------------------------------------------------------------------------------
/lib/src/providers/providers.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2024 The Flutter Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | export 'implementations/echo_provider.dart';
6 | export 'implementations/gemini_provider.dart';
7 | export 'implementations/vertex_provider.dart';
8 | export 'interface/attachments.dart';
9 | export 'interface/chat_message.dart';
10 | export 'interface/llm_provider.dart';
11 | export 'interface/message_origin.dart';
12 |
--------------------------------------------------------------------------------
/lib/src/styles/action_button_type.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2024 The Flutter Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | /// Enum representing different types of action buttons in the chat view.
6 | enum ActionButtonType {
7 | /// Button to add content or initiate a new action.
8 | add,
9 |
10 | /// Button to attach a file to the chat.
11 | attachFile,
12 |
13 | /// Button to access the camera for taking photos or videos.
14 | camera,
15 |
16 | /// Button to cancel an ongoing action or input.
17 | stop,
18 |
19 | /// Button to close the current view or dialog.
20 | close,
21 |
22 | /// Button to close an open menu.
23 | closeMenu,
24 |
25 | /// Button to cancel an operation.
26 | cancel,
27 |
28 | /// Button to copy selected text or content.
29 | copy,
30 |
31 | /// Button to edit existing content or settings.
32 | edit,
33 |
34 | /// Button to access the device's photo gallery.
35 | gallery,
36 |
37 | /// Button to start or stop audio recording.
38 | record,
39 |
40 | /// Button to submit the current input or action.
41 | submit,
42 | }
43 |
--------------------------------------------------------------------------------
/lib/src/styles/chat_input_style.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2024 The Flutter Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | import 'package:flutter/widgets.dart';
6 |
7 | import 'toolkit_colors.dart';
8 | import 'toolkit_text_styles.dart';
9 |
10 | /// Style for the input text box.
11 | @immutable
12 | class ChatInputStyle {
13 | /// Creates an InputBoxStyle.
14 | const ChatInputStyle({
15 | this.textStyle,
16 | this.hintStyle,
17 | this.hintText,
18 | this.backgroundColor,
19 | this.decoration,
20 | });
21 |
22 | /// Merges the provided styles with the default styles.
23 | factory ChatInputStyle.resolve(
24 | ChatInputStyle? style, {
25 | ChatInputStyle? defaultStyle,
26 | }) {
27 | defaultStyle ??= ChatInputStyle.defaultStyle();
28 | return ChatInputStyle(
29 | textStyle: style?.textStyle ?? defaultStyle.textStyle,
30 | hintStyle: style?.hintStyle ?? defaultStyle.hintStyle,
31 | hintText: style?.hintText ?? defaultStyle.hintText,
32 | backgroundColor: style?.backgroundColor ?? defaultStyle.backgroundColor,
33 | decoration: style?.decoration ?? defaultStyle.decoration,
34 | );
35 | }
36 |
37 | /// Provides a default style.
38 | factory ChatInputStyle.defaultStyle() => ChatInputStyle._lightStyle();
39 |
40 | /// Provides a default light style.
41 | factory ChatInputStyle._lightStyle() => ChatInputStyle(
42 | textStyle: ToolkitTextStyles.body2,
43 | hintStyle:
44 | ToolkitTextStyles.body2.copyWith(color: ToolkitColors.hintText),
45 | hintText: 'Ask me anything...',
46 | backgroundColor: ToolkitColors.containerBackground,
47 | decoration: BoxDecoration(
48 | color: ToolkitColors.containerBackground,
49 | border: Border.all(width: 1, color: ToolkitColors.outline),
50 | borderRadius: BorderRadius.circular(24),
51 | ),
52 | );
53 |
54 | /// The text style for the input text box.
55 | final TextStyle? textStyle;
56 |
57 | /// The hint text style for the input text box.
58 | final TextStyle? hintStyle;
59 |
60 | /// The hint text for the input text box.
61 | final String? hintText;
62 |
63 | /// The background color of the input box.
64 | final Color? backgroundColor;
65 |
66 | /// The decoration of the input box.
67 | final Decoration? decoration;
68 | }
69 |
--------------------------------------------------------------------------------
/lib/src/styles/file_attachment_style.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2024 The Flutter Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | import 'package:flutter/widgets.dart';
6 |
7 | import 'tookit_icons.dart';
8 | import 'toolkit_colors.dart';
9 | import 'toolkit_text_styles.dart';
10 |
11 | /// Style for file attachments in the chat view.
12 | @immutable
13 | class FileAttachmentStyle {
14 | /// Creates a FileAttachmentStyle.
15 | const FileAttachmentStyle({
16 | this.decoration,
17 | this.icon,
18 | this.iconColor,
19 | this.iconDecoration,
20 | this.filenameStyle,
21 | this.filetypeStyle,
22 | });
23 |
24 | /// Resolves the FileAttachmentStyle by combining the provided style with
25 | /// default values.
26 | ///
27 | /// This method takes an optional [style] and merges it with the
28 | /// [defaultStyle]. If [defaultStyle] is not provided, it uses
29 | /// [FileAttachmentStyle.defaultStyle].
30 | ///
31 | /// [style] - The custom FileAttachmentStyle to apply. Can be null.
32 | /// [defaultStyle] - The default FileAttachmentStyle to use as a base. If
33 | /// null, uses [FileAttachmentStyle.defaultStyle].
34 | ///
35 | /// Returns a new [FileAttachmentStyle] instance with resolved properties.
36 | factory FileAttachmentStyle.resolve(
37 | FileAttachmentStyle? style, {
38 | FileAttachmentStyle? defaultStyle,
39 | }) {
40 | defaultStyle ??= FileAttachmentStyle.defaultStyle();
41 | return FileAttachmentStyle(
42 | decoration: style?.decoration ?? defaultStyle.decoration,
43 | icon: style?.icon ?? defaultStyle.icon,
44 | iconColor: style?.iconColor ?? defaultStyle.iconColor,
45 | iconDecoration: style?.iconDecoration ?? defaultStyle.iconDecoration,
46 | filenameStyle: style?.filenameStyle ?? defaultStyle.filenameStyle,
47 | filetypeStyle: style?.filetypeStyle ?? defaultStyle.filetypeStyle,
48 | );
49 | }
50 |
51 | /// Provides a default style.
52 | factory FileAttachmentStyle.defaultStyle() =>
53 | FileAttachmentStyle._lightStyle();
54 |
55 | /// Provides a default light style.
56 | factory FileAttachmentStyle._lightStyle() => FileAttachmentStyle(
57 | decoration: ShapeDecoration(
58 | color: ToolkitColors.fileContainerBackground,
59 | shape: RoundedRectangleBorder(
60 | borderRadius: BorderRadius.circular(12),
61 | ),
62 | ),
63 | icon: ToolkitIcons.attach_file,
64 | iconColor: ToolkitColors.darkIcon,
65 | iconDecoration: ShapeDecoration(
66 | color: ToolkitColors.fileAttachmentIconBackground,
67 | shape: RoundedRectangleBorder(
68 | borderRadius: BorderRadius.circular(8),
69 | ),
70 | ),
71 | filenameStyle: ToolkitTextStyles.filename,
72 | filetypeStyle: ToolkitTextStyles.filetype,
73 | );
74 |
75 | /// The decoration for the file attachment container.
76 | final Decoration? decoration;
77 |
78 | /// The icon to display for the file attachment.
79 | final IconData? icon;
80 |
81 | /// The color of the file attachment icon.
82 | final Color? iconColor;
83 |
84 | /// The decoration for the file attachment icon container.
85 | final Decoration? iconDecoration;
86 |
87 | /// The text style for the filename.
88 | final TextStyle? filenameStyle;
89 |
90 | /// The text style for the filetype.
91 | final TextStyle? filetypeStyle;
92 | }
93 |
--------------------------------------------------------------------------------
/lib/src/styles/styles.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2024 The Flutter Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | export 'action_button_style.dart';
6 | export 'action_button_type.dart';
7 | export 'chat_input_style.dart';
8 | export 'file_attachment_style.dart';
9 | export 'llm_chat_view_style.dart';
10 | export 'llm_message_style.dart';
11 | export 'suggestion_style.dart';
12 | export 'user_message_style.dart';
13 |
--------------------------------------------------------------------------------
/lib/src/styles/suggestion_style.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2024 The Flutter Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | import 'package:flutter/widgets.dart';
6 |
7 | import 'toolkit_colors.dart';
8 | import 'toolkit_text_styles.dart';
9 |
10 | /// A class that defines the style for suggestions.
11 | @immutable
12 | class SuggestionStyle {
13 | /// Creates a [SuggestionStyle].
14 | ///
15 | /// The [textStyle] and [decoration] parameters can be used to customize
16 | /// the appearance of the suggestion.
17 | const SuggestionStyle({
18 | this.textStyle,
19 | this.decoration,
20 | });
21 |
22 | /// Resolves the [SuggestionStyle] by merging the provided [style] with the
23 | /// [defaultStyle].
24 | ///
25 | /// If [style] is null, the [defaultStyle] is used. If [defaultStyle] is not
26 | /// provided, the [defaultStyle] is obtained from
27 | /// [SuggestionStyle.defaultStyle].
28 | factory SuggestionStyle.resolve(
29 | SuggestionStyle? style, {
30 | SuggestionStyle? defaultStyle,
31 | }) {
32 | defaultStyle ??= SuggestionStyle.defaultStyle();
33 | return SuggestionStyle(
34 | textStyle: style?.textStyle ?? defaultStyle.textStyle,
35 | decoration: style?.decoration ?? defaultStyle.decoration,
36 | );
37 | }
38 |
39 | /// Provides a default style.
40 | ///
41 | /// This style is typically used as the base style for suggestions.
42 | factory SuggestionStyle.defaultStyle() => SuggestionStyle._lightStyle();
43 |
44 | /// Provides a default light style.
45 | ///
46 | /// This style is typically used for suggestions in light mode.
47 | factory SuggestionStyle._lightStyle() => SuggestionStyle(
48 | textStyle: ToolkitTextStyles.body1,
49 | decoration: const BoxDecoration(
50 | color: ToolkitColors.userMessageBackground,
51 | borderRadius: BorderRadius.all(Radius.circular(8)),
52 | ),
53 | );
54 |
55 | /// The text style for the suggestion.
56 | final TextStyle? textStyle;
57 |
58 | /// The decoration for the suggestion.
59 | final Decoration? decoration;
60 | }
61 |
--------------------------------------------------------------------------------
/lib/src/styles/tookit_icons.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2024 The Flutter Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | // by convention, using the names of the icons as the constant names
6 | // ignore_for_file: constant_identifier_names
7 |
8 | import 'package:flutter/widgets.dart';
9 |
10 | /// A collection of custom icons used in the Fat application.
11 | ///
12 | /// This class provides a set of static [IconData] constants that can be used
13 | /// to display custom icons in the application. These icons are part of a custom
14 | /// font called 'FatIcons'.
15 | /// Material Design Icons, Copyright (C) Google, Inc
16 | /// Author: Google
17 | /// License: Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0)
18 | /// Homepage: https://design.google.com/icons/
19 | ///
20 | @immutable
21 | class ToolkitIcons {
22 | const ToolkitIcons._();
23 |
24 | static const _kFontFam = 'FatIcons';
25 | static const String _kFontPkg = 'flutter_ai_toolkit';
26 |
27 | /// Icon for submitting or sending.
28 | static const IconData submit_icon =
29 | IconData(0xe800, fontFamily: _kFontFam, fontPackage: _kFontPkg);
30 |
31 | /// Icon representing a spark or idea.
32 | static const IconData spark_icon =
33 | IconData(0xe801, fontFamily: _kFontFam, fontPackage: _kFontPkg);
34 |
35 | /// Icon for adding or creating new items.
36 | static const IconData add =
37 | IconData(0xe802, fontFamily: _kFontFam, fontPackage: _kFontPkg);
38 |
39 | /// Icon for attaching files.
40 | static const IconData attach_file =
41 | IconData(0xe803, fontFamily: _kFontFam, fontPackage: _kFontPkg);
42 |
43 | /// Icon for stopping or halting an action.
44 | static const IconData stop =
45 | IconData(0xe804, fontFamily: _kFontFam, fontPackage: _kFontPkg);
46 |
47 | /// Icon representing a microphone.
48 | static const IconData mic =
49 | IconData(0xe805, fontFamily: _kFontFam, fontPackage: _kFontPkg);
50 |
51 | /// Icon for closing or dismissing.
52 | static const IconData close =
53 | IconData(0xe806, fontFamily: _kFontFam, fontPackage: _kFontPkg);
54 |
55 | /// Icon representing a camera.
56 | static const IconData camera_alt =
57 | IconData(0xe807, fontFamily: _kFontFam, fontPackage: _kFontPkg);
58 |
59 | /// Icon representing an image or picture.
60 | static const IconData image =
61 | IconData(0xe808, fontFamily: _kFontFam, fontPackage: _kFontPkg);
62 |
63 | /// Icon for editing.
64 | static const IconData edit =
65 | IconData(0xe809, fontFamily: _kFontFam, fontPackage: _kFontPkg);
66 |
67 | /// Icon for copying content.
68 | static const IconData content_copy =
69 | IconData(0xe80a, fontFamily: _kFontFam, fontPackage: _kFontPkg);
70 | }
71 |
--------------------------------------------------------------------------------
/lib/src/styles/toolkit_text_styles.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2024 The Flutter Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | import 'package:flutter/widgets.dart';
6 | import 'package:google_fonts/google_fonts.dart';
7 |
8 | import 'toolkit_colors.dart';
9 |
10 | /// A utility class that defines text styles for the Fat design system.
11 | @immutable
12 | abstract final class ToolkitTextStyles {
13 | /// Large display text style.
14 | ///
15 | /// Used for the most prominent text elements, typically headers or titles.
16 | static final TextStyle display = GoogleFonts.roboto(
17 | color: ToolkitColors.enabledText,
18 | fontSize: 32,
19 | fontWeight: FontWeight.w400,
20 | );
21 |
22 | /// Primary heading text style.
23 | ///
24 | /// Used for main section headings or important subheadings.
25 | static final TextStyle heading1 = GoogleFonts.roboto(
26 | color: ToolkitColors.enabledText,
27 | fontSize: 24,
28 | fontWeight: FontWeight.w400,
29 | );
30 |
31 | /// Secondary heading text style.
32 | ///
33 | /// Used for subsection headings or less prominent titles.
34 | static final TextStyle heading2 = GoogleFonts.roboto(
35 | color: ToolkitColors.enabledText,
36 | fontSize: 20,
37 | fontWeight: FontWeight.w400,
38 | );
39 |
40 | /// Primary body text style.
41 | ///
42 | /// Used for the main content text in the application.
43 | static final TextStyle body1 = GoogleFonts.roboto(
44 | color: ToolkitColors.enabledText,
45 | fontSize: 16,
46 | fontWeight: FontWeight.w400,
47 | );
48 |
49 | /// Code text style.
50 | ///
51 | /// Used for displaying code snippets or monospaced text.
52 | static final TextStyle code = GoogleFonts.robotoMono(
53 | color: ToolkitColors.enabledText,
54 | fontSize: 16,
55 | fontWeight: FontWeight.w400,
56 | );
57 |
58 | /// Secondary body text style.
59 | ///
60 | /// Used for less prominent body text or supporting information.
61 | static final TextStyle body2 = GoogleFonts.roboto(
62 | color: ToolkitColors.enabledText,
63 | fontSize: 14,
64 | fontWeight: FontWeight.w400,
65 | );
66 |
67 | /// Tooltip text style.
68 | ///
69 | /// Used for the text of tooltips.
70 | static final TextStyle tooltip = GoogleFonts.roboto(
71 | color: ToolkitColors.tooltipText.withOpacity(0.9),
72 | fontSize: 14,
73 | fontWeight: FontWeight.w400,
74 | );
75 |
76 | /// Filename text style.
77 | ///
78 | /// Used for the text of file attachments.
79 | static final TextStyle filename = GoogleFonts.roboto(
80 | color: ToolkitColors.enabledText,
81 | fontSize: 14,
82 | fontWeight: FontWeight.w400,
83 | );
84 |
85 | /// File type text style.
86 | ///
87 | /// Used for displaying the file type or MIME type of attachments.
88 | static final TextStyle filetype = GoogleFonts.roboto(
89 | color: ToolkitColors.hintText,
90 | fontSize: 14,
91 | fontWeight: FontWeight.w400,
92 | );
93 |
94 | /// Label text style.
95 | ///
96 | /// Used for small labels, captions, or helper text.
97 | static final TextStyle label = GoogleFonts.roboto(
98 | color: ToolkitColors.enabledText,
99 | fontSize: 12,
100 | fontWeight: FontWeight.w400,
101 | );
102 | }
103 |
--------------------------------------------------------------------------------
/lib/src/styles/user_message_style.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2024 The Flutter Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | import 'package:flutter/widgets.dart';
6 |
7 | import 'toolkit_colors.dart';
8 | import 'toolkit_text_styles.dart';
9 |
10 | /// Style for user messages.
11 | @immutable
12 | class UserMessageStyle {
13 | /// Creates a UserMessageStyle.
14 | const UserMessageStyle({
15 | this.textStyle,
16 | this.decoration,
17 | });
18 |
19 | /// Resolves the UserMessageStyle by combining the provided style with default
20 | /// values.
21 | ///
22 | /// This method takes an optional [style] and merges it with the
23 | /// [defaultStyle]. If [defaultStyle] is not provided, it uses
24 | /// [UserMessageStyle.defaultStyle].
25 | ///
26 | /// [style] - The custom UserMessageStyle to apply. Can be null.
27 | /// [defaultStyle] - The default UserMessageStyle to use as a base. If null,
28 | /// uses [UserMessageStyle.defaultStyle].
29 | ///
30 | /// Returns a new [UserMessageStyle] instance with resolved properties.
31 | factory UserMessageStyle.resolve(
32 | UserMessageStyle? style, {
33 | UserMessageStyle? defaultStyle,
34 | }) {
35 | defaultStyle ??= UserMessageStyle.defaultStyle();
36 | return UserMessageStyle(
37 | textStyle: style?.textStyle ?? defaultStyle.textStyle,
38 | decoration: style?.decoration ?? defaultStyle.decoration,
39 | );
40 | }
41 |
42 | /// Provides default style data for user messages.
43 | factory UserMessageStyle.defaultStyle() => UserMessageStyle._lightStyle();
44 |
45 | /// Provides a default light style.
46 | factory UserMessageStyle._lightStyle() => UserMessageStyle(
47 | textStyle: ToolkitTextStyles.body1,
48 | decoration: const BoxDecoration(
49 | color: ToolkitColors.userMessageBackground,
50 | borderRadius: BorderRadius.only(
51 | topLeft: Radius.circular(20),
52 | topRight: Radius.zero,
53 | bottomLeft: Radius.circular(20),
54 | bottomRight: Radius.circular(20),
55 | ),
56 | ),
57 | );
58 |
59 | /// The text style for user messages.
60 | final TextStyle? textStyle;
61 |
62 | /// The decoration for user message bubbles.
63 | final Decoration? decoration;
64 | }
65 |
--------------------------------------------------------------------------------
/lib/src/utility.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2024 The Flutter Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | import 'package:flutter/cupertino.dart' show BuildContext, CupertinoApp;
6 | import 'package:flutter/services.dart';
7 | import 'package:universal_platform/universal_platform.dart';
8 |
9 | import 'dialogs/adaptive_snack_bar/adaptive_snack_bar.dart';
10 |
11 | bool? _isCupertinoApp;
12 |
13 | /// Determines if the current application is a Cupertino-style app.
14 | ///
15 | /// This function checks the widget tree for the presence of a [CupertinoApp]
16 | /// widget. If found, it indicates that the app is using Cupertino (iOS-style)
17 | /// widgets.
18 | ///
19 | /// Parameters:
20 | /// * [context]: The [BuildContext] used to search the widget tree.
21 | ///
22 | /// Returns: A [bool] value. `true` if a [CupertinoApp] is found in the widget
23 | /// tree, `false` otherwise.
24 | bool isCupertinoApp(BuildContext context) {
25 | // caching the result to avoid recomputing it on every call; it's not likely
26 | // to change during the lifetime of the app
27 | _isCupertinoApp ??=
28 | context.findAncestorWidgetOfExactType() != null;
29 | return _isCupertinoApp!;
30 | }
31 |
32 | /// Determines if the current platform is a mobile device (Android or iOS).
33 | ///
34 | /// This constant uses the [UniversalPlatform] package to check the platform.
35 | ///
36 | /// Returns:
37 | /// A [bool] value. `true` if the platform is either Android or iOS,
38 | /// `false` otherwise.
39 | final isMobile = UniversalPlatform.isAndroid || UniversalPlatform.isIOS;
40 |
41 | /// Copies the given text to the clipboard and shows a confirmation message.
42 | ///
43 | /// This function uses the [Clipboard] API to copy the provided [text] to the
44 | /// system clipboard. After copying, it displays a confirmation message using
45 | /// [AdaptiveSnackBar] if the [context] is still mounted.
46 | ///
47 | /// Parameters:
48 | /// * [context]: The [BuildContext] used to show the confirmation message.
49 | /// * [text]: The text to be copied to the clipboard.
50 | ///
51 | /// Returns: A [Future] that completes when the text has been copied to the
52 | /// clipboard and the confirmation message has been shown.
53 | Future copyToClipboard(BuildContext context, String text) async {
54 | await Clipboard.setData(ClipboardData(text: text));
55 | if (context.mounted) {
56 | AdaptiveSnackBar.show(context, 'Message copied to clipboard');
57 | }
58 | }
59 |
60 | /// Inverts the given color.
61 | ///
62 | /// This function takes a [Color] object and returns a new [Color] object
63 | /// with the RGB values inverted. The alpha value remains unchanged.
64 | ///
65 | /// Parameters:
66 | /// * [color]: The [Color] to be inverted. This parameter must not be null.
67 | ///
68 | /// Returns: A new [Color] object with the inverted RGB values.
69 | Color invertColor(Color? color) => Color.fromARGB(
70 | color!.alpha,
71 | 255 - color.red,
72 | 255 - color.green,
73 | 255 - color.blue,
74 | );
75 |
--------------------------------------------------------------------------------
/lib/src/views/action_button/action_button.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2024 The Flutter Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | import 'package:flutter/material.dart' show Tooltip;
6 | import 'package:flutter/widgets.dart';
7 |
8 | import '../../styles/action_button_style.dart';
9 | import '../../utility.dart';
10 |
11 | /// A button widget with an icon.
12 | ///
13 | /// This widget creates a button with a customizable icon, size, decoration, and
14 | /// color. It can be enabled or disabled based on the presence of an [onPressed]
15 | /// callback.
16 | @immutable
17 | class ActionButton extends StatelessWidget {
18 | /// Creates an [ActionButton].
19 | ///
20 | /// The [onPressed] and [style] parameters must not be null.
21 | /// The [size] parameter defaults to 40 if not provided.
22 | const ActionButton({
23 | required this.onPressed,
24 | required this.style,
25 | super.key,
26 | this.size = 40,
27 | });
28 |
29 | /// The callback that is called when the button is tapped.
30 | /// If null, the button will be disabled.
31 | final VoidCallback onPressed;
32 |
33 | /// The style of the button.
34 | final ActionButtonStyle style;
35 |
36 | /// The diameter of the circular button.
37 | final double size;
38 |
39 | @override
40 | Widget build(BuildContext context) => GestureDetector(
41 | onTap: onPressed,
42 | child: Container(
43 | width: size,
44 | height: size,
45 | decoration: style.iconDecoration,
46 | // tooltips aren't a thing in cupertino, so skip it
47 | child: isCupertinoApp(context)
48 | ? Icon(
49 | style.icon,
50 | color: style.iconColor,
51 | size: size * 0.6,
52 | )
53 | : Tooltip(
54 | message: style.tooltip,
55 | textStyle: style.tooltipTextStyle,
56 | decoration: style.tooltipDecoration,
57 | child: Icon(
58 | style.icon,
59 | color: style.iconColor,
60 | size: size * 0.6,
61 | ),
62 | ),
63 | ),
64 | );
65 | }
66 |
--------------------------------------------------------------------------------
/lib/src/views/action_button/action_button_bar.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2024 The Flutter Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | import 'package:flutter/widgets.dart';
6 |
7 | import '../../styles/styles.dart';
8 | import 'action_button.dart';
9 |
10 | /// A widget that displays a horizontal bar of [ActionButton]s.
11 | ///
12 | /// This widget creates a container with rounded corners that houses a series of
13 | /// [ActionButton]s. The buttons are laid out horizontally and can overflow if
14 | /// there's not enough space.
15 | @immutable
16 | class ActionButtonBar extends StatelessWidget {
17 | /// Creates a [ActionButtonBar].
18 | ///
19 | /// The [buttons] parameter is required and specifies the list of
20 | /// [ActionButton]s to be displayed in the bar.
21 | const ActionButtonBar(
22 | this.buttons, {
23 | required this.style,
24 | super.key,
25 | });
26 |
27 | /// The list of [ActionButton]s to be displayed in the bar.
28 | final List buttons;
29 |
30 | /// The style of the action button bar.
31 | final LlmChatViewStyle style;
32 |
33 | @override
34 | Widget build(BuildContext context) => DecoratedBox(
35 | decoration: style.actionButtonBarDecoration!,
36 | child: OverflowBar(
37 | children: buttons,
38 | ),
39 | );
40 | }
41 |
--------------------------------------------------------------------------------
/lib/src/views/adaptive_progress_indicator.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2024 The Flutter Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | import 'package:flutter/cupertino.dart' show CupertinoActivityIndicator;
6 | import 'package:flutter/material.dart' show CircularProgressIndicator;
7 | import 'package:flutter/widgets.dart';
8 |
9 | import '../utility.dart';
10 |
11 | /// A progress indicator that adapts to the current platform.
12 | ///
13 | @immutable
14 | class AdaptiveCircularProgressIndicator extends StatelessWidget {
15 | /// Creates an adaptive circular progress indicator.
16 | ///
17 | /// This widget will display a [CupertinoActivityIndicator] on iOS
18 | /// and a [CircularProgressIndicator] on other platforms.
19 | ///
20 | /// The [key] parameter is optional and is used to control how one widget
21 | /// replaces another widget in the tree.
22 | const AdaptiveCircularProgressIndicator({required this.color, super.key});
23 |
24 | /// The color of the progress indicator.
25 | final Color color;
26 |
27 | @override
28 | Widget build(BuildContext context) => isCupertinoApp(context)
29 | ? CupertinoActivityIndicator(color: color)
30 | : CircularProgressIndicator(color: color);
31 | }
32 |
--------------------------------------------------------------------------------
/lib/src/views/attachment_view/attachment_view.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2024 The Flutter Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | import 'package:flutter/widgets.dart';
6 |
7 | import '../../providers/interface/attachments.dart';
8 | import 'file_attatchment_view.dart';
9 | import 'image_attachment_view.dart';
10 |
11 | /// A widget that displays an attachment based on its type.
12 | ///
13 | /// This widget determines the appropriate view for the given [attachment]
14 | /// and renders it accordingly. It supports file attachments and image
15 | /// attachments, but throws an exception for link attachments.
16 | @immutable
17 | class AttachmentView extends StatelessWidget {
18 | /// Creates an AttachmentView.
19 | ///
20 | /// The [attachment] parameter must not be null.
21 | const AttachmentView(this.attachment, {super.key});
22 |
23 | /// The attachment to be displayed.
24 | final Attachment attachment;
25 |
26 | /// The style for the attachment view.
27 |
28 | @override
29 | Widget build(BuildContext context) => switch (attachment) {
30 | (final ImageFileAttachment a) => ImageAttachmentView(a),
31 | (final FileAttachment a) => FileAttachmentView(a),
32 | (LinkAttachment _) => throw Exception('Link attachments not supported'),
33 | };
34 | }
35 |
--------------------------------------------------------------------------------
/lib/src/views/attachment_view/file_attatchment_view.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2024 The Flutter Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | import 'package:flutter/widgets.dart';
6 | import 'package:gap/gap.dart';
7 |
8 | import '../../chat_view_model/chat_view_model_client.dart';
9 | import '../../providers/interface/attachments.dart';
10 | import '../../styles/file_attachment_style.dart';
11 |
12 | /// A widget that displays a file attachment.
13 | ///
14 | /// This widget creates a container with a file icon and information about the
15 | /// attached file, such as its name and MIME type.
16 | @immutable
17 | class FileAttachmentView extends StatelessWidget {
18 | /// Creates a FileAttachmentView.
19 | ///
20 | /// The [attachment] parameter must not be null and represents the
21 | /// file attachment to be displayed.
22 | const FileAttachmentView(this.attachment, {super.key});
23 |
24 | /// The file attachment to be displayed.
25 | final FileAttachment attachment;
26 |
27 | @override
28 | Widget build(BuildContext context) => ChatViewModelClient(
29 | builder: (context, viewModel, child) {
30 | final attachmentStyle = FileAttachmentStyle.resolve(
31 | viewModel.style?.fileAttachmentStyle,
32 | );
33 |
34 | return Container(
35 | height: 80,
36 | padding: const EdgeInsets.all(8),
37 | decoration: attachmentStyle.decoration,
38 | child: Row(
39 | mainAxisSize: MainAxisSize.min,
40 | children: [
41 | Container(
42 | height: 64,
43 | padding: const EdgeInsets.all(10),
44 | decoration: attachmentStyle.iconDecoration,
45 | child: Icon(
46 | attachmentStyle.icon,
47 | color: attachmentStyle.iconColor,
48 | size: 24,
49 | ),
50 | ),
51 | const Gap(8),
52 | Flexible(
53 | child: Column(
54 | crossAxisAlignment: CrossAxisAlignment.start,
55 | mainAxisAlignment: MainAxisAlignment.center,
56 | children: [
57 | Text(
58 | attachment.name,
59 | style: attachmentStyle.filenameStyle,
60 | overflow: TextOverflow.ellipsis,
61 | ),
62 | Text(
63 | attachment.mimeType,
64 | style: attachmentStyle.filetypeStyle,
65 | overflow: TextOverflow.ellipsis,
66 | ),
67 | ],
68 | ),
69 | ),
70 | ],
71 | ),
72 | );
73 | },
74 | );
75 | }
76 |
--------------------------------------------------------------------------------
/lib/src/views/attachment_view/image_attachment_view.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2024 The Flutter Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | import 'dart:async';
6 |
7 | import 'package:flutter/widgets.dart';
8 |
9 | import '../../dialogs/adaptive_dialog.dart';
10 | import '../../dialogs/image_preview_dialog.dart';
11 | import '../../providers/interface/attachments.dart';
12 |
13 | /// A widget that displays an image attachment.
14 | ///
15 | /// This widget aligns the image to the center-right of its parent and
16 | /// allows the user to tap on the image to open a preview dialog.
17 | @immutable
18 | class ImageAttachmentView extends StatelessWidget {
19 | /// Creates an ImageAttachmentView.
20 | ///
21 | /// The [attachment] parameter must not be null and represents the
22 | /// image file attachment to be displayed.
23 | const ImageAttachmentView(this.attachment, {super.key});
24 |
25 | /// The image file attachment to be displayed.
26 | final ImageFileAttachment attachment;
27 |
28 | @override
29 | Widget build(BuildContext context) => Align(
30 | alignment: Alignment.centerRight,
31 | child: GestureDetector(
32 | onTap: () => unawaited(_showPreviewDialog(context)),
33 | child: Image.memory(attachment.bytes)),
34 | );
35 |
36 | Future _showPreviewDialog(BuildContext context) async =>
37 | AdaptiveAlertDialog.show(
38 | context: context,
39 | barrierDismissible: true,
40 | content: ImagePreviewDialog(attachment),
41 | );
42 | }
43 |
--------------------------------------------------------------------------------
/lib/src/views/chat_history_view.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2024 The Flutter Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | import 'package:flutter/widgets.dart';
6 |
7 | import '../chat_view_model/chat_view_model_client.dart';
8 | import '../providers/interface/chat_message.dart';
9 | import '../providers/interface/message_origin.dart';
10 | import 'chat_message_view/llm_message_view.dart';
11 | import 'chat_message_view/user_message_view.dart';
12 |
13 | /// A widget that displays a history of chat messages.
14 | ///
15 | /// This widget renders a scrollable list of chat messages, supporting
16 | /// selection and editing of messages. It displays messages in reverse
17 | /// chronological order (newest at the bottom).
18 | @immutable
19 | class ChatHistoryView extends StatefulWidget {
20 | /// Creates a [ChatHistoryView].
21 | ///
22 | /// If [onEditMessage] is provided, it will be called when a user initiates an
23 | /// edit action on an editable message (typically the last user message in the
24 | /// history).
25 | const ChatHistoryView({
26 | this.onEditMessage,
27 | super.key,
28 | });
29 |
30 | /// Optional callback function for editing a message.
31 | ///
32 | /// If provided, this function will be called when a user initiates an edit
33 | /// action on an editable message (typically the last user message in the
34 | /// history). The function receives the [ChatMessage] to be edited as its
35 | /// parameter.
36 | final void Function(ChatMessage message)? onEditMessage;
37 |
38 | @override
39 | State createState() => _ChatHistoryViewState();
40 | }
41 |
42 | class _ChatHistoryViewState extends State {
43 | @override
44 | Widget build(BuildContext context) => Padding(
45 | padding: const EdgeInsets.only(top: 16, left: 16, right: 16),
46 | child: ChatViewModelClient(
47 | builder: (context, viewModel, child) {
48 | final history = [
49 | if (viewModel.welcomeMessage != null)
50 | ChatMessage(
51 | origin: MessageOrigin.llm,
52 | text: viewModel.welcomeMessage,
53 | attachments: [],
54 | ),
55 | ...viewModel.provider.history,
56 | ];
57 |
58 | return ListView.builder(
59 | reverse: true,
60 | itemCount: history.length,
61 | itemBuilder: (context, index) {
62 | final messageIndex = history.length - index - 1;
63 | final message = history[messageIndex];
64 | final isLastUserMessage =
65 | message.origin.isUser && messageIndex >= history.length - 2;
66 | final canEdit =
67 | isLastUserMessage && widget.onEditMessage != null;
68 | final isUser = message.origin.isUser;
69 |
70 | return Padding(
71 | padding: const EdgeInsets.only(top: 6),
72 | child: isUser
73 | ? UserMessageView(
74 | message,
75 | onEdit: canEdit
76 | ? () => widget.onEditMessage?.call(message)
77 | : null,
78 | )
79 | : LlmMessageView(
80 | message,
81 | isWelcomeMessage: messageIndex == 0,
82 | ),
83 | );
84 | },
85 | );
86 | },
87 | ),
88 | );
89 | }
90 |
--------------------------------------------------------------------------------
/lib/src/views/chat_input/attachments_view.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2024 The Flutter Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | import 'package:flutter/widgets.dart';
6 |
7 | import '../../providers/interface/attachments.dart';
8 | import 'removable_attachment.dart';
9 |
10 | /// A widget that displays a horizontal list of attachments with the ability to
11 | /// remove them.
12 | @immutable
13 | class AttachmentsView extends StatelessWidget {
14 | /// Creates an [AttachmentsView].
15 | ///
16 | /// The [attachments] parameter is required and represents the list of
17 | /// attachments to display. The [onRemove] parameter is a callback function
18 | /// that is called when an attachment is removed.
19 | const AttachmentsView({
20 | required this.attachments,
21 | required this.onRemove,
22 | super.key,
23 | });
24 |
25 | /// The list of attachments to display.
26 | final Iterable attachments;
27 |
28 | /// Callback function that is called when an attachment is removed.
29 | ///
30 | /// The removed [Attachment] is passed as an argument to this function.
31 | final Function(Attachment) onRemove;
32 |
33 | @override
34 | Widget build(BuildContext context) => Container(
35 | height: attachments.isNotEmpty ? 104 : 0,
36 | padding: const EdgeInsets.only(top: 12, bottom: 12, left: 12),
37 | child: attachments.isNotEmpty
38 | ? ListView(
39 | scrollDirection: Axis.horizontal,
40 | children: [
41 | for (final a in attachments)
42 | RemovableAttachment(attachment: a, onRemove: onRemove),
43 | ],
44 | )
45 | : const SizedBox(),
46 | );
47 | }
48 |
--------------------------------------------------------------------------------
/lib/src/views/chat_input/chat_suggestion_view.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/widgets.dart';
2 |
3 | import '../../chat_view_model/chat_view_model_client.dart';
4 | import '../../styles/suggestion_style.dart';
5 |
6 | /// A widget that displays a list of chat suggestions.
7 | ///
8 | /// This widget takes a list of suggestions and a callback function that is
9 | /// triggered when a suggestion is selected. Each suggestion is displayed
10 | /// as a tappable container with padding and a background color.
11 | @immutable
12 | class ChatSuggestionsView extends StatelessWidget {
13 | /// Creates a [ChatSuggestionsView] widget.
14 | ///
15 | /// The [suggestions] parameter is a list of suggestion strings to display.
16 | /// The [onSelectSuggestion] parameter is a callback function that is called
17 | /// when a suggestion is tapped.
18 | const ChatSuggestionsView({
19 | required this.suggestions,
20 | required this.onSelectSuggestion,
21 | super.key,
22 | });
23 |
24 | /// The list of suggestions to display.
25 | final List suggestions;
26 |
27 | /// The callback function to call when a suggestion is selected.
28 | final void Function(String suggestion) onSelectSuggestion;
29 |
30 | @override
31 | Widget build(BuildContext context) => ChatViewModelClient(
32 | builder: (context, viewModel, child) {
33 | final suggestionStyle = SuggestionStyle.resolve(
34 | viewModel.style?.suggestionStyle,
35 | );
36 | return Wrap(
37 | children: [
38 | for (final suggestion in suggestions)
39 | GestureDetector(
40 | onTap: () => onSelectSuggestion(suggestion),
41 | child: Padding(
42 | padding: const EdgeInsets.all(8),
43 | child: Container(
44 | padding: const EdgeInsets.all(8),
45 | decoration: suggestionStyle.decoration,
46 | child: Text(
47 | suggestion,
48 | softWrap: true,
49 | maxLines: 3,
50 | style: suggestionStyle.textStyle,
51 | ),
52 | ),
53 | ),
54 | ),
55 | ],
56 | );
57 | },
58 | );
59 | }
60 |
--------------------------------------------------------------------------------
/lib/src/views/chat_input/editing_indicator.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/widgets.dart';
2 | import 'package:gap/gap.dart';
3 |
4 | import '../../styles/action_button_style.dart';
5 | import '../../styles/toolkit_text_styles.dart';
6 | import '../../utility.dart';
7 | import '../action_button/action_button.dart';
8 |
9 | /// A widget that displays an editing indicator with a cancel button.
10 | ///
11 | /// This widget is used to show that the user is currently editing a message.
12 | /// It provides a visual indicator with the text "Editing" and a button to
13 | /// cancel the editing action.
14 | ///
15 | /// The [onCancelEdit] callback is triggered when the cancel button is pressed.
16 | /// The [cancelButtonStyle] is used to style the cancel button.
17 | class EditingIndicator extends StatelessWidget {
18 | /// Creates an [EditingIndicator].
19 | ///
20 | /// The [onCancelEdit] and [cancelButtonStyle] parameters are required.
21 | const EditingIndicator({
22 | required this.onCancelEdit,
23 | required this.cancelButtonStyle,
24 | super.key,
25 | });
26 |
27 | /// The callback to be invoked when the cancel button is pressed.
28 | final VoidCallback onCancelEdit;
29 |
30 | /// The style to be applied to the cancel button.
31 | final ActionButtonStyle cancelButtonStyle;
32 |
33 | @override
34 | Widget build(BuildContext context) => Padding(
35 | padding: const EdgeInsets.only(right: 16),
36 | child: Row(
37 | mainAxisAlignment: MainAxisAlignment.end,
38 | crossAxisAlignment: CrossAxisAlignment.end,
39 | children: [
40 | Text(
41 | 'Editing',
42 | style: ToolkitTextStyles.label.copyWith(
43 | color: invertColor(cancelButtonStyle.iconColor),
44 | ),
45 | ),
46 | const Gap(6),
47 | ActionButton(
48 | onPressed: onCancelEdit,
49 | style: cancelButtonStyle,
50 | size: 16,
51 | ),
52 | ],
53 | ),
54 | );
55 | }
56 |
--------------------------------------------------------------------------------
/lib/src/views/chat_input/input_button.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2024 The Flutter Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | import 'package:flutter/widgets.dart';
6 |
7 | import '../../styles/llm_chat_view_style.dart';
8 | import '../action_button/action_button.dart';
9 | import '../adaptive_progress_indicator.dart';
10 | import 'input_state.dart';
11 |
12 | /// A button widget that adapts its appearance and behavior based on the current
13 | /// input state.
14 | @immutable
15 | class InputButton extends StatelessWidget {
16 | /// Creates an [InputButton].
17 | ///
18 | /// All parameters are required:
19 | /// - [inputState]: The current state of the input.
20 | /// - [chatStyle]: The style configuration for the chat interface.
21 | /// - [onSubmitPrompt]: Callback function when submitting a prompt.
22 | /// - [onCancelPrompt]: Callback function when cancelling a prompt.
23 | /// - [onStartRecording]: Callback function when starting audio recording.
24 | /// - [onStopRecording]: Callback function when stopping audio recording.
25 | const InputButton({
26 | required this.inputState,
27 | required this.chatStyle,
28 | required this.onSubmitPrompt,
29 | required this.onCancelPrompt,
30 | required this.onStartRecording,
31 | required this.onStopRecording,
32 | super.key,
33 | });
34 |
35 | /// The current state of the input.
36 | final InputState inputState;
37 |
38 | /// The style configuration for the chat interface.
39 | final LlmChatViewStyle chatStyle;
40 |
41 | /// Callback function when submitting a prompt.
42 | final void Function() onSubmitPrompt;
43 |
44 | /// Callback function when cancelling a prompt.
45 | final void Function() onCancelPrompt;
46 |
47 | /// Callback function when starting audio recording.
48 | final void Function() onStartRecording;
49 |
50 | /// Callback function when stopping audio recording.
51 | final void Function() onStopRecording;
52 |
53 | @override
54 | Widget build(BuildContext context) => switch (inputState) {
55 | InputState.canSubmitPrompt => ActionButton(
56 | style: chatStyle.submitButtonStyle!,
57 | onPressed: onSubmitPrompt,
58 | ),
59 | InputState.canCancelPrompt => ActionButton(
60 | style: chatStyle.stopButtonStyle!,
61 | onPressed: onCancelPrompt,
62 | ),
63 | InputState.canStt => ActionButton(
64 | style: chatStyle.recordButtonStyle!,
65 | onPressed: onStartRecording,
66 | ),
67 | InputState.isRecording => ActionButton(
68 | style: chatStyle.stopButtonStyle!,
69 | onPressed: onStopRecording,
70 | ),
71 | InputState.canCancelStt => AdaptiveCircularProgressIndicator(
72 | color: chatStyle.progressIndicatorColor!,
73 | ),
74 | };
75 | }
76 |
--------------------------------------------------------------------------------
/lib/src/views/chat_input/input_state.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2024 The Flutter Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | /// Represents the different states of the chat input.
6 | enum InputState {
7 | /// The input has text and the submit button is enabled.
8 | canSubmitPrompt,
9 |
10 | /// A prompt is being submitted and the cancel button is enabled.
11 | canCancelPrompt,
12 |
13 | /// The input is empty and the microphone button for speech-to-text is
14 | /// enabled.
15 | canStt,
16 |
17 | /// Speech is being recorded and the stop button is enabled.
18 | isRecording,
19 |
20 | /// Speech is being translated to text and a progress indicator is shown.
21 | canCancelStt,
22 | }
23 |
--------------------------------------------------------------------------------
/lib/src/views/chat_input/removable_attachment.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2024 The Flutter Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | import 'dart:async';
6 |
7 | import 'package:flutter/widgets.dart';
8 |
9 | import '../../chat_view_model/chat_view_model_client.dart';
10 | import '../../dialogs/adaptive_dialog.dart';
11 | import '../../dialogs/image_preview_dialog.dart';
12 | import '../../providers/interface/attachments.dart';
13 | import '../../styles/llm_chat_view_style.dart';
14 | import '../action_button/action_button.dart';
15 | import '../attachment_view/attachment_view.dart';
16 |
17 | /// A widget that displays an attachment with a remove button.
18 | @immutable
19 | class RemovableAttachment extends StatelessWidget {
20 | /// Creates a [RemovableAttachment].
21 | ///
22 | /// The [attachment] parameter is required and represents the attachment to
23 | /// display. The [onRemove] parameter is a callback function that is called
24 | /// when the remove button is pressed.
25 | const RemovableAttachment({
26 | required this.attachment,
27 | required this.onRemove,
28 | super.key,
29 | });
30 |
31 | /// The attachment to display.
32 | final Attachment attachment;
33 |
34 | /// Callback function that is called when the remove button is pressed.
35 | ///
36 | /// The [Attachment] to be removed is passed as an argument to this function.
37 | final Function(Attachment) onRemove;
38 |
39 | @override
40 | Widget build(BuildContext context) => Stack(
41 | children: [
42 | GestureDetector(
43 | onTap: attachment is ImageFileAttachment
44 | ? () => unawaited(_showPreviewDialog(context))
45 | : null,
46 | child: Container(
47 | padding: const EdgeInsets.only(right: 12),
48 | height: 80,
49 | child: AttachmentView(attachment),
50 | ),
51 | ),
52 | Padding(
53 | padding: const EdgeInsets.all(2),
54 | child: ChatViewModelClient(
55 | builder: (context, viewModel, child) {
56 | final chatStyle = LlmChatViewStyle.resolve(viewModel.style);
57 | return ActionButton(
58 | style: chatStyle.closeButtonStyle!,
59 | size: 20,
60 | onPressed: () => onRemove(attachment),
61 | );
62 | },
63 | ),
64 | ),
65 | ],
66 | );
67 |
68 | Future _showPreviewDialog(BuildContext context) async =>
69 | AdaptiveAlertDialog.show(
70 | context: context,
71 | barrierDismissible: true,
72 | content: ImagePreviewDialog(attachment as ImageFileAttachment),
73 | );
74 | }
75 |
--------------------------------------------------------------------------------
/lib/src/views/chat_message_view/adaptive_copy_text.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | import 'package:flutter/material.dart'
4 | show DefaultMaterialLocalizations, SelectionArea;
5 | import 'package:flutter/widgets.dart';
6 | import 'package:flutter_context_menu/flutter_context_menu.dart';
7 |
8 | import '../../styles/llm_chat_view_style.dart';
9 | import '../../utility.dart';
10 |
11 | /// A widget that displays text with adaptive copy functionality.
12 | ///
13 | /// This widget provides a context menu for copying text to the clipboard on
14 | /// mobile devices, and a selection area for mouse-driven selection on desktop
15 | /// and web platforms.
16 | @immutable
17 | class AdaptiveCopyText extends StatelessWidget {
18 | /// Creates an [AdaptiveCopyText] widget.
19 | ///
20 | /// The [clipboardText] parameter is required and contains the text to be
21 | /// copied to the clipboard. The [child] parameter is required and contains
22 | /// the widget to be displayed. The [chatStyle] parameter is required and
23 | /// contains the style information for the chat. The [onEdit] parameter is
24 | /// optional and contains the callback to be invoked when the text is edited.
25 | const AdaptiveCopyText({
26 | required this.clipboardText,
27 | required this.child,
28 | required this.chatStyle,
29 | this.onEdit,
30 | super.key,
31 | });
32 |
33 | /// The text to be copied to the clipboard.
34 | final String clipboardText;
35 |
36 | /// The widget to be displayed.
37 | final Widget child;
38 |
39 | /// The callback to be invoked when the text is edited.
40 | final VoidCallback? onEdit;
41 |
42 | /// The style information for the chat.
43 | final LlmChatViewStyle chatStyle;
44 |
45 | @override
46 | Widget build(BuildContext context) {
47 | final contextMenu = ContextMenu(
48 | entries: [
49 | if (onEdit != null)
50 | MenuItem(
51 | label: 'Edit',
52 | icon: chatStyle.editButtonStyle!.icon,
53 | onSelected: onEdit,
54 | ),
55 | MenuItem(
56 | label: 'Copy',
57 | icon: chatStyle.copyButtonStyle!.icon,
58 | onSelected: () => unawaited(copyToClipboard(context, clipboardText)),
59 | ),
60 | ],
61 | );
62 |
63 | // On mobile, show the context menu for long-press;
64 | // on desktop and web, show the selection area for mouse-driven selection.
65 | return isMobile
66 | ? ContextMenuRegion(contextMenu: contextMenu, child: child)
67 | : Localizations(
68 | locale: Localizations.localeOf(context),
69 | delegates: const [
70 | DefaultWidgetsLocalizations.delegate,
71 | DefaultMaterialLocalizations.delegate,
72 | ],
73 | child: SelectionArea(child: child),
74 | );
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/lib/src/views/chat_message_view/user_message_view.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2024 The Flutter Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | import 'package:flutter/widgets.dart';
6 |
7 | import '../../chat_view_model/chat_view_model_client.dart';
8 | import '../../providers/interface/chat_message.dart';
9 | import '../../styles/styles.dart';
10 | import '../attachment_view/attachment_view.dart';
11 | import 'adaptive_copy_text.dart';
12 | import 'hovering_buttons.dart';
13 |
14 | /// A widget that displays a user's message in a chat interface.
15 | ///
16 | /// This widget is responsible for rendering the user's message, including any
17 | /// attachments, in a right-aligned layout. It uses a [Row] and [Column] to
18 | /// structure the content, with the message text displayed in a styled
19 | /// container.
20 | @immutable
21 | class UserMessageView extends StatelessWidget {
22 | /// Creates a [UserMessageView].
23 | ///
24 | /// The [message] parameter is required and contains the [ChatMessage] to be
25 | /// displayed.
26 | const UserMessageView(this.message, {super.key, this.onEdit});
27 |
28 | /// The chat message to be displayed.
29 | final ChatMessage message;
30 |
31 | /// The callback to be invoked when the message is edited.
32 | final VoidCallback? onEdit;
33 |
34 | @override
35 | Widget build(BuildContext context) => Column(
36 | children: [
37 | ...[
38 | for (final attachment in message.attachments)
39 | Padding(
40 | padding: const EdgeInsets.only(bottom: 6),
41 | child: Align(
42 | alignment: Alignment.topRight,
43 | child: SizedBox(
44 | height: 80,
45 | width: 200,
46 | child: AttachmentView(attachment),
47 | ),
48 | ),
49 | ),
50 | ],
51 | ChatViewModelClient(
52 | builder: (context, viewModel, child) {
53 | final text = message.text!;
54 | final chatStyle = LlmChatViewStyle.resolve(viewModel.style);
55 | final userStyle = UserMessageStyle.resolve(
56 | chatStyle.userMessageStyle,
57 | );
58 |
59 | return Align(
60 | alignment: Alignment.topRight,
61 | child: Padding(
62 | padding: const EdgeInsets.only(right: 16),
63 | child: HoveringButtons(
64 | isUserMessage: true,
65 | chatStyle: chatStyle,
66 | clipboardText: text,
67 | onEdit: onEdit,
68 | child: DecoratedBox(
69 | decoration: userStyle.decoration!,
70 | child: Padding(
71 | padding: const EdgeInsets.only(
72 | left: 16,
73 | right: 16,
74 | top: 12,
75 | bottom: 12,
76 | ),
77 | child: AdaptiveCopyText(
78 | chatStyle: chatStyle,
79 | clipboardText: text,
80 | onEdit: onEdit,
81 | child: Text(text, style: userStyle.textStyle),
82 | ),
83 | ),
84 | ),
85 | ),
86 | ),
87 | );
88 | },
89 | ),
90 | ],
91 | );
92 | }
93 |
--------------------------------------------------------------------------------
/lib/src/views/chat_text_field.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2024 The Flutter Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | import 'package:flutter/cupertino.dart' show CupertinoTextField;
6 | import 'package:flutter/material.dart'
7 | show InputBorder, InputDecoration, TextField, TextInputAction;
8 | import 'package:flutter/widgets.dart';
9 |
10 | import '../styles/toolkit_colors.dart';
11 | import '../utility.dart';
12 |
13 | /// A text field that adapts to the current app style (Material or Cupertino).
14 | ///
15 | /// This widget will render either a [CupertinoTextField] or a [TextField]
16 | /// depending on whether the app is using Cupertino or Material design.
17 | @immutable
18 | class ChatTextField extends StatelessWidget {
19 | /// Creates an adaptive text field.
20 | ///
21 | /// Many of the parameters are required to ensure consistent behavior
22 | /// across both Cupertino and Material designs.
23 | const ChatTextField({
24 | required this.minLines,
25 | required this.maxLines,
26 | required this.autofocus,
27 | required this.style,
28 | required this.textInputAction,
29 | required this.controller,
30 | required this.focusNode,
31 | required this.onSubmitted,
32 | required this.hintText,
33 | required this.hintStyle,
34 | required this.hintPadding,
35 | super.key,
36 | });
37 |
38 | /// The minimum number of lines to show.
39 | final int minLines;
40 |
41 | /// The maximum number of lines to show.
42 | final int maxLines;
43 |
44 | /// Whether the text field should be focused initially.
45 | final bool autofocus;
46 |
47 | /// The style to use for the text being edited.
48 | final TextStyle style;
49 |
50 | /// The type of action button to use for the keyboard.
51 | final TextInputAction textInputAction;
52 |
53 | /// Controls the text being edited.
54 | final TextEditingController controller;
55 |
56 | /// Defines the keyboard focus for this widget.
57 | final FocusNode focusNode;
58 |
59 | /// The text to show when the text field is empty.
60 | final String hintText;
61 |
62 | /// The style to use for the hint text.
63 | final TextStyle hintStyle;
64 |
65 | /// The padding to use for the hint text.
66 | final EdgeInsetsGeometry? hintPadding;
67 |
68 | /// Called when the user submits editable content.
69 | final void Function(String text) onSubmitted;
70 |
71 | @override
72 | Widget build(BuildContext context) => isCupertinoApp(context)
73 | ? CupertinoTextField(
74 | minLines: minLines,
75 | maxLines: maxLines,
76 | controller: controller,
77 | autofocus: autofocus,
78 | focusNode: focusNode,
79 | onSubmitted: onSubmitted,
80 | style: style,
81 | placeholder: hintText,
82 | placeholderStyle: hintStyle,
83 | padding: hintPadding ?? EdgeInsets.zero,
84 | decoration: BoxDecoration(
85 | border: Border.all(width: 0, color: ToolkitColors.transparent),
86 | ),
87 | textInputAction: textInputAction,
88 | )
89 | : TextField(
90 | minLines: minLines,
91 | maxLines: maxLines,
92 | controller: controller,
93 | autofocus: autofocus,
94 | focusNode: focusNode,
95 | textInputAction: textInputAction,
96 | onSubmitted: onSubmitted,
97 | style: style,
98 | decoration: InputDecoration(
99 | border: InputBorder.none,
100 | hintText: hintText,
101 | hintStyle: hintStyle,
102 | contentPadding: hintPadding,
103 | ),
104 | );
105 | }
106 |
--------------------------------------------------------------------------------
/lib/src/views/jumping_dots_progress_indicator/jumping_dot.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2024 The Flutter Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | import 'package:flutter/widgets.dart';
6 |
7 | /// A widget that represents a single jumping dot in the progress indicator.
8 | @immutable
9 | class JumpingDot extends AnimatedWidget {
10 | /// Creates a [JumpingDot] widget.
11 | ///
12 | /// The [animation] parameter is required and controls the vertical movement
13 | /// of the dot. The [color] parameter sets the color of the dot. The
14 | /// [fontSize] parameter determines the size of the dot.
15 | const JumpingDot({
16 | required Animation animation,
17 | required this.color,
18 | required this.fontSize,
19 | super.key,
20 | }) : super(listenable: animation);
21 |
22 | /// The color of the dot.
23 | final Color color;
24 |
25 | /// The font size of the dot.
26 | final double fontSize;
27 |
28 | Animation get _animation => listenable as Animation;
29 |
30 | @override
31 | Widget build(BuildContext context) => SizedBox(
32 | height: _animation.value + fontSize,
33 | child: Text(
34 | '.',
35 | style: TextStyle(
36 | color: color,
37 | fontSize: fontSize,
38 | height: 1, // Center the text vertically within its line height
39 | ),
40 | ),
41 | );
42 | }
43 |
--------------------------------------------------------------------------------
/lib/src/views/llm_chat_view/llm_response.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2024 The Flutter Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | import 'dart:async';
6 |
7 | import '../../llm_exception.dart';
8 |
9 | /// Represents a response from an LLM (Language Learning Model).
10 | ///
11 | /// This class manages the streaming of LLM responses, error handling, and
12 | /// cleanup.
13 | class LlmResponse {
14 | /// Creates an LlmResponse.
15 | ///
16 | /// [stream] is the stream of text chunks from the LLM. [onDone] is an
17 | /// optional callback for when the response is complete or encounters an
18 | /// error.
19 | LlmResponse({
20 | required Stream stream,
21 | required this.onUpdate,
22 | required this.onDone,
23 | }) {
24 | _subscription = stream.listen(
25 | onUpdate,
26 | onDone: () => onDone(null),
27 | cancelOnError: true,
28 | onError: (err) => _close(_exception(err)),
29 | );
30 | }
31 |
32 | /// Callback function to be called when a new chunk is received from the
33 | /// response stream.
34 | final void Function(String text) onUpdate;
35 |
36 | /// Callback function to be called when the response is complete or encounters
37 | /// an error.
38 | final void Function(LlmException? error) onDone;
39 |
40 | /// Cancels the response stream.
41 | void cancel() => _close(const LlmCancelException());
42 |
43 | StreamSubscription? _subscription;
44 |
45 | LlmException _exception(dynamic err) => switch (err) {
46 | (LlmCancelException _) => const LlmCancelException(),
47 | (final LlmFailureException ex) => ex,
48 | _ => LlmFailureException(err.toString()),
49 | };
50 |
51 | void _close(LlmException error) {
52 | assert(_subscription != null);
53 | unawaited(_subscription!.cancel());
54 | _subscription = null;
55 | onDone.call(error);
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/lib/src/views/response_builder.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2024 The Flutter Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | import 'package:flutter/widgets.dart';
6 |
7 | /// A function type that defines how to build a widget for displaying a response
8 | /// in the chat interface.
9 | ///
10 | /// [context] is the build context, which can be used to access theme data and
11 | /// other contextual information.
12 | ///
13 | /// [response] is the text of the response from the LLM.
14 | ///
15 | /// The function should return a [Widget] that represents the formatted response
16 | /// in the chat interface.
17 | typedef ResponseBuilder = Widget Function(
18 | BuildContext context,
19 | String response,
20 | );
21 |
--------------------------------------------------------------------------------
/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: flutter_ai_toolkit
2 | description: "A set of AI chat-related widgets for your Flutter app targeting mobile, desktop and web."
3 | version: 0.6.5
4 | homepage: https://github.com/csells/flutter_ai_toolkit
5 |
6 | environment:
7 | sdk: '>=3.4.0 <4.0.0'
8 | flutter: ">=1.17.0"
9 |
10 | dependencies:
11 | cross_file: ^0.3.4+2
12 | file_selector: ^1.0.3
13 | firebase_vertexai: ^1.0.1
14 | flutter:
15 | sdk: flutter
16 | flutter_context_menu: ^0.2.0
17 | flutter_markdown: ^0.7.4+3
18 | flutter_picture_taker: ^0.2.0
19 | gap: ^3.0.1
20 | google_fonts: ^6.2.1
21 | google_generative_ai: ^0.4.3
22 | image_picker: ^1.1.2
23 | mime: ^2.0.0
24 | universal_platform: ^1.1.0
25 | uuid: ^4.4.2
26 | waveform_recorder: ^1.3.0
27 |
28 | dev_dependencies:
29 | all_lint_rules_community: ^0.0.42
30 | flutter_test:
31 | sdk: flutter
32 |
33 | flutter:
34 | fonts:
35 | - family: FatIcons
36 | fonts:
37 | - asset: lib/fonts/FatIcons.ttf
38 |
--------------------------------------------------------------------------------