├── linux ├── .gitignore ├── runner │ ├── main.cc │ ├── my_application.h │ └── CMakeLists.txt ├── flatpak │ ├── dev.ibrahimcetin.reins.desktop │ └── dev.ibrahimcetin.reins.metainfo.xml ├── flutter │ ├── generated_plugin_registrant.h │ ├── generated_plugin_registrant.cc │ ├── generated_plugins.cmake │ └── CMakeLists.txt └── CMakeLists.txt ├── ios ├── Runner │ ├── Runner-Bridging-Header.h │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── reins.png │ │ │ ├── reins-dark 1.png │ │ │ ├── reins-dark.png │ │ │ └── Contents.json │ │ ├── LaunchImage.imageset │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ ├── LaunchImageDark.png │ │ │ ├── LaunchImageDark@2x.png │ │ │ ├── LaunchImageDark@3x.png │ │ │ ├── README.md │ │ │ └── Contents.json │ │ └── LaunchBackground.imageset │ │ │ ├── background.png │ │ │ ├── darkbackground.png │ │ │ └── Contents.json │ ├── AppDelegate.swift │ ├── Base.lproj │ │ ├── Main.storyboard │ │ └── LaunchScreen.storyboard │ └── Info.plist ├── Flutter │ ├── Debug.xcconfig │ ├── Release.xcconfig │ └── AppFrameworkInfo.plist ├── Runner.xcodeproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── WorkspaceSettings.xcsettings │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── WorkspaceSettings.xcsettings │ │ └── IDEWorkspaceChecks.plist ├── RunnerTests │ └── RunnerTests.swift ├── .gitignore ├── Podfile └── Podfile.lock ├── web ├── favicon.png ├── icons │ ├── Icon-192.png │ ├── Icon-512.png │ ├── Icon-maskable-192.png │ └── Icon-maskable-512.png ├── manifest.json └── index.html ├── assets └── images │ ├── ollama.png │ ├── reins.png │ ├── icons │ ├── reins.png │ ├── reins-macos.png │ └── reins-windows.png │ └── splash │ ├── splash_dark.png │ ├── splash_light.png │ ├── splash_dark_android12.png │ └── splash_light_android12.png ├── test ├── assets │ └── images │ │ └── ollama.png └── ollama_modefile_generator_test.dart ├── macos ├── Runner │ ├── Configs │ │ ├── Debug.xcconfig │ │ ├── Release.xcconfig │ │ ├── Warnings.xcconfig │ │ └── AppInfo.xcconfig │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ ├── app_icon_128.png │ │ │ ├── app_icon_16.png │ │ │ ├── app_icon_256.png │ │ │ ├── app_icon_32.png │ │ │ ├── app_icon_512.png │ │ │ ├── app_icon_64.png │ │ │ ├── app_icon_1024.png │ │ │ └── Contents.json │ ├── AppDelegate.swift │ ├── Release.entitlements │ ├── MainFlutterWindow.swift │ ├── DebugProfile.entitlements │ └── Info.plist ├── .gitignore ├── Flutter │ ├── Flutter-Debug.xcconfig │ ├── Flutter-Release.xcconfig │ └── GeneratedPluginRegistrant.swift ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── Runner.xcodeproj │ ├── project.xcworkspace │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── RunnerTests │ └── RunnerTests.swift ├── Podfile.lock └── Podfile ├── PRIVACY ├── windows ├── runner │ ├── resources │ │ └── app_icon.ico │ ├── resource.h │ ├── runner.exe.manifest │ ├── utils.h │ ├── flutter_window.h │ ├── main.cpp │ ├── CMakeLists.txt │ ├── utils.cpp │ ├── flutter_window.cpp │ ├── Runner.rc │ └── win32_window.h ├── .gitignore ├── flutter │ ├── generated_plugin_registrant.h │ ├── generated_plugins.cmake │ ├── generated_plugin_registrant.cc │ └── CMakeLists.txt └── CMakeLists.txt ├── .vscode ├── settings.json └── launch.json ├── lib ├── Models │ ├── ollama_request_state.dart │ ├── settings_route_arguments.dart │ ├── ollama_exception.dart │ ├── chat_preset.dart │ ├── chat_configure_arguments.dart │ └── ollama_model.dart ├── Pages │ ├── settings_page │ │ ├── subwidgets │ │ │ ├── subwidgets.dart │ │ │ └── reins_settings.dart │ │ └── settings_page.dart │ ├── chat_page │ │ ├── subwidgets │ │ │ ├── subwidgets.dart │ │ │ ├── chat_select_model_button.dart │ │ │ ├── chat_attachment │ │ │ │ ├── chat_attachment_row.dart │ │ │ │ ├── chat_attachment_image.dart │ │ │ │ └── chat_attachment_preset.dart │ │ │ ├── chat_empty.dart │ │ │ ├── chat_error.dart │ │ │ ├── chat_bubble │ │ │ │ ├── chat_bubble_bottom_sheet.dart │ │ │ │ ├── chat_bubble_menu.dart │ │ │ │ ├── chat_bubble_think_block.dart │ │ │ │ ├── chat_bubble_image.dart │ │ │ │ ├── chat_bubble_actions.dart │ │ │ │ └── chat_bubble.dart │ │ │ ├── chat_text_field.dart │ │ │ └── chat_welcome.dart │ │ └── chat_page_view_model.dart │ └── main_page.dart ├── Services │ ├── services.dart │ ├── permission_service.dart │ ├── ollama_modelfile_generator.dart │ └── image_service.dart ├── Constants │ ├── constants.dart │ ├── app_constants.dart │ ├── path_manager.dart │ └── generate_title_constants.dart ├── Widgets │ ├── title_divider.dart │ ├── ollama_bottom_sheet_header.dart │ ├── chat_image.dart │ ├── chat_drawer.dart │ └── chat_app_bar.dart ├── Utils │ ├── material_color_adapter.dart │ ├── observe_size.dart │ ├── retained_position_scroll_physics.dart │ ├── border_painter.dart │ └── request_review_helper.dart └── main.dart ├── android ├── app │ ├── src │ │ ├── main │ │ │ ├── res │ │ │ │ ├── drawable-hdpi │ │ │ │ │ ├── splash.png │ │ │ │ │ └── android12splash.png │ │ │ │ ├── drawable-mdpi │ │ │ │ │ ├── splash.png │ │ │ │ │ └── android12splash.png │ │ │ │ ├── drawable │ │ │ │ │ ├── background.png │ │ │ │ │ └── launch_background.xml │ │ │ │ ├── drawable-v21 │ │ │ │ │ ├── background.png │ │ │ │ │ └── launch_background.xml │ │ │ │ ├── drawable-xhdpi │ │ │ │ │ ├── splash.png │ │ │ │ │ └── android12splash.png │ │ │ │ ├── drawable-xxhdpi │ │ │ │ │ ├── splash.png │ │ │ │ │ └── android12splash.png │ │ │ │ ├── drawable-xxxhdpi │ │ │ │ │ ├── splash.png │ │ │ │ │ └── android12splash.png │ │ │ │ ├── mipmap-hdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── drawable-night │ │ │ │ │ ├── background.png │ │ │ │ │ └── launch_background.xml │ │ │ │ ├── mipmap-xhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── drawable-night-hdpi │ │ │ │ │ ├── splash.png │ │ │ │ │ └── android12splash.png │ │ │ │ ├── drawable-night-mdpi │ │ │ │ │ ├── splash.png │ │ │ │ │ └── android12splash.png │ │ │ │ ├── drawable-night-xhdpi │ │ │ │ │ ├── splash.png │ │ │ │ │ └── android12splash.png │ │ │ │ ├── drawable-night-xxhdpi │ │ │ │ │ ├── splash.png │ │ │ │ │ └── android12splash.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── drawable-night-v21 │ │ │ │ │ ├── background.png │ │ │ │ │ └── launch_background.xml │ │ │ │ ├── drawable-night-xxxhdpi │ │ │ │ │ ├── splash.png │ │ │ │ │ └── android12splash.png │ │ │ │ ├── values-v31 │ │ │ │ │ └── styles.xml │ │ │ │ ├── values-night-v31 │ │ │ │ │ └── styles.xml │ │ │ │ ├── values │ │ │ │ │ └── styles.xml │ │ │ │ └── values-night │ │ │ │ │ └── styles.xml │ │ │ ├── kotlin │ │ │ │ └── dev │ │ │ │ │ └── ibrahimcetin │ │ │ │ │ └── reins │ │ │ │ │ └── MainActivity.kt │ │ │ └── AndroidManifest.xml │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ └── profile │ │ │ └── AndroidManifest.xml │ └── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── .gitignore ├── build.gradle └── settings.gradle ├── devtools_options.yaml ├── .gitignore ├── analysis_options.yaml ├── .metadata └── README.md /linux/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral 2 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonofagl1tch/reins/main/web/favicon.png -------------------------------------------------------------------------------- /web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonofagl1tch/reins/main/web/icons/Icon-192.png -------------------------------------------------------------------------------- /web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonofagl1tch/reins/main/web/icons/Icon-512.png -------------------------------------------------------------------------------- /assets/images/ollama.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonofagl1tch/reins/main/assets/images/ollama.png -------------------------------------------------------------------------------- /assets/images/reins.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonofagl1tch/reins/main/assets/images/reins.png -------------------------------------------------------------------------------- /assets/images/icons/reins.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonofagl1tch/reins/main/assets/images/icons/reins.png -------------------------------------------------------------------------------- /test/assets/images/ollama.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonofagl1tch/reins/main/test/assets/images/ollama.png -------------------------------------------------------------------------------- /web/icons/Icon-maskable-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonofagl1tch/reins/main/web/icons/Icon-maskable-192.png -------------------------------------------------------------------------------- /web/icons/Icon-maskable-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonofagl1tch/reins/main/web/icons/Icon-maskable-512.png -------------------------------------------------------------------------------- /macos/Runner/Configs/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Debug.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /PRIVACY: -------------------------------------------------------------------------------- 1 | Reins app does not track any activity or actions of their users. No private information is being collected by the app. -------------------------------------------------------------------------------- /assets/images/icons/reins-macos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonofagl1tch/reins/main/assets/images/icons/reins-macos.png -------------------------------------------------------------------------------- /assets/images/icons/reins-windows.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonofagl1tch/reins/main/assets/images/icons/reins-windows.png -------------------------------------------------------------------------------- /assets/images/splash/splash_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonofagl1tch/reins/main/assets/images/splash/splash_dark.png -------------------------------------------------------------------------------- /assets/images/splash/splash_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonofagl1tch/reins/main/assets/images/splash/splash_light.png -------------------------------------------------------------------------------- /macos/Runner/Configs/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Release.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /windows/runner/resources/app_icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonofagl1tch/reins/main/windows/runner/resources/app_icon.ico -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cmake.ignoreCMakeListsMissing": true, 3 | "cSpell.words": [ 4 | "Ollama" 5 | ] 6 | } -------------------------------------------------------------------------------- /macos/.gitignore: -------------------------------------------------------------------------------- 1 | # Flutter-related 2 | **/Flutter/ephemeral/ 3 | **/Pods/ 4 | 5 | # Xcode-related 6 | **/dgph 7 | **/xcuserdata/ 8 | -------------------------------------------------------------------------------- /lib/Models/ollama_request_state.dart: -------------------------------------------------------------------------------- 1 | enum OllamaRequestState { 2 | error, 3 | loading, 4 | success, 5 | uninitialized, 6 | } 7 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /assets/images/splash/splash_dark_android12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonofagl1tch/reins/main/assets/images/splash/splash_dark_android12.png -------------------------------------------------------------------------------- /assets/images/splash/splash_light_android12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonofagl1tch/reins/main/assets/images/splash/splash_light_android12.png -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-hdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonofagl1tch/reins/main/android/app/src/main/res/drawable-hdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-mdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonofagl1tch/reins/main/android/app/src/main/res/drawable-mdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonofagl1tch/reins/main/android/app/src/main/res/drawable/background.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonofagl1tch/reins/main/android/app/src/main/res/drawable-v21/background.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonofagl1tch/reins/main/android/app/src/main/res/drawable-xhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonofagl1tch/reins/main/android/app/src/main/res/drawable-xxhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxxhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonofagl1tch/reins/main/android/app/src/main/res/drawable-xxxhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonofagl1tch/reins/main/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonofagl1tch/reins/main/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /lib/Pages/settings_page/subwidgets/subwidgets.dart: -------------------------------------------------------------------------------- 1 | export 'server_settings.dart'; 2 | export 'themes_settings.dart'; 3 | export 'reins_settings.dart'; 4 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonofagl1tch/reins/main/android/app/src/main/res/drawable-night/background.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonofagl1tch/reins/main/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonofagl1tch/reins/main/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night-hdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonofagl1tch/reins/main/android/app/src/main/res/drawable-night-hdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night-mdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonofagl1tch/reins/main/android/app/src/main/res/drawable-night-mdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night-xhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonofagl1tch/reins/main/android/app/src/main/res/drawable-night-xhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night-xxhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonofagl1tch/reins/main/android/app/src/main/res/drawable-night-xxhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonofagl1tch/reins/main/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/reins.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonofagl1tch/reins/main/ios/Runner/Assets.xcassets/AppIcon.appiconset/reins.png -------------------------------------------------------------------------------- /lib/Services/services.dart: -------------------------------------------------------------------------------- 1 | export 'database_service.dart'; 2 | export 'ollama_service.dart'; 3 | export 'permission_service.dart'; 4 | export 'image_service.dart'; 5 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-hdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonofagl1tch/reins/main/android/app/src/main/res/drawable-hdpi/android12splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-mdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonofagl1tch/reins/main/android/app/src/main/res/drawable-mdpi/android12splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night-v21/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonofagl1tch/reins/main/android/app/src/main/res/drawable-night-v21/background.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night-xxxhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonofagl1tch/reins/main/android/app/src/main/res/drawable-night-xxxhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xhdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonofagl1tch/reins/main/android/app/src/main/res/drawable-xhdpi/android12splash.png -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /lib/Constants/constants.dart: -------------------------------------------------------------------------------- 1 | export 'app_constants.dart'; 2 | export 'path_manager.dart'; 3 | export 'chat_presets.dart'; 4 | export 'generate_title_constants.dart'; 5 | -------------------------------------------------------------------------------- /macos/Flutter/Flutter-Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "ephemeral/Flutter-Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/Flutter/Flutter-Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "ephemeral/Flutter-Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxhdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonofagl1tch/reins/main/android/app/src/main/res/drawable-xxhdpi/android12splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxxhdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonofagl1tch/reins/main/android/app/src/main/res/drawable-xxxhdpi/android12splash.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/reins-dark 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonofagl1tch/reins/main/ios/Runner/Assets.xcassets/AppIcon.appiconset/reins-dark 1.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/reins-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonofagl1tch/reins/main/ios/Runner/Assets.xcassets/AppIcon.appiconset/reins-dark.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night-hdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonofagl1tch/reins/main/android/app/src/main/res/drawable-night-hdpi/android12splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night-mdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonofagl1tch/reins/main/android/app/src/main/res/drawable-night-mdpi/android12splash.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonofagl1tch/reins/main/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonofagl1tch/reins/main/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonofagl1tch/reins/main/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonofagl1tch/reins/main/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonofagl1tch/reins/main/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonofagl1tch/reins/main/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonofagl1tch/reins/main/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night-xhdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonofagl1tch/reins/main/android/app/src/main/res/drawable-night-xhdpi/android12splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night-xxhdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonofagl1tch/reins/main/android/app/src/main/res/drawable-night-xxhdpi/android12splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night-xxxhdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonofagl1tch/reins/main/android/app/src/main/res/drawable-night-xxxhdpi/android12splash.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonofagl1tch/reins/main/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonofagl1tch/reins/main/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonofagl1tch/reins/main/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonofagl1tch/reins/main/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonofagl1tch/reins/main/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonofagl1tch/reins/main/ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonofagl1tch/reins/main/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonofagl1tch/reins/main/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@3x.png -------------------------------------------------------------------------------- /lib/Models/settings_route_arguments.dart: -------------------------------------------------------------------------------- 1 | class SettingsRouteArguments { 2 | final bool autoFocusServerAddress; 3 | 4 | SettingsRouteArguments({required this.autoFocusServerAddress}); 5 | } 6 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/dev/ibrahimcetin/reins/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package dev.ibrahimcetin.reins 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() 6 | -------------------------------------------------------------------------------- /devtools_options.yaml: -------------------------------------------------------------------------------- 1 | description: This file stores settings for Dart & Flutter DevTools. 2 | documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states 3 | extensions: 4 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /linux/runner/main.cc: -------------------------------------------------------------------------------- 1 | #include "my_application.h" 2 | 3 | int main(int argc, char** argv) { 4 | g_autoptr(MyApplication) app = my_application_new(); 5 | return g_application_run(G_APPLICATION(app), argc, argv); 6 | } 7 | -------------------------------------------------------------------------------- /lib/Models/ollama_exception.dart: -------------------------------------------------------------------------------- 1 | class OllamaException implements Exception { 2 | final String message; 3 | 4 | OllamaException(this.message); 5 | 6 | @override 7 | String toString() { 8 | return message; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /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.3-all.zip 6 | -------------------------------------------------------------------------------- /lib/Models/chat_preset.dart: -------------------------------------------------------------------------------- 1 | class ChatPreset { 2 | final String title; 3 | final String subtitle; 4 | final String prompt; 5 | 6 | ChatPreset({ 7 | required this.title, 8 | required this.subtitle, 9 | required this.prompt, 10 | }); 11 | } 12 | -------------------------------------------------------------------------------- /linux/flatpak/dev.ibrahimcetin.reins.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Type=Application 3 | 4 | Name=Reins 5 | Comment=Private AI Chat 6 | Categories=Utility;Development;Chat; 7 | Keywords=ai;chat;private;ollama;llm 8 | 9 | Icon=dev.ibrahimcetin.reins 10 | Exec=reins 11 | Terminal=false 12 | StartupNotify=true -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /macos/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /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 | 15 | **/.cxx -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /windows/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral/ 2 | 3 | # Visual Studio user-specific files. 4 | *.suo 5 | *.user 6 | *.userosscache 7 | *.sln.docstates 8 | 9 | # Visual Studio build-related files. 10 | x64/ 11 | x86/ 12 | 13 | # Visual Studio cache files 14 | # files ending in .cache can be ignored 15 | *.[Cc]ache 16 | # but keep track of directories ending in .cache 17 | !*.[Cc]ache/ 18 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include 11 | 12 | // Registers Flutter plugins. 13 | void fl_register_plugins(FlPluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include 11 | 12 | // Registers Flutter plugins. 13 | void RegisterPlugins(flutter::PluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /lib/Pages/chat_page/subwidgets/subwidgets.dart: -------------------------------------------------------------------------------- 1 | export 'chat_list_view.dart'; 2 | export 'chat_empty.dart'; 3 | export 'chat_select_model_button.dart'; 4 | export 'chat_welcome.dart'; 5 | export 'chat_text_field.dart'; 6 | export 'chat_error.dart'; 7 | 8 | export 'chat_attachment/chat_attachment_row.dart'; 9 | export 'chat_attachment/chat_attachment_image.dart'; 10 | export 'chat_attachment/chat_attachment_preset.dart'; 11 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /lib/Constants/app_constants.dart: -------------------------------------------------------------------------------- 1 | class AppConstants { 2 | static const String appName = 'Reins'; 3 | static const String appIconPng = 'assets/images/reins.png'; 4 | static const String appIconSvg = 'assets/images/reins.svg'; 5 | 6 | static const String ollamaIconPng = 'assets/images/ollama.png'; 7 | } 8 | 9 | class NotificationNames { 10 | static const String generationBegin = "generation_begin_notification"; 11 | } 12 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /macos/Runner/Release.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-only 8 | 9 | com.apple.security.network.client 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /lib/Models/chat_configure_arguments.dart: -------------------------------------------------------------------------------- 1 | import 'package:reins/Models/ollama_chat.dart'; 2 | 3 | class ChatConfigureArguments { 4 | String? systemPrompt; 5 | OllamaChatOptions chatOptions; 6 | 7 | ChatConfigureArguments({ 8 | required this.systemPrompt, 9 | required this.chatOptions, 10 | }); 11 | 12 | static get defaultArguments => ChatConfigureArguments( 13 | systemPrompt: null, 14 | chatOptions: OllamaChatOptions(), 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /linux/runner/my_application.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUTTER_MY_APPLICATION_H_ 2 | #define FLUTTER_MY_APPLICATION_H_ 3 | 4 | #include 5 | 6 | G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, 7 | GtkApplication) 8 | 9 | /** 10 | * my_application_new: 11 | * 12 | * Creates a new Flutter-based application. 13 | * 14 | * Returns: a new #MyApplication. 15 | */ 16 | MyApplication* my_application_new(); 17 | 18 | #endif // FLUTTER_MY_APPLICATION_H_ 19 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchBackground.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "background.png", 5 | "idiom" : "universal" 6 | }, 7 | { 8 | "appearances" : [ 9 | { 10 | "appearance" : "luminosity", 11 | "value" : "dark" 12 | } 13 | ], 14 | "filename" : "darkbackground.png", 15 | "idiom" : "universal" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /windows/runner/resource.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Visual C++ generated include file. 3 | // Used by Runner.rc 4 | // 5 | #define IDI_APP_ICON 101 6 | 7 | // Next default values for new objects 8 | // 9 | #ifdef APSTUDIO_INVOKED 10 | #ifndef APSTUDIO_READONLY_SYMBOLS 11 | #define _APS_NEXT_RESOURCE_VALUE 102 12 | #define _APS_NEXT_COMMAND_VALUE 40001 13 | #define _APS_NEXT_CONTROL_VALUE 1001 14 | #define _APS_NEXT_SYMED_VALUE 101 15 | #endif 16 | #endif 17 | -------------------------------------------------------------------------------- /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.files.user-selected.read-only 10 | 11 | com.apple.security.network.client 12 | 13 | com.apple.security.network.server 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /lib/Widgets/title_divider.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class TitleDivider extends StatelessWidget { 4 | final String title; 5 | 6 | const TitleDivider({super.key, required this.title}); 7 | 8 | @override 9 | Widget build(BuildContext context) { 10 | return Row( 11 | children: [ 12 | Expanded(child: Divider()), 13 | Padding( 14 | padding: EdgeInsets.symmetric(horizontal: 8), 15 | child: Text(title), 16 | ), 17 | Expanded(child: Divider()) 18 | ], 19 | ); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/Services/permission_service.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'package:flutter/material.dart' show VoidCallback; 3 | import 'package:permission_handler/permission_handler.dart'; 4 | 5 | class PermissionService { 6 | Future requestPhotoPermission({ 7 | VoidCallback? onDenied, 8 | }) async { 9 | if (!Platform.isIOS) return true; 10 | 11 | final status = await Permission.photos 12 | .onDeniedCallback(onDenied ?? () {}) 13 | .onPermanentlyDeniedCallback(onDenied ?? () {}) 14 | .request(); 15 | 16 | return status.isGranted || status.isLimited; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lib/Utils/material_color_adapter.dart: -------------------------------------------------------------------------------- 1 | import 'package:hive/hive.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class MaterialColorAdapter extends TypeAdapter { 5 | @override 6 | final typeId = 0; 7 | 8 | @override 9 | MaterialColor read(BinaryReader reader) { 10 | final value = reader.readInt(); 11 | return Colors.primaries.firstWhere( 12 | (color) => color.value == value, 13 | orElse: () => Colors.grey, 14 | ); 15 | } 16 | 17 | @override 18 | void write(BinaryWriter writer, MaterialColor obj) { 19 | writer.writeInt(obj.value); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /windows/runner/runner.exe.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PerMonitorV2 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /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 = reins 9 | 10 | // The application's bundle identifier 11 | PRODUCT_BUNDLE_IDENTIFIER = dev.ibrahimcetin.reins 12 | 13 | // The copyright displayed in application information 14 | PRODUCT_COPYRIGHT = Copyright © 2024 dev.ibrahimcetin. All rights reserved. 15 | -------------------------------------------------------------------------------- /lib/Pages/chat_page/subwidgets/chat_select_model_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ChatSelectModelButton extends StatelessWidget { 4 | final String? currentModelName; 5 | final void Function() onPressed; 6 | 7 | const ChatSelectModelButton({ 8 | super.key, 9 | this.currentModelName, 10 | required this.onPressed, 11 | }); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return TextButton.icon( 16 | icon: const Icon(Icons.auto_awesome_outlined), 17 | label: Text(currentModelName ?? 'Select a model to start'), 18 | iconAlignment: IconAlignment.end, 19 | onPressed: onPressed, 20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/Constants/path_manager.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:path_provider/path_provider.dart'; 4 | 5 | class PathManager { 6 | static final PathManager _instance = PathManager._internal(); 7 | late final Directory documentsDirectory; 8 | 9 | PathManager._internal(); 10 | 11 | static Future initialize() async { 12 | if (Platform.isLinux) { 13 | final directory = await getApplicationSupportDirectory(); 14 | _instance.documentsDirectory = directory; 15 | } else { 16 | final directory = await getApplicationDocumentsDirectory(); 17 | _instance.documentsDirectory = directory; 18 | } 19 | } 20 | 21 | static PathManager get instance => _instance; 22 | } 23 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /windows/runner/utils.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_UTILS_H_ 2 | #define RUNNER_UTILS_H_ 3 | 4 | #include 5 | #include 6 | 7 | // Creates a console for the process, and redirects stdout and stderr to 8 | // it for both the runner and the Flutter library. 9 | void CreateAndAttachConsole(); 10 | 11 | // Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string 12 | // encoded in UTF-8. Returns an empty std::string on failure. 13 | std::string Utf8FromUtf16(const wchar_t* utf16_string); 14 | 15 | // Gets the command line arguments passed in as a std::vector, 16 | // encoded in UTF-8. Returns an empty std::vector on failure. 17 | std::vector GetCommandLineArguments(); 18 | 19 | #endif // RUNNER_UTILS_H_ 20 | -------------------------------------------------------------------------------- /macos/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - flutter_image_compress_macos (1.0.0): 3 | - FlutterMacOS 4 | - FlutterMacOS (1.0.0) 5 | 6 | DEPENDENCIES: 7 | - flutter_image_compress_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_image_compress_macos/macos`) 8 | - FlutterMacOS (from `Flutter/ephemeral`) 9 | 10 | EXTERNAL SOURCES: 11 | flutter_image_compress_macos: 12 | :path: Flutter/ephemeral/.symlinks/plugins/flutter_image_compress_macos/macos 13 | FlutterMacOS: 14 | :path: Flutter/ephemeral 15 | 16 | SPEC CHECKSUMS: 17 | flutter_image_compress_macos: c26c3c13ea0f28ae6dea4e139b3292e7729f99f1 18 | FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1 19 | 20 | PODFILE CHECKSUM: 9ebaf0ce3d369aaa26a9ea0e159195ed94724cf3 21 | 22 | COCOAPODS: 1.16.2 23 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "reins", 9 | "request": "launch", 10 | "type": "dart" 11 | }, 12 | { 13 | "name": "reins (profile mode)", 14 | "request": "launch", 15 | "type": "dart", 16 | "flutterMode": "profile" 17 | }, 18 | { 19 | "name": "reins (release mode)", 20 | "request": "launch", 21 | "type": "dart", 22 | "flutterMode": "release" 23 | } 24 | ] 25 | } -------------------------------------------------------------------------------- /linux/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | #include 10 | #include 11 | 12 | void fl_register_plugins(FlPluginRegistry* registry) { 13 | g_autoptr(FlPluginRegistrar) file_selector_linux_registrar = 14 | fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin"); 15 | file_selector_plugin_register_with_registrar(file_selector_linux_registrar); 16 | g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = 17 | fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); 18 | url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); 19 | } 20 | -------------------------------------------------------------------------------- /lib/Widgets/ollama_bottom_sheet_header.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:reins/Constants/constants.dart'; 3 | 4 | class OllamaBottomSheetHeader extends StatelessWidget { 5 | final String title; 6 | 7 | const OllamaBottomSheetHeader({super.key, required this.title}); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return Row( 12 | children: [ 13 | Padding( 14 | padding: const EdgeInsets.all(16.0), 15 | child: ClipRRect( 16 | borderRadius: BorderRadius.circular(8.0), 17 | child: Image.asset(AppConstants.appIconPng, height: 48), 18 | ), 19 | ), 20 | Text( 21 | title, 22 | style: TextStyle(fontWeight: FontWeight.bold), 23 | ), 24 | ], 25 | ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /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.1.0" apply false 22 | id "org.jetbrains.kotlin.android" version "1.8.22" apply false 23 | } 24 | 25 | include ":app" 26 | -------------------------------------------------------------------------------- /lib/Pages/chat_page/subwidgets/chat_attachment/chat_attachment_row.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ChatAttachmentRow extends StatelessWidget { 4 | final int itemCount; 5 | final IndexedWidgetBuilder itemBuilder; 6 | 7 | const ChatAttachmentRow({ 8 | super.key, 9 | required this.itemCount, 10 | required this.itemBuilder, 11 | }); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return SingleChildScrollView( 16 | scrollDirection: Axis.horizontal, 17 | padding: const EdgeInsets.symmetric(horizontal: 8.0), 18 | physics: const ClampingScrollPhysics(), 19 | child: Row( 20 | spacing: 8.0, 21 | children: List.generate( 22 | itemCount, 23 | (index) => itemBuilder(context, index), 24 | ), 25 | ), 26 | ); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .build/ 9 | .buildlog/ 10 | .history 11 | .svn/ 12 | .swiftpm/ 13 | migrate_working_dir/ 14 | 15 | # IntelliJ related 16 | *.iml 17 | *.ipr 18 | *.iws 19 | .idea/ 20 | 21 | # The .vscode folder contains launch configuration and tasks you configure in 22 | # VS Code which you may wish to be included in version control, so this line 23 | # is commented out by default. 24 | #.vscode/ 25 | 26 | # Flutter/Dart/Pub related 27 | **/doc/api/ 28 | **/ios/Flutter/.last_build_id 29 | .dart_tool/ 30 | .flutter-plugins 31 | .flutter-plugins-dependencies 32 | .pub-cache/ 33 | .pub/ 34 | /build/ 35 | 36 | # Symbolication related 37 | app.*.symbols 38 | 39 | # Obfuscation related 40 | app.*.map.json 41 | 42 | # Android Studio will place build artifacts here 43 | /android/app/debug 44 | /android/app/profile 45 | /android/app/release 46 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | file_selector_linux 7 | url_launcher_linux 8 | ) 9 | 10 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 11 | ) 12 | 13 | set(PLUGIN_BUNDLED_LIBRARIES) 14 | 15 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 16 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) 17 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 18 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 19 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 20 | endforeach(plugin) 21 | 22 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 23 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) 24 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 25 | endforeach(ffi_plugin) 26 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "reins.png", 5 | "idiom" : "universal", 6 | "platform" : "ios", 7 | "size" : "1024x1024" 8 | }, 9 | { 10 | "appearances" : [ 11 | { 12 | "appearance" : "luminosity", 13 | "value" : "dark" 14 | } 15 | ], 16 | "filename" : "reins-dark.png", 17 | "idiom" : "universal", 18 | "platform" : "ios", 19 | "size" : "1024x1024" 20 | }, 21 | { 22 | "appearances" : [ 23 | { 24 | "appearance" : "luminosity", 25 | "value" : "tinted" 26 | } 27 | ], 28 | "filename" : "reins-dark 1.png", 29 | "idiom" : "universal", 30 | "platform" : "ios", 31 | "size" : "1024x1024" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lib/Utils/observe_size.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/rendering.dart'; 3 | 4 | class ObserveSize extends SingleChildRenderObjectWidget { 5 | final Function(Size?, Size) onSizeChanged; 6 | 7 | const ObserveSize({ 8 | super.key, 9 | required this.onSizeChanged, 10 | required super.child, 11 | }); 12 | 13 | @override 14 | RenderObject createRenderObject(BuildContext context) { 15 | return _RenderObserveSize(onSizeChanged); 16 | } 17 | } 18 | 19 | class _RenderObserveSize extends RenderProxyBox { 20 | final Function(Size?, Size) onSizeChanged; 21 | 22 | _RenderObserveSize(this.onSizeChanged); 23 | 24 | Size? _previousSize; 25 | 26 | @override 27 | void performLayout() { 28 | super.performLayout(); 29 | 30 | final newSize = (child?.size ?? size); 31 | 32 | onSizeChanged(_previousSize, newSize); 33 | _previousSize = newSize; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | file_selector_windows 7 | permission_handler_windows 8 | share_plus 9 | url_launcher_windows 10 | ) 11 | 12 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 13 | ) 14 | 15 | set(PLUGIN_BUNDLED_LIBRARIES) 16 | 17 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 18 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) 19 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 20 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 21 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 22 | endforeach(plugin) 23 | 24 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 25 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) 26 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 27 | endforeach(ffi_plugin) 28 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | void RegisterPlugins(flutter::PluginRegistry* registry) { 15 | FileSelectorWindowsRegisterWithRegistrar( 16 | registry->GetRegistrarForPlugin("FileSelectorWindows")); 17 | PermissionHandlerWindowsPluginRegisterWithRegistrar( 18 | registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); 19 | SharePlusWindowsPluginCApiRegisterWithRegistrar( 20 | registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi")); 21 | UrlLauncherWindowsRegisterWithRegistrar( 22 | registry->GetRegistrarForPlugin("UrlLauncherWindows")); 23 | } 24 | -------------------------------------------------------------------------------- /lib/Pages/chat_page/subwidgets/chat_empty.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_svg/svg.dart'; 3 | import 'package:reins/Constants/constants.dart'; 4 | 5 | class ChatEmpty extends StatelessWidget { 6 | final Widget child; 7 | 8 | const ChatEmpty({super.key, required this.child}); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return Center( 13 | child: SingleChildScrollView( 14 | physics: NeverScrollableScrollPhysics(), 15 | child: Column( 16 | mainAxisAlignment: MainAxisAlignment.center, 17 | children: [ 18 | SvgPicture.asset( 19 | AppConstants.appIconSvg, 20 | height: 48, 21 | colorFilter: ColorFilter.mode( 22 | Theme.of(context).colorScheme.onSurface, 23 | BlendMode.srcIn, 24 | ), 25 | ), 26 | child, 27 | ], 28 | ), 29 | ), 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lib/Widgets/chat_image.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ChatImage extends StatelessWidget { 4 | final ImageProvider image; 5 | final double aspectRatio; 6 | final double? height; 7 | final double? width; 8 | 9 | const ChatImage({ 10 | super.key, 11 | required this.image, 12 | this.aspectRatio = 1.0, 13 | this.height, 14 | this.width, 15 | }); 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | return SizedBox( 20 | height: height, 21 | width: width, 22 | child: ClipRRect( 23 | borderRadius: BorderRadius.circular(8.0), 24 | child: AspectRatio( 25 | aspectRatio: aspectRatio, 26 | child: Image( 27 | image: image, 28 | fit: BoxFit.cover, 29 | errorBuilder: (context, error, stackTrace) { 30 | return Center( 31 | child: Icon(Icons.error, color: Colors.red), 32 | ); 33 | }, 34 | ), 35 | ), 36 | ), 37 | ); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reins", 3 | "short_name": "reins", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "A new Flutter project.", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | }, 22 | { 23 | "src": "icons/Icon-maskable-192.png", 24 | "sizes": "192x192", 25 | "type": "image/png", 26 | "purpose": "maskable" 27 | }, 28 | { 29 | "src": "icons/Icon-maskable-512.png", 30 | "sizes": "512x512", 31 | "type": "image/png", 32 | "purpose": "maskable" 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /windows/runner/flutter_window.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_FLUTTER_WINDOW_H_ 2 | #define RUNNER_FLUTTER_WINDOW_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "win32_window.h" 10 | 11 | // A window that does nothing but host a Flutter view. 12 | class FlutterWindow : public Win32Window { 13 | public: 14 | // Creates a new FlutterWindow hosting a Flutter view running |project|. 15 | explicit FlutterWindow(const flutter::DartProject& project); 16 | virtual ~FlutterWindow(); 17 | 18 | protected: 19 | // Win32Window: 20 | bool OnCreate() override; 21 | void OnDestroy() override; 22 | LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, 23 | LPARAM const lparam) noexcept override; 24 | 25 | private: 26 | // The project to run. 27 | flutter::DartProject project_; 28 | 29 | // The Flutter instance hosted by this window. 30 | std::unique_ptr flutter_controller_; 31 | }; 32 | 33 | #endif // RUNNER_FLUTTER_WINDOW_H_ 34 | -------------------------------------------------------------------------------- /linux/runner/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13) 2 | project(runner LANGUAGES CXX) 3 | 4 | # Define the application target. To change its name, change BINARY_NAME in the 5 | # top-level CMakeLists.txt, not the value here, or `flutter run` will no longer 6 | # work. 7 | # 8 | # Any new source files that you add to the application should be added here. 9 | add_executable(${BINARY_NAME} 10 | "main.cc" 11 | "my_application.cc" 12 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" 13 | ) 14 | 15 | # Apply the standard set of build settings. This can be removed for applications 16 | # that need different build settings. 17 | apply_standard_settings(${BINARY_NAME}) 18 | 19 | # Add preprocessor definitions for the application ID. 20 | add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") 21 | 22 | # Add dependency libraries. Add any application-specific dependencies here. 23 | target_link_libraries(${BINARY_NAME} PRIVATE flutter) 24 | target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) 25 | 26 | target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") 27 | -------------------------------------------------------------------------------- /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 flutter_image_compress_macos 10 | import in_app_review 11 | import path_provider_foundation 12 | import share_plus 13 | import sqflite_darwin 14 | import url_launcher_macos 15 | 16 | func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { 17 | FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) 18 | FlutterImageCompressMacosPlugin.register(with: registry.registrar(forPlugin: "FlutterImageCompressMacosPlugin")) 19 | InAppReviewPlugin.register(with: registry.registrar(forPlugin: "InAppReviewPlugin")) 20 | PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) 21 | SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) 22 | SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) 23 | UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) 24 | } 25 | -------------------------------------------------------------------------------- /lib/Pages/chat_page/subwidgets/chat_attachment/chat_attachment_image.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:reins/Widgets/chat_image.dart'; 5 | 6 | class ChatAttachmentImage extends StatelessWidget { 7 | final File imageFile; 8 | final Function(File) onRemove; 9 | 10 | const ChatAttachmentImage({ 11 | super.key, 12 | required this.imageFile, 13 | required this.onRemove, 14 | }); 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | return Stack( 19 | children: [ 20 | ChatImage( 21 | image: FileImage(imageFile), 22 | height: MediaQuery.of(context).size.height * 0.15, 23 | ), 24 | Positioned( 25 | top: 2, 26 | right: 2, 27 | child: InkWell( 28 | onTap: () => onRemove(imageFile), 29 | child: Icon( 30 | Icons.close, 31 | color: Colors.white, 32 | shadows: [BoxShadow(blurRadius: 10)], 33 | ), 34 | ), 35 | ), 36 | ], 37 | ); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /lib/Services/ollama_modelfile_generator.dart: -------------------------------------------------------------------------------- 1 | import '../Models/ollama_chat.dart'; 2 | import '../Models/ollama_message.dart'; 3 | 4 | class OllamaModelfileGenerator { 5 | static final Map defaultOptions = 6 | OllamaChatOptions().toMap(); 7 | 8 | Future generate(OllamaChat chat, List messages) async { 9 | final buffer = StringBuffer(); 10 | 11 | // Write FROM instruction 12 | buffer.writeln('FROM ${chat.model}'); 13 | 14 | // Write SYSTEM message if available 15 | if (chat.systemPrompt != null) { 16 | buffer.writeln('SYSTEM """${chat.systemPrompt}"""'); 17 | } 18 | 19 | // Write PARAMETERS 20 | chat.options.toMap().forEach((key, value) { 21 | if (defaultOptions[key] != value) { 22 | buffer.writeln('PARAMETER $key $value'); 23 | } 24 | }); 25 | 26 | // Write MESSAGE history 27 | for (var message in messages) { 28 | final role = message.role.toCaseString(); 29 | final content = message.content; 30 | buffer.writeln('MESSAGE $role $content'); 31 | } 32 | 33 | return buffer.toString(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/Pages/chat_page/subwidgets/chat_attachment/chat_attachment_preset.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:reins/Models/chat_preset.dart'; 3 | 4 | class ChatAttachmentPreset extends StatelessWidget { 5 | final ChatPreset preset; 6 | final Function() onPressed; 7 | 8 | const ChatAttachmentPreset({ 9 | super.key, 10 | required this.preset, 11 | required this.onPressed, 12 | }); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return InkWell( 17 | onTap: onPressed, 18 | borderRadius: BorderRadius.circular(16), 19 | child: Ink( 20 | decoration: BoxDecoration( 21 | color: Theme.of(context).colorScheme.surfaceContainer, 22 | borderRadius: BorderRadius.circular(16), 23 | ), 24 | padding: EdgeInsets.all(16), 25 | child: Column( 26 | mainAxisSize: MainAxisSize.min, 27 | crossAxisAlignment: CrossAxisAlignment.start, 28 | children: [ 29 | Text(preset.title, style: Theme.of(context).textTheme.titleSmall), 30 | Text( 31 | preset.subtitle, 32 | style: Theme.of(context).textTheme.bodyMedium, 33 | ), 34 | ], 35 | ), 36 | ), 37 | ); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /lib/Pages/chat_page/subwidgets/chat_error.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ChatError extends StatelessWidget { 4 | final String message; 5 | final void Function() onRetry; 6 | 7 | const ChatError({ 8 | super.key, 9 | required this.message, 10 | required this.onRetry, 11 | }); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return Container( 16 | decoration: BoxDecoration( 17 | border: Border.all( 18 | color: Theme.of(context).colorScheme.error, 19 | ), 20 | borderRadius: BorderRadius.circular(10.0), 21 | ), 22 | padding: EdgeInsets.all(10.0), 23 | margin: EdgeInsets.all(10.0), 24 | child: Column( 25 | crossAxisAlignment: CrossAxisAlignment.stretch, 26 | children: [ 27 | Text( 28 | message, 29 | style: TextStyle(color: Theme.of(context).colorScheme.error), 30 | ), 31 | const SizedBox(height: 10.0), 32 | FilledButton( 33 | onPressed: onRetry, 34 | style: FilledButton.styleFrom( 35 | backgroundColor: Theme.of(context).colorScheme.error, 36 | ), 37 | child: Text('Retry'), 38 | ), 39 | ], 40 | ), 41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /android/app/src/main/res/values-v31/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 18 | 21 | 22 | -------------------------------------------------------------------------------- /android/app/src/main/res/values-night-v31/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 18 | 21 | 22 | -------------------------------------------------------------------------------- /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 | ITSAppUsesNonExemptEncryption 24 | 25 | LSApplicationCategoryType 26 | public.app-category.developer-tools 27 | LSMinimumSystemVersion 28 | $(MACOSX_DEPLOYMENT_TARGET) 29 | NSHumanReadableCopyright 30 | $(PRODUCT_COPYRIGHT) 31 | NSMainNibFile 32 | MainMenu 33 | NSPrincipalClass 34 | NSApplication 35 | 36 | 37 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "LaunchImage.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "appearances" : [ 10 | { 11 | "appearance" : "luminosity", 12 | "value" : "dark" 13 | } 14 | ], 15 | "filename" : "LaunchImageDark.png", 16 | "idiom" : "universal", 17 | "scale" : "1x" 18 | }, 19 | { 20 | "filename" : "LaunchImage@2x.png", 21 | "idiom" : "universal", 22 | "scale" : "2x" 23 | }, 24 | { 25 | "appearances" : [ 26 | { 27 | "appearance" : "luminosity", 28 | "value" : "dark" 29 | } 30 | ], 31 | "filename" : "LaunchImageDark@2x.png", 32 | "idiom" : "universal", 33 | "scale" : "2x" 34 | }, 35 | { 36 | "filename" : "LaunchImage@3x.png", 37 | "idiom" : "universal", 38 | "scale" : "3x" 39 | }, 40 | { 41 | "appearances" : [ 42 | { 43 | "appearance" : "luminosity", 44 | "value" : "dark" 45 | } 46 | ], 47 | "filename" : "LaunchImageDark@3x.png", 48 | "idiom" : "universal", 49 | "scale" : "3x" 50 | } 51 | ], 52 | "info" : { 53 | "author" : "xcode", 54 | "version" : 1 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | reins 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 19 | 22 | 23 | -------------------------------------------------------------------------------- /android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 19 | 22 | 23 | -------------------------------------------------------------------------------- /lib/Pages/main_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:reins/Pages/chat_page/chat_page.dart'; 3 | import 'package:reins/Widgets/chat_app_bar.dart'; 4 | import 'package:reins/Widgets/chat_drawer.dart'; 5 | import 'package:responsive_framework/responsive_framework.dart'; 6 | 7 | class ReinsMainPage extends StatelessWidget { 8 | const ReinsMainPage({super.key}); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | if (ResponsiveBreakpoints.of(context).isMobile) { 13 | return _ReinsMobileMainPage(); 14 | } else { 15 | return _ReinsLargeMainPage(); 16 | } 17 | } 18 | } 19 | 20 | class _ReinsMobileMainPage extends StatelessWidget { 21 | const _ReinsMobileMainPage({super.key}); 22 | 23 | @override 24 | Widget build(BuildContext context) { 25 | return const Scaffold( 26 | appBar: ChatAppBar(), 27 | body: SafeArea(child: ChatPage()), 28 | drawer: ChatDrawer(), 29 | ); 30 | } 31 | } 32 | 33 | class _ReinsLargeMainPage extends StatelessWidget { 34 | const _ReinsLargeMainPage({super.key}); 35 | 36 | @override 37 | Widget build(BuildContext context) { 38 | return const Scaffold( 39 | body: SafeArea( 40 | child: Row( 41 | children: [ 42 | ChatDrawer(), 43 | Expanded(child: ChatPage()), 44 | ], 45 | ), 46 | ), 47 | ); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /lib/Pages/settings_page/settings_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:google_fonts/google_fonts.dart'; 3 | import 'package:reins/Models/settings_route_arguments.dart'; 4 | 5 | import 'subwidgets/subwidgets.dart'; 6 | 7 | class SettingsPage extends StatelessWidget { 8 | final SettingsRouteArguments? arguments; 9 | 10 | const SettingsPage({super.key, this.arguments}); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | return Scaffold( 15 | appBar: AppBar( 16 | title: Text('Settings', style: GoogleFonts.pacifico()), 17 | ), 18 | body: SafeArea( 19 | child: _SettingsPageContent(arguments: arguments), 20 | ), 21 | ); 22 | } 23 | } 24 | 25 | class _SettingsPageContent extends StatelessWidget { 26 | final SettingsRouteArguments? arguments; 27 | 28 | const _SettingsPageContent({required this.arguments}); 29 | 30 | @override 31 | Widget build(BuildContext context) { 32 | return ListView( 33 | physics: const BouncingScrollPhysics(), 34 | padding: const EdgeInsets.all(16), 35 | children: [ 36 | ThemesSettings(), 37 | SizedBox(height: 16), 38 | ServerSettings( 39 | autoFocusServerAddress: arguments?.autoFocusServerAddress ?? false, 40 | ), 41 | SizedBox(height: 16), 42 | ReinsSettings(), 43 | ], 44 | ); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /windows/runner/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "flutter_window.h" 6 | #include "utils.h" 7 | 8 | int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, 9 | _In_ wchar_t *command_line, _In_ int show_command) { 10 | // Attach to console when present (e.g., 'flutter run') or create a 11 | // new console when running with a debugger. 12 | if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { 13 | CreateAndAttachConsole(); 14 | } 15 | 16 | // Initialize COM, so that it is available for use in the library and/or 17 | // plugins. 18 | ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); 19 | 20 | flutter::DartProject project(L"data"); 21 | 22 | std::vector command_line_arguments = 23 | GetCommandLineArguments(); 24 | 25 | project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); 26 | 27 | FlutterWindow window(project); 28 | Win32Window::Point origin(10, 10); 29 | Win32Window::Size size(1280, 720); 30 | if (!window.Create(L"reins", origin, size)) { 31 | return EXIT_FAILURE; 32 | } 33 | window.SetQuitOnClose(true); 34 | 35 | ::MSG msg; 36 | while (::GetMessage(&msg, nullptr, 0, 0)) { 37 | ::TranslateMessage(&msg); 38 | ::DispatchMessage(&msg); 39 | } 40 | 41 | ::CoUninitialize(); 42 | return EXIT_SUCCESS; 43 | } 44 | -------------------------------------------------------------------------------- /lib/Constants/generate_title_constants.dart: -------------------------------------------------------------------------------- 1 | class GenerateTitleConstants { 2 | static const String systemPrompt = 3 | "You are a title generator for a chat application. Your task is to create a concise and descriptive title based on the user's first message in a chat. The title should capture the main topic or intent of the message while being engaging and informative. Avoid generic titles like 'Chat' or 'Conversation.' Keep the title under 5 words. For example: If the first message is 'What's the weather like today in Paris?' the title should be 'Weather in Paris.' If the first message is 'Can you help me with my homework?' the title could be 'Homework Help.' If the first message is 'Tell me a story about a dragon,' the title could be 'Dragon Story Request.' Always focus on the essence of the message and aim for clarity and relevance."; 4 | 5 | static const String prompt = 6 | "You are a title generator for a chat application. Your task is to create a concise and descriptive title based on the user's first message in a chat. Respond with the title only—do not include any additional words, explanations, or phrases. The title should be no more than 5 words and capture the main topic of the user's message. For example: Input: 'What's the weather in Paris?' → Output: 'Weather in Paris' Input: 'Tell me a story about a dragon.' → Output: 'Dragon Story' Input: 'Plan a trip to Italy.' → Output: 'Italy Trip Plan'. Now generate a title for this message: "; 7 | } 8 | -------------------------------------------------------------------------------- /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 | include: package:flutter_lints/flutter.yaml 11 | 12 | linter: 13 | # The lint rules applied to this project can be customized in the 14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml` 15 | # included above or to enable additional rules. A list of all available lints 16 | # and their documentation is published at https://dart.dev/lints. 17 | # 18 | # Instead of disabling a lint rule for the entire project in the 19 | # section below, it can also be suppressed for a single line of code 20 | # or a specific dart file by using the `// ignore: name_of_lint` and 21 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 22 | # producing the lint. 23 | rules: 24 | # avoid_print: false # Uncomment to disable the `avoid_print` rule 25 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 26 | 27 | # Additional information about this file can be found at 28 | # https://dart.dev/guides/language/analysis-options 29 | -------------------------------------------------------------------------------- /lib/Utils/retained_position_scroll_physics.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class WidgetSizeProxy { 4 | double deltaHeight = 0.0; 5 | } 6 | 7 | class RetainedPositionScrollPhysics extends ScrollPhysics { 8 | const RetainedPositionScrollPhysics({ 9 | super.parent, 10 | required this.widgetSizeProxy, 11 | }); 12 | 13 | final WidgetSizeProxy widgetSizeProxy; 14 | 15 | @override 16 | ScrollPhysics applyTo(ScrollPhysics? ancestor) { 17 | return RetainedPositionScrollPhysics( 18 | parent: ancestor, 19 | widgetSizeProxy: widgetSizeProxy, 20 | ); 21 | } 22 | 23 | @override 24 | double adjustPositionForNewDimensions({ 25 | required ScrollMetrics oldPosition, 26 | required ScrollMetrics newPosition, 27 | required bool isScrolling, 28 | required double velocity, 29 | }) { 30 | final adjustPosition = super.adjustPositionForNewDimensions( 31 | oldPosition: oldPosition, 32 | newPosition: newPosition, 33 | isScrolling: isScrolling, 34 | velocity: velocity, 35 | ); 36 | 37 | if (adjustPosition <= 44) { 38 | // 44 is just a threshold to adjust the position when the user scrolls to the bottom 39 | // if the user scrolls to the bottom, the adjustPosition is 0 40 | // so we need to return the original position 41 | return adjustPosition; 42 | } else { 43 | // Add the delta height to keep the scroll position stable 44 | return adjustPosition + widgetSizeProxy.deltaHeight; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /lib/Pages/chat_page/subwidgets/chat_bubble/chat_bubble_bottom_sheet.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ChatBubbleBottomSheet extends StatelessWidget { 4 | final String title; 5 | final Widget child; 6 | final List actions; 7 | 8 | const ChatBubbleBottomSheet({ 9 | super.key, 10 | required this.title, 11 | required this.child, 12 | this.actions = const [], 13 | }); 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return Scaffold( 18 | appBar: AppBar( 19 | title: Text(title), 20 | titleTextStyle: Theme.of(context) 21 | .textTheme 22 | .titleMedium 23 | ?.copyWith(fontWeight: FontWeight.bold), 24 | forceMaterialTransparency: true, 25 | automaticallyImplyLeading: false, 26 | actions: [ 27 | IconButton( 28 | onPressed: () { 29 | Navigator.pop(context); 30 | }, 31 | icon: const Icon(Icons.close), 32 | ), 33 | ], 34 | ), 35 | body: SafeArea( 36 | child: Column( 37 | children: [ 38 | Expanded( 39 | child: Padding( 40 | padding: const EdgeInsets.symmetric(horizontal: 16), 41 | child: child, 42 | ), 43 | ), 44 | Row( 45 | mainAxisAlignment: MainAxisAlignment.end, 46 | children: actions, 47 | ), 48 | ], 49 | ), 50 | ), 51 | ); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /lib/Utils/border_painter.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class BorderPainter extends CustomPainter { 4 | final Color? color; 5 | final Gradient? gradient; 6 | final double strokeWidth; 7 | final Radius borderRadius; 8 | final EdgeInsets padding; 9 | 10 | BorderPainter({ 11 | this.color, 12 | this.gradient, 13 | this.borderRadius = const Radius.circular(0.0), 14 | this.padding = EdgeInsets.zero, 15 | this.strokeWidth = 1.0, 16 | }); 17 | 18 | @override 19 | void paint(Canvas canvas, Size size) { 20 | final rect = Offset.zero & size; 21 | 22 | final paint = Paint() 23 | ..style = PaintingStyle.stroke 24 | ..strokeWidth = strokeWidth; 25 | 26 | if (color != null) { 27 | paint.color = color!; 28 | } else if (gradient != null) { 29 | paint.shader = gradient!.createShader(rect); 30 | } 31 | 32 | final borderRect = RRect.fromRectAndRadius( 33 | Rect.fromLTWH( 34 | strokeWidth / 2 + padding.left, 35 | strokeWidth / 2 + padding.top, 36 | size.width - strokeWidth - padding.horizontal, 37 | size.height - strokeWidth - padding.vertical, 38 | ), 39 | borderRadius, 40 | ); 41 | 42 | canvas.drawRRect(borderRect, paint); 43 | } 44 | 45 | @override 46 | bool shouldRepaint(BorderPainter oldDelegate) => 47 | color != oldDelegate.color || 48 | gradient != oldDelegate.gradient || 49 | borderRadius != oldDelegate.borderRadius || 50 | padding != oldDelegate.padding || 51 | strokeWidth != oldDelegate.strokeWidth; 52 | } 53 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/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 | 44 | target.build_configurations.each do |config| 45 | config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [ 46 | '$(inherited)', 47 | ## dart: PermissionGroup.photos 48 | 'PERMISSION_PHOTOS=1', 49 | ] 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /lib/Utils/request_review_helper.dart: -------------------------------------------------------------------------------- 1 | import 'package:hive/hive.dart'; 2 | 3 | final class RequestReviewHelper { 4 | /// The number of times the app has been launched 5 | int _launchCount = 0; 6 | get launchCount => _launchCount; 7 | 8 | /// The last time a review request was made 9 | DateTime? _lastReviewRequest; 10 | get _isReviewReminderDue => 11 | _lastReviewRequest == null || 12 | DateTime.now().difference(_lastReviewRequest!) >= Duration(days: 3); 13 | 14 | /// Private constructor 15 | RequestReviewHelper._internal(); 16 | 17 | // Singleton instance 18 | static final RequestReviewHelper _instance = RequestReviewHelper._internal(); 19 | 20 | // Factory constructor to return the singleton instance 21 | static RequestReviewHelper get instance => _instance; 22 | 23 | // Hive box 24 | late final Box _box; 25 | 26 | // Initialize the singleton instance from Hive 27 | static Future initialize() async { 28 | final box = await Hive.openBox('reviewBox'); 29 | 30 | _instance._launchCount = box.get('launchCount', defaultValue: 0); 31 | _instance._lastReviewRequest = box.get('lastReviewRequest'); 32 | 33 | _instance._box = box; 34 | 35 | return _instance; 36 | } 37 | 38 | Future incrementCount({bool isLaunch = false}) async { 39 | if (isLaunch) { 40 | _launchCount++; 41 | } 42 | 43 | await save(); 44 | } 45 | 46 | bool shouldRequestReview() { 47 | final shouldRequestReview = _launchCount >= 10 && _isReviewReminderDue; 48 | 49 | if (shouldRequestReview) { 50 | _lastReviewRequest = DateTime.now(); 51 | save(); 52 | } 53 | 54 | return shouldRequestReview; 55 | } 56 | 57 | Future save() async { 58 | await _box.put('launchCount', _launchCount); 59 | await _box.put('lastReviewRequest', _lastReviewRequest); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /.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: "8495dee1fd4aacbe9de707e7581203232f591b2f" 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: 8495dee1fd4aacbe9de707e7581203232f591b2f 17 | base_revision: 8495dee1fd4aacbe9de707e7581203232f591b2f 18 | - platform: android 19 | create_revision: 8495dee1fd4aacbe9de707e7581203232f591b2f 20 | base_revision: 8495dee1fd4aacbe9de707e7581203232f591b2f 21 | - platform: ios 22 | create_revision: 8495dee1fd4aacbe9de707e7581203232f591b2f 23 | base_revision: 8495dee1fd4aacbe9de707e7581203232f591b2f 24 | - platform: linux 25 | create_revision: 8495dee1fd4aacbe9de707e7581203232f591b2f 26 | base_revision: 8495dee1fd4aacbe9de707e7581203232f591b2f 27 | - platform: macos 28 | create_revision: 8495dee1fd4aacbe9de707e7581203232f591b2f 29 | base_revision: 8495dee1fd4aacbe9de707e7581203232f591b2f 30 | - platform: web 31 | create_revision: 8495dee1fd4aacbe9de707e7581203232f591b2f 32 | base_revision: 8495dee1fd4aacbe9de707e7581203232f591b2f 33 | - platform: windows 34 | create_revision: 8495dee1fd4aacbe9de707e7581203232f591b2f 35 | base_revision: 8495dee1fd4aacbe9de707e7581203232f591b2f 36 | 37 | # User provided section 38 | 39 | # List of Local paths (relative to this file) that should be 40 | # ignored by the migrate tool. 41 | # 42 | # Files that are not part of the templates will be ignored by default. 43 | unmanaged_files: 44 | - 'lib/main.dart' 45 | - 'ios/Runner.xcodeproj/project.pbxproj' 46 | -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "version": 1, 4 | "author": "xcode" 5 | }, 6 | "images": [ 7 | { 8 | "size": "16x16", 9 | "idiom": "mac", 10 | "filename": "app_icon_16.png", 11 | "scale": "1x" 12 | }, 13 | { 14 | "size": "16x16", 15 | "idiom": "mac", 16 | "filename": "app_icon_32.png", 17 | "scale": "2x" 18 | }, 19 | { 20 | "size": "32x32", 21 | "idiom": "mac", 22 | "filename": "app_icon_32.png", 23 | "scale": "1x" 24 | }, 25 | { 26 | "size": "32x32", 27 | "idiom": "mac", 28 | "filename": "app_icon_64.png", 29 | "scale": "2x" 30 | }, 31 | { 32 | "size": "128x128", 33 | "idiom": "mac", 34 | "filename": "app_icon_128.png", 35 | "scale": "1x" 36 | }, 37 | { 38 | "size": "128x128", 39 | "idiom": "mac", 40 | "filename": "app_icon_256.png", 41 | "scale": "2x" 42 | }, 43 | { 44 | "size": "256x256", 45 | "idiom": "mac", 46 | "filename": "app_icon_256.png", 47 | "scale": "1x" 48 | }, 49 | { 50 | "size": "256x256", 51 | "idiom": "mac", 52 | "filename": "app_icon_512.png", 53 | "scale": "2x" 54 | }, 55 | { 56 | "size": "512x512", 57 | "idiom": "mac", 58 | "filename": "app_icon_512.png", 59 | "scale": "1x" 60 | }, 61 | { 62 | "size": "512x512", 63 | "idiom": "mac", 64 | "filename": "app_icon_1024.png", 65 | "scale": "2x" 66 | } 67 | ] 68 | } -------------------------------------------------------------------------------- /windows/runner/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | project(runner LANGUAGES CXX) 3 | 4 | # Define the application target. To change its name, change BINARY_NAME in the 5 | # top-level CMakeLists.txt, not the value here, or `flutter run` will no longer 6 | # work. 7 | # 8 | # Any new source files that you add to the application should be added here. 9 | add_executable(${BINARY_NAME} WIN32 10 | "flutter_window.cpp" 11 | "main.cpp" 12 | "utils.cpp" 13 | "win32_window.cpp" 14 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" 15 | "Runner.rc" 16 | "runner.exe.manifest" 17 | ) 18 | 19 | # Apply the standard set of build settings. This can be removed for applications 20 | # that need different build settings. 21 | apply_standard_settings(${BINARY_NAME}) 22 | 23 | # Add preprocessor definitions for the build version. 24 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") 25 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") 26 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") 27 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") 28 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") 29 | 30 | # Disable Windows macros that collide with C++ standard library functions. 31 | target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") 32 | 33 | # Add dependency libraries and include directories. Add any application-specific 34 | # dependencies here. 35 | target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) 36 | target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") 37 | target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") 38 | 39 | # Run the Flutter tool portions of the build. This must not be removed. 40 | add_dependencies(${BINARY_NAME} flutter_assemble) 41 | -------------------------------------------------------------------------------- /lib/Pages/chat_page/subwidgets/chat_bubble/chat_bubble_menu.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:reins/Utils/border_painter.dart'; 3 | 4 | class ChatBubbleMenu extends StatefulWidget { 5 | final Widget child; 6 | final List menuChildren; 7 | 8 | const ChatBubbleMenu({ 9 | super.key, 10 | required this.child, 11 | required this.menuChildren, 12 | }); 13 | 14 | @override 15 | State createState() => _ChatBubbleMenuState(); 16 | } 17 | 18 | class _ChatBubbleMenuState extends State { 19 | @override 20 | Widget build(BuildContext context) { 21 | return MenuAnchor( 22 | menuChildren: widget.menuChildren, 23 | builder: (context, controller, child) { 24 | return GestureDetector( 25 | onTap: () => controller.close(), 26 | onLongPressStart: (details) { 27 | controller.open(position: details.localPosition); 28 | }, 29 | onDoubleTapDown: (details) { 30 | if (controller.isOpen) { 31 | controller.close(); 32 | } else { 33 | controller.open(position: details.localPosition); 34 | } 35 | }, 36 | onSecondaryTapDown: (details) { 37 | controller.open(position: details.localPosition); 38 | }, 39 | child: CustomPaint( 40 | foregroundPainter: BorderPainter( 41 | color: controller.isOpen 42 | ? Theme.of(context).colorScheme.primaryContainer 43 | : Theme.of(context).colorScheme.surface, 44 | borderRadius: Radius.circular(10.0), 45 | strokeWidth: 2, 46 | padding: EdgeInsets.symmetric(horizontal: 10.0), 47 | ), 48 | child: child, 49 | ), 50 | ); 51 | }, 52 | child: widget.child, 53 | onOpen: () => setState(() {}), 54 | onClose: () => setState(() {}), 55 | ); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /windows/runner/utils.cpp: -------------------------------------------------------------------------------- 1 | #include "utils.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | void CreateAndAttachConsole() { 11 | if (::AllocConsole()) { 12 | FILE *unused; 13 | if (freopen_s(&unused, "CONOUT$", "w", stdout)) { 14 | _dup2(_fileno(stdout), 1); 15 | } 16 | if (freopen_s(&unused, "CONOUT$", "w", stderr)) { 17 | _dup2(_fileno(stdout), 2); 18 | } 19 | std::ios::sync_with_stdio(); 20 | FlutterDesktopResyncOutputStreams(); 21 | } 22 | } 23 | 24 | std::vector GetCommandLineArguments() { 25 | // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. 26 | int argc; 27 | wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); 28 | if (argv == nullptr) { 29 | return std::vector(); 30 | } 31 | 32 | std::vector command_line_arguments; 33 | 34 | // Skip the first argument as it's the binary name. 35 | for (int i = 1; i < argc; i++) { 36 | command_line_arguments.push_back(Utf8FromUtf16(argv[i])); 37 | } 38 | 39 | ::LocalFree(argv); 40 | 41 | return command_line_arguments; 42 | } 43 | 44 | std::string Utf8FromUtf16(const wchar_t* utf16_string) { 45 | if (utf16_string == nullptr) { 46 | return std::string(); 47 | } 48 | unsigned int target_length = ::WideCharToMultiByte( 49 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 50 | -1, nullptr, 0, nullptr, nullptr) 51 | -1; // remove the trailing null character 52 | int input_length = (int)wcslen(utf16_string); 53 | std::string utf8_string; 54 | if (target_length == 0 || target_length > utf8_string.max_size()) { 55 | return utf8_string; 56 | } 57 | utf8_string.resize(target_length); 58 | int converted_length = ::WideCharToMultiByte( 59 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 60 | input_length, utf8_string.data(), target_length, nullptr, nullptr); 61 | if (converted_length == 0) { 62 | return std::string(); 63 | } 64 | return utf8_string; 65 | } 66 | -------------------------------------------------------------------------------- /ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Flutter (1.0.0) 3 | - flutter_image_compress_common (1.0.0): 4 | - Flutter 5 | - Mantle 6 | - SDWebImage 7 | - SDWebImageWebPCoder 8 | - libwebp (1.3.2): 9 | - libwebp/demux (= 1.3.2) 10 | - libwebp/mux (= 1.3.2) 11 | - libwebp/sharpyuv (= 1.3.2) 12 | - libwebp/webp (= 1.3.2) 13 | - libwebp/demux (1.3.2): 14 | - libwebp/webp 15 | - libwebp/mux (1.3.2): 16 | - libwebp/demux 17 | - libwebp/sharpyuv (1.3.2) 18 | - libwebp/webp (1.3.2): 19 | - libwebp/sharpyuv 20 | - Mantle (2.2.0): 21 | - Mantle/extobjc (= 2.2.0) 22 | - Mantle/extobjc (2.2.0) 23 | - permission_handler_apple (9.3.0): 24 | - Flutter 25 | - SDWebImage (5.20.0): 26 | - SDWebImage/Core (= 5.20.0) 27 | - SDWebImage/Core (5.20.0) 28 | - SDWebImageWebPCoder (0.14.6): 29 | - libwebp (~> 1.0) 30 | - SDWebImage/Core (~> 5.17) 31 | 32 | DEPENDENCIES: 33 | - Flutter (from `Flutter`) 34 | - flutter_image_compress_common (from `.symlinks/plugins/flutter_image_compress_common/ios`) 35 | - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) 36 | 37 | SPEC REPOS: 38 | trunk: 39 | - libwebp 40 | - Mantle 41 | - SDWebImage 42 | - SDWebImageWebPCoder 43 | 44 | EXTERNAL SOURCES: 45 | Flutter: 46 | :path: Flutter 47 | flutter_image_compress_common: 48 | :path: ".symlinks/plugins/flutter_image_compress_common/ios" 49 | permission_handler_apple: 50 | :path: ".symlinks/plugins/permission_handler_apple/ios" 51 | 52 | SPEC CHECKSUMS: 53 | Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 54 | flutter_image_compress_common: ec1d45c362c9d30a3f6a0426c297f47c52007e3e 55 | libwebp: 1786c9f4ff8a279e4dac1e8f385004d5fc253009 56 | Mantle: c5aa8794a29a022dfbbfc9799af95f477a69b62d 57 | permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2 58 | SDWebImage: 73c6079366fea25fa4bb9640d5fb58f0893facd8 59 | SDWebImageWebPCoder: e38c0a70396191361d60c092933e22c20d5b1380 60 | 61 | PODFILE CHECKSUM: c14ad0b77e0b5ac1474cc8d50a0f4fa11850857a 62 | 63 | COCOAPODS: 1.16.2 64 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CADisableMinimumFrameDurationOnPhone 6 | 7 | CFBundleDevelopmentRegion 8 | $(DEVELOPMENT_LANGUAGE) 9 | CFBundleDisplayName 10 | Reins 11 | CFBundleExecutable 12 | $(EXECUTABLE_NAME) 13 | CFBundleIdentifier 14 | $(PRODUCT_BUNDLE_IDENTIFIER) 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | CFBundleName 18 | reins 19 | CFBundlePackageType 20 | APPL 21 | CFBundleShortVersionString 22 | $(FLUTTER_BUILD_NAME) 23 | CFBundleSignature 24 | ???? 25 | CFBundleVersion 26 | $(FLUTTER_BUILD_NUMBER) 27 | ITSAppUsesNonExemptEncryption 28 | 29 | LSRequiresIPhoneOS 30 | 31 | NSPhotoLibraryUsageDescription 32 | Reins needs access to your photo library to let you select images to send in chats. 33 | UIApplicationSupportsIndirectInputEvents 34 | 35 | UILaunchStoryboardName 36 | LaunchScreen 37 | UIMainStoryboardFile 38 | Main 39 | UIStatusBarHidden 40 | 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 | -------------------------------------------------------------------------------- /lib/Pages/chat_page/chat_page_view_model.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'dart:ui'; 3 | 4 | import 'package:image_picker/image_picker.dart'; 5 | import 'package:reins/Services/services.dart'; 6 | 7 | class ChatPageViewModel { 8 | final OllamaService _ollamaService; 9 | final DatabaseService _databaseService; 10 | final PermissionService _permissionService; 11 | final ImageService _imageService; 12 | 13 | ChatPageViewModel({ 14 | required OllamaService ollamaService, 15 | required DatabaseService databaseService, 16 | required PermissionService permissionService, 17 | required ImageService imageService, 18 | }) : _ollamaService = ollamaService, 19 | _databaseService = databaseService, 20 | _permissionService = permissionService, 21 | _imageService = imageService; 22 | 23 | /// Handles image picking and compression 24 | Future> pickImages({ 25 | VoidCallback? onPermissionDenied, 26 | int quality = 10, 27 | }) async { 28 | // Check permissions 29 | final hasPermission = await _permissionService.requestPhotoPermission( 30 | onDenied: onPermissionDenied, 31 | ); 32 | if (!hasPermission) return []; 33 | 34 | // Pick images 35 | final picker = ImagePicker(); 36 | final pickedImage = await picker.pickImage( 37 | source: ImageSource.gallery, 38 | ); 39 | // await _picker.pickMultiImage(limit: maxImages); 40 | 41 | if (pickedImage == null) return []; 42 | 43 | // Compress and save 44 | final compressedFile = await _imageService.compressAndSave( 45 | pickedImage.path, 46 | quality: quality, 47 | ); 48 | 49 | // Add an empty path if the image could not be compressed to show error 50 | return compressedFile != null ? [compressedFile] : [File('')]; 51 | } 52 | 53 | /// Deletes a single image 54 | Future deleteImage(File imageFile) async { 55 | await _imageService.deleteImage(imageFile); 56 | } 57 | 58 | /// Deletes multiple images 59 | Future deleteImages(List imageFiles) async { 60 | await _imageService.deleteImages(imageFiles); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "com.android.application" 3 | id "kotlin-android" 4 | // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. 5 | id "dev.flutter.flutter-gradle-plugin" 6 | } 7 | 8 | def keystoreProperties = new Properties() 9 | def keystorePropertiesFile = rootProject.file('key.properties') 10 | if (keystorePropertiesFile.exists()) { 11 | keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) 12 | } 13 | 14 | android { 15 | namespace = "dev.ibrahimcetin.reins" 16 | compileSdk = flutter.compileSdkVersion 17 | ndkVersion = flutter.ndkVersion 18 | 19 | compileOptions { 20 | sourceCompatibility = JavaVersion.VERSION_1_8 21 | targetCompatibility = JavaVersion.VERSION_1_8 22 | } 23 | 24 | kotlinOptions { 25 | jvmTarget = JavaVersion.VERSION_1_8 26 | } 27 | 28 | defaultConfig { 29 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 30 | applicationId = "dev.ibrahimcetin.reins" 31 | // You can update the following values to match your application needs. 32 | // For more information, see: https://flutter.dev/to/review-gradle-config. 33 | minSdk = flutter.minSdkVersion 34 | targetSdk = flutter.targetSdkVersion 35 | versionCode = flutter.versionCode 36 | versionName = flutter.versionName 37 | } 38 | 39 | signingConfigs { 40 | release { 41 | keyAlias = keystoreProperties['keyAlias'] 42 | keyPassword = keystoreProperties['keyPassword'] 43 | storeFile = keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null 44 | storePassword = keystoreProperties['storePassword'] 45 | } 46 | } 47 | 48 | buildTypes { 49 | release { 50 | // TODO: Add your own signing config for the release build. 51 | // Signing with the debug keys for now, so `flutter run --release` works. 52 | signingConfig = signingConfigs.release 53 | } 54 | } 55 | } 56 | 57 | flutter { 58 | source = "../.." 59 | } 60 | -------------------------------------------------------------------------------- /windows/runner/flutter_window.cpp: -------------------------------------------------------------------------------- 1 | #include "flutter_window.h" 2 | 3 | #include 4 | 5 | #include "flutter/generated_plugin_registrant.h" 6 | 7 | FlutterWindow::FlutterWindow(const flutter::DartProject& project) 8 | : project_(project) {} 9 | 10 | FlutterWindow::~FlutterWindow() {} 11 | 12 | bool FlutterWindow::OnCreate() { 13 | if (!Win32Window::OnCreate()) { 14 | return false; 15 | } 16 | 17 | RECT frame = GetClientArea(); 18 | 19 | // The size here must match the window dimensions to avoid unnecessary surface 20 | // creation / destruction in the startup path. 21 | flutter_controller_ = std::make_unique( 22 | frame.right - frame.left, frame.bottom - frame.top, project_); 23 | // Ensure that basic setup of the controller was successful. 24 | if (!flutter_controller_->engine() || !flutter_controller_->view()) { 25 | return false; 26 | } 27 | RegisterPlugins(flutter_controller_->engine()); 28 | SetChildContent(flutter_controller_->view()->GetNativeWindow()); 29 | 30 | flutter_controller_->engine()->SetNextFrameCallback([&]() { 31 | this->Show(); 32 | }); 33 | 34 | // Flutter can complete the first frame before the "show window" callback is 35 | // registered. The following call ensures a frame is pending to ensure the 36 | // window is shown. It is a no-op if the first frame hasn't completed yet. 37 | flutter_controller_->ForceRedraw(); 38 | 39 | return true; 40 | } 41 | 42 | void FlutterWindow::OnDestroy() { 43 | if (flutter_controller_) { 44 | flutter_controller_ = nullptr; 45 | } 46 | 47 | Win32Window::OnDestroy(); 48 | } 49 | 50 | LRESULT 51 | FlutterWindow::MessageHandler(HWND hwnd, UINT const message, 52 | WPARAM const wparam, 53 | LPARAM const lparam) noexcept { 54 | // Give Flutter, including plugins, an opportunity to handle window messages. 55 | if (flutter_controller_) { 56 | std::optional result = 57 | flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, 58 | lparam); 59 | if (result) { 60 | return *result; 61 | } 62 | } 63 | 64 | switch (message) { 65 | case WM_FONTCHANGE: 66 | flutter_controller_->engine()->ReloadSystemFonts(); 67 | break; 68 | } 69 | 70 | return Win32Window::MessageHandler(hwnd, message, wparam, lparam); 71 | } 72 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 15 | 19 | 23 | 24 | 25 | 26 | 27 | 28 | 30 | 33 | 34 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /lib/Models/ollama_model.dart: -------------------------------------------------------------------------------- 1 | class OllamaModel { 2 | String name; 3 | String model; 4 | DateTime modifiedAt; 5 | int size; 6 | String digest; 7 | OllamaModelDetails details; 8 | 9 | OllamaModel({ 10 | required this.name, 11 | required this.model, 12 | required this.modifiedAt, 13 | required this.size, 14 | required this.digest, 15 | required this.details, 16 | }); 17 | 18 | factory OllamaModel.fromJson(Map json) => OllamaModel( 19 | name: json["name"], 20 | model: json["model"], 21 | modifiedAt: DateTime.parse(json["modified_at"]), 22 | size: json["size"], 23 | digest: json["digest"], 24 | details: OllamaModelDetails.fromJson(json["details"]), 25 | ); 26 | 27 | Map toJson() => { 28 | "name": name, 29 | "model": model, 30 | "modified_at": modifiedAt.toIso8601String(), 31 | "size": size, 32 | "digest": digest, 33 | "details": details.toJson(), 34 | }; 35 | 36 | @override 37 | String toString() { 38 | return name; 39 | } 40 | 41 | @override 42 | int get hashCode => digest.hashCode; 43 | 44 | @override 45 | bool operator ==(Object other) { 46 | if (identical(this, other)) return true; 47 | 48 | return other is OllamaModel && other.digest == digest; 49 | } 50 | } 51 | 52 | class OllamaModelDetails { 53 | String parentModel; 54 | String format; 55 | String family; 56 | List? families; 57 | String parameterSize; 58 | String quantizationLevel; 59 | 60 | OllamaModelDetails({ 61 | required this.parentModel, 62 | required this.format, 63 | required this.family, 64 | required this.families, 65 | required this.parameterSize, 66 | required this.quantizationLevel, 67 | }); 68 | 69 | factory OllamaModelDetails.fromJson(Map json) => 70 | OllamaModelDetails( 71 | parentModel: json["parent_model"], 72 | format: json["format"], 73 | family: json["family"], 74 | families: json["families"] != null 75 | ? List.from(json["families"].map((x) => x)) 76 | : null, 77 | parameterSize: json["parameter_size"], 78 | quantizationLevel: json["quantization_level"], 79 | ); 80 | 81 | Map toJson() => { 82 | "parent_model": parentModel, 83 | "format": format, 84 | "family": family, 85 | "families": families != null 86 | ? List.from(families!.map((x) => x)) 87 | : null, 88 | "parameter_size": parameterSize, 89 | "quantization_level": quantizationLevel, 90 | }; 91 | } 92 | -------------------------------------------------------------------------------- /lib/Pages/chat_page/subwidgets/chat_bubble/chat_bubble_think_block.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:markdown/markdown.dart' as md; 3 | import 'package:flutter_markdown/flutter_markdown.dart'; 4 | 5 | class ThinkBlockSyntax extends md.BlockSyntax { 6 | @override 7 | RegExp get pattern => RegExp(r'^$'); 8 | 9 | @override 10 | bool canEndBlock(md.BlockParser parser) => false; 11 | 12 | const ThinkBlockSyntax(); 13 | 14 | @override 15 | List parseChildLines(md.BlockParser parser) { 16 | final childLines = []; 17 | 18 | parser.advance(); // Advance past the opening tag 19 | 20 | while (!parser.isDone) { 21 | if (parser.current.content == '') { 22 | parser.advance(); // Advance past the closing tag 23 | break; 24 | } 25 | 26 | childLines.add(parser.current); 27 | parser.advance(); 28 | } 29 | 30 | return childLines; 31 | } 32 | 33 | @override 34 | md.Node parse(md.BlockParser parser) { 35 | final childLines = parseChildLines(parser); 36 | 37 | var content = childLines.map((e) => e.content).join('\n'); 38 | 39 | return md.Element('pre', [md.Element.text('think', content)]); 40 | } 41 | } 42 | 43 | class ThinkBlockBuilder extends MarkdownElementBuilder { 44 | @override 45 | Widget visitElementAfter(md.Element element, TextStyle? preferredStyle) { 46 | return ThinkBlockWidget(content: element.textContent); 47 | } 48 | } 49 | 50 | class ThinkBlockWidget extends StatefulWidget { 51 | final String content; 52 | 53 | const ThinkBlockWidget({super.key, required this.content}); 54 | 55 | @override 56 | State createState() => _ThinkBlockWidgetState(); 57 | } 58 | 59 | class _ThinkBlockWidgetState extends State { 60 | bool _showingThought = true; 61 | 62 | @override 63 | Widget build(BuildContext context) { 64 | return Column( 65 | crossAxisAlignment: CrossAxisAlignment.start, 66 | children: [ 67 | InkWell( 68 | onTap: () { 69 | setState(() => _showingThought = !_showingThought); 70 | }, 71 | child: Row( 72 | children: [ 73 | Text('Thought', style: TextStyle(color: _thoughtColor)), 74 | Icon(_thoughtButtonIcon, color: _thoughtColor), 75 | ], 76 | ), 77 | ), 78 | if (_showingThought) 79 | SelectableText(widget.content, 80 | style: TextStyle(color: _thoughtColor)), 81 | ], 82 | ); 83 | } 84 | 85 | IconData get _thoughtButtonIcon => 86 | _showingThought ? Icons.keyboard_arrow_down : Icons.keyboard_arrow_up; 87 | 88 | Color get _thoughtColor => Theme.of(context).colorScheme.secondary; 89 | } 90 | -------------------------------------------------------------------------------- /lib/Pages/chat_page/subwidgets/chat_bubble/chat_bubble_image.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'dart:math'; 3 | 4 | import 'package:flutter/material.dart'; 5 | import 'package:photo_view/photo_view.dart'; 6 | import 'package:reins/Widgets/chat_image.dart'; 7 | 8 | class ChatBubbleImage extends StatelessWidget { 9 | final File imageFile; 10 | 11 | const ChatBubbleImage({super.key, required this.imageFile}); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return GestureDetector( 16 | onTap: () { 17 | Navigator.of(context).push( 18 | PageRouteBuilder( 19 | pageBuilder: (context, animation, secondaryAnimation) { 20 | return _ChatBubbleImageFullScreen(imageFile: imageFile); 21 | }, 22 | transitionsBuilder: (context, animation, _, child) { 23 | return FadeTransition(opacity: animation, child: child); 24 | }, 25 | ), 26 | ); 27 | }, 28 | child: Hero( 29 | tag: imageFile.path, 30 | child: ChatImage( 31 | image: FileImage(imageFile), 32 | aspectRatio: 1.5, 33 | width: max( 34 | MediaQuery.of(context).size.width * 0.35, 35 | MediaQuery.of(context).size.height * 0.25, 36 | ), 37 | ), 38 | ), 39 | ); 40 | } 41 | } 42 | 43 | class _ChatBubbleImageFullScreen extends StatelessWidget { 44 | const _ChatBubbleImageFullScreen({required this.imageFile}); 45 | 46 | final File imageFile; 47 | 48 | @override 49 | Widget build(BuildContext context) { 50 | return Scaffold( 51 | body: SafeArea( 52 | child: Stack( 53 | children: [ 54 | Center( 55 | child: PhotoView( 56 | imageProvider: FileImage(imageFile), 57 | errorBuilder: (context, error, stackTrace) { 58 | return Center( 59 | child: Icon(Icons.error, color: Colors.red), 60 | ); 61 | }, 62 | backgroundDecoration: BoxDecoration( 63 | color: Colors.transparent, 64 | ), 65 | heroAttributes: PhotoViewHeroAttributes( 66 | tag: imageFile.path, 67 | ), 68 | ), 69 | ), 70 | Positioned( 71 | top: 5, 72 | right: 0, 73 | child: IconButton( 74 | icon: Icon( 75 | Icons.close, 76 | color: Colors.white, 77 | shadows: [BoxShadow(blurRadius: 10)], 78 | ), 79 | onPressed: () => Navigator.pop(context), 80 | ), 81 | ), 82 | ], 83 | ), 84 | ), 85 | ); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Reins 2 | 3 | Reins is a multi-platform, open-source, privacy-first app designed for Ollama users. **It simplifies chat configurations** with a user-friendly interface to configure system prompts, change the chat model, and adjust options for each conversation **individually**. Reins ensures a smooth, customizable experience for anyone working with self-hosted LLMs. 4 | 5 | If you like the project, don't forget to give a ⭐️! 6 | 7 | 8 | Download on the App Store 9 | 10 | 11 | 12 | Get it on Flathub 13 | 14 | 15 | You can download it for iOS and macOS on the App Store and for Linux on Flathub. 16 | 17 | You can find Android and Windows releases from [here](https://github.com/ibrahimcetin/reins/releases). 18 | 19 | ## Key Features 20 | - **Customizable Chat Configurations**: Configure system prompt, model, and options (e.g., temperature, seed, context size, max tokens) for each conversation. 21 | - **Model Selection & Switching**: Change the model of the current chat without interruption. 22 | - **Message Editing & Regeneration**: Edit and regenerate messages 23 | - **Save Custom Models**: Save system and chat prompts as new models. 24 | - **Image Integration**: Send and receive images within chats. 25 | - **Multiple Chat Management**: Easily manage and switch between multiple conversations. 26 | - **Real-Time Message Streaming**: Get messages instantly as they arrive. 27 | 28 | ## Mobile Screenshots 29 | Main 30 | Configuration 31 | Advanced Configurations 32 | Edit & Regenerate 33 | Change Current Chat Model 34 | Select Model 35 | Dark Theme 36 | 37 | ## Large Screen Screenshots 38 | Main 39 | Configuration 40 | 41 | ## Contributing 42 | Contributions are welcome! Feel free to fork the repository, make changes, and submit a pull request. 43 | 44 | ## License 45 | Reins is licensed under the GPL-3.0. 46 | -------------------------------------------------------------------------------- /lib/Pages/chat_page/subwidgets/chat_text_field.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io' show Platform; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter/services.dart'; 5 | 6 | class ChatTextField extends StatefulWidget { 7 | final TextEditingController? controller; 8 | 9 | final void Function(String)? onChanged; 10 | final void Function()? onEditingComplete; 11 | 12 | final Widget? prefixIcon; 13 | final Widget? suffixIcon; 14 | 15 | const ChatTextField({ 16 | super.key, 17 | this.controller, 18 | this.onChanged, 19 | this.onEditingComplete, 20 | this.prefixIcon, 21 | this.suffixIcon, 22 | }); 23 | 24 | @override 25 | State createState() => _ChatTextFieldState(); 26 | } 27 | 28 | class _ChatTextFieldState extends State { 29 | static final _textFieldBucket = PageStorageBucket(); 30 | 31 | @override 32 | void initState() { 33 | super.initState(); 34 | 35 | WidgetsBinding.instance.addPostFrameCallback((_) { 36 | widget.controller?.text = _readTextFieldState(); 37 | widget.onChanged?.call(widget.controller?.text ?? ''); 38 | }); 39 | } 40 | 41 | @override 42 | void deactivate() { 43 | // Write the latest text to the bucket 44 | _writeTextFieldState(widget.controller?.text ?? ''); 45 | 46 | super.deactivate(); 47 | } 48 | 49 | @override 50 | Widget build(BuildContext context) { 51 | return CallbackShortcuts( 52 | bindings: { 53 | SingleActivator(LogicalKeyboardKey.enter, shift: true): () { 54 | widget.controller?.text += '\n'; 55 | }, 56 | }, 57 | child: TextField( 58 | controller: widget.controller, 59 | onChanged: widget.onChanged, 60 | onEditingComplete: widget.onEditingComplete, 61 | decoration: InputDecoration( 62 | border: OutlineInputBorder( 63 | borderRadius: BorderRadius.circular(30.0), 64 | ), 65 | labelText: 'Prompt', 66 | prefixIcon: widget.prefixIcon, 67 | suffixIcon: widget.suffixIcon, 68 | ), 69 | minLines: 1, 70 | maxLines: 5, 71 | textCapitalization: TextCapitalization.sentences, 72 | textInputAction: _textInputAction, 73 | onTapOutside: (PointerDownEvent event) { 74 | FocusManager.instance.primaryFocus?.unfocus(); 75 | }, 76 | ), 77 | ); 78 | } 79 | 80 | TextInputAction get _textInputAction { 81 | return Platform.isIOS || Platform.isAndroid 82 | ? TextInputAction.newline 83 | : TextInputAction.send; 84 | } 85 | 86 | String _readTextFieldState() { 87 | return _textFieldBucket.readState(context, identifier: widget.key) ?? ''; 88 | } 89 | 90 | void _writeTextFieldState(String text) { 91 | if (widget.key == null) return; 92 | 93 | if (widget.key is ValueKey && (widget.key as ValueKey).value == null) { 94 | return; 95 | } 96 | 97 | _textFieldBucket.writeState(context, text, identifier: widget.key); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /linux/flutter/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file controls Flutter-level build steps. It should not be edited. 2 | cmake_minimum_required(VERSION 3.10) 3 | 4 | set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") 5 | 6 | # Configuration provided via flutter tool. 7 | include(${EPHEMERAL_DIR}/generated_config.cmake) 8 | 9 | # TODO: Move the rest of this into files in ephemeral. See 10 | # https://github.com/flutter/flutter/issues/57146. 11 | 12 | # Serves the same purpose as list(TRANSFORM ... PREPEND ...), 13 | # which isn't available in 3.10. 14 | function(list_prepend LIST_NAME PREFIX) 15 | set(NEW_LIST "") 16 | foreach(element ${${LIST_NAME}}) 17 | list(APPEND NEW_LIST "${PREFIX}${element}") 18 | endforeach(element) 19 | set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) 20 | endfunction() 21 | 22 | # === Flutter Library === 23 | # System-level dependencies. 24 | find_package(PkgConfig REQUIRED) 25 | pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) 26 | pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) 27 | pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) 28 | 29 | set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") 30 | 31 | # Published to parent scope for install step. 32 | set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) 33 | set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) 34 | set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) 35 | set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) 36 | 37 | list(APPEND FLUTTER_LIBRARY_HEADERS 38 | "fl_basic_message_channel.h" 39 | "fl_binary_codec.h" 40 | "fl_binary_messenger.h" 41 | "fl_dart_project.h" 42 | "fl_engine.h" 43 | "fl_json_message_codec.h" 44 | "fl_json_method_codec.h" 45 | "fl_message_codec.h" 46 | "fl_method_call.h" 47 | "fl_method_channel.h" 48 | "fl_method_codec.h" 49 | "fl_method_response.h" 50 | "fl_plugin_registrar.h" 51 | "fl_plugin_registry.h" 52 | "fl_standard_message_codec.h" 53 | "fl_standard_method_codec.h" 54 | "fl_string_codec.h" 55 | "fl_value.h" 56 | "fl_view.h" 57 | "flutter_linux.h" 58 | ) 59 | list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") 60 | add_library(flutter INTERFACE) 61 | target_include_directories(flutter INTERFACE 62 | "${EPHEMERAL_DIR}" 63 | ) 64 | target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") 65 | target_link_libraries(flutter INTERFACE 66 | PkgConfig::GTK 67 | PkgConfig::GLIB 68 | PkgConfig::GIO 69 | ) 70 | add_dependencies(flutter flutter_assemble) 71 | 72 | # === Flutter tool backend === 73 | # _phony_ is a non-existent file to force this command to run every time, 74 | # since currently there's no way to get a full input/output list from the 75 | # flutter tool. 76 | add_custom_command( 77 | OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} 78 | ${CMAKE_CURRENT_BINARY_DIR}/_phony_ 79 | COMMAND ${CMAKE_COMMAND} -E env 80 | ${FLUTTER_TOOL_ENVIRONMENT} 81 | "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" 82 | ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} 83 | VERBATIM 84 | ) 85 | add_custom_target(flutter_assemble DEPENDS 86 | "${FLUTTER_LIBRARY}" 87 | ${FLUTTER_LIBRARY_HEADERS} 88 | ) 89 | -------------------------------------------------------------------------------- /lib/Widgets/chat_drawer.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:reins/Constants/constants.dart'; 3 | import 'package:reins/Providers/chat_provider.dart'; 4 | import 'package:provider/provider.dart'; 5 | import 'package:responsive_framework/responsive_framework.dart'; 6 | 7 | import 'title_divider.dart'; 8 | 9 | class ChatDrawer extends StatelessWidget { 10 | const ChatDrawer({super.key}); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | return Drawer( 15 | child: SafeArea( 16 | child: Column( 17 | children: [ 18 | const Expanded(child: ChatNavigationDrawer()), 19 | Container( 20 | alignment: Alignment.centerLeft, 21 | padding: const EdgeInsets.fromLTRB(28, 16, 28, 10), 22 | child: IconButton( 23 | icon: const Icon(Icons.settings_outlined), 24 | onPressed: () { 25 | if (ResponsiveBreakpoints.of(context).isMobile) { 26 | Navigator.pop(context); 27 | } 28 | 29 | Navigator.pushNamed(context, '/settings'); 30 | }, 31 | ), 32 | ), 33 | ], 34 | ), 35 | ), 36 | ); 37 | } 38 | } 39 | 40 | class ChatNavigationDrawer extends StatelessWidget { 41 | const ChatNavigationDrawer({super.key}); 42 | 43 | @override 44 | Widget build(BuildContext context) { 45 | return Consumer( 46 | builder: (context, chatProvider, _) { 47 | return NavigationDrawer( 48 | selectedIndex: chatProvider.selectedDestination, 49 | onDestinationSelected: (destination) { 50 | chatProvider.destinationChatSelected(destination); 51 | 52 | if (ResponsiveBreakpoints.of(context).isMobile) { 53 | Navigator.pop(context); 54 | } 55 | }, 56 | children: [ 57 | Padding( 58 | padding: const EdgeInsets.fromLTRB(28, 16, 16, 10), 59 | child: Text( 60 | AppConstants.appName, 61 | style: Theme.of(context).textTheme.titleSmall, 62 | ), 63 | ), 64 | const NavigationDrawerDestination( 65 | icon: CircleAvatar( 66 | backgroundImage: AssetImage(AppConstants.ollamaIconPng), 67 | radius: 16, 68 | ), 69 | label: Text("Ollama"), 70 | ), 71 | const Padding( 72 | padding: EdgeInsets.fromLTRB(28, 16, 28, 10), 73 | child: TitleDivider(title: "Chats"), 74 | ), 75 | ...chatProvider.chats.map((chat) { 76 | return NavigationDrawerDestination( 77 | icon: const Icon(Icons.chat_outlined), 78 | label: Expanded( 79 | child: Text( 80 | chat.title, 81 | overflow: TextOverflow.ellipsis, 82 | ), 83 | ), 84 | selectedIcon: const Icon(Icons.chat), 85 | ); 86 | }), 87 | ], 88 | ); 89 | }, 90 | ); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /lib/Services/image_service.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'package:flutter_image_compress/flutter_image_compress.dart'; 3 | import 'package:image_compression/image_compression.dart' as img_compress; 4 | import 'package:path/path.dart' as path; 5 | import 'package:reins/Constants/constants.dart'; 6 | 7 | /// Handles all image storage and compression operations 8 | class ImageService { 9 | Future getImagesDirectory() async { 10 | final documentsDirectory = PathManager.instance.documentsDirectory; 11 | final imagesPath = path.join(documentsDirectory.path, 'images'); 12 | return await Directory(imagesPath).create(recursive: true); 13 | } 14 | 15 | Future compressAndSave(String sourcePath, {int quality = 10}) async { 16 | try { 17 | final imagesDir = await getImagesDirectory(); 18 | final targetPath = path.join( 19 | imagesDir.path, 20 | '${DateTime.now().microsecondsSinceEpoch}.jpg', 21 | ); 22 | 23 | return await _compressAndSaveImageForPlatform( 24 | sourcePath, 25 | targetPath, 26 | quality: quality, 27 | ); 28 | } catch (e) { 29 | return null; 30 | } 31 | } 32 | 33 | Future _compressAndSaveImageForPlatform( 34 | String sourcePath, 35 | String targetPath, { 36 | int quality = 10, 37 | }) async { 38 | Function(String, String, {int quality}) function; 39 | 40 | if (Platform.isLinux) { 41 | function = _compressAndSaveImageLinux; 42 | } else { 43 | function = _compressAndSaveImage; 44 | } 45 | 46 | return function( 47 | sourcePath, 48 | targetPath, 49 | quality: quality, 50 | ); 51 | } 52 | 53 | Future _compressAndSaveImage( 54 | String sourcePath, 55 | String targetPath, { 56 | int quality = 10, 57 | }) async { 58 | final compressed = await FlutterImageCompress.compressAndGetFile( 59 | sourcePath, 60 | targetPath, 61 | quality: quality, 62 | ); 63 | 64 | return compressed != null ? File(compressed.path) : null; 65 | } 66 | 67 | Future _compressAndSaveImageLinux( 68 | String sourcePath, 69 | String targetPath, { 70 | int quality = 10, 71 | }) async { 72 | final sourceFile = File(sourcePath); 73 | if (!await sourceFile.exists()) return null; 74 | 75 | final inputImage = img_compress.ImageFile( 76 | filePath: sourcePath, 77 | rawBytes: await sourceFile.readAsBytes(), 78 | ); 79 | 80 | final compressedImage = await img_compress.compressInQueue( 81 | img_compress.ImageFileConfiguration( 82 | input: inputImage, 83 | config: img_compress.Configuration(jpgQuality: quality), 84 | ), 85 | ); 86 | 87 | return await File(targetPath).writeAsBytes(compressedImage.rawBytes); 88 | } 89 | 90 | Future deleteImage(File imageFile) async { 91 | if (await imageFile.exists()) { 92 | await imageFile.delete(); 93 | } 94 | } 95 | 96 | Future deleteImages(List imageFiles) async { 97 | await Future.wait(imageFiles.map((file) => deleteImage(file))); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /lib/Pages/settings_page/subwidgets/reins_settings.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:share_plus/share_plus.dart'; 3 | import 'package:url_launcher/url_launcher_string.dart'; 4 | import 'package:in_app_review/in_app_review.dart'; 5 | import 'dart:io' show Platform; 6 | 7 | class ReinsSettings extends StatelessWidget { 8 | const ReinsSettings({super.key}); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return Column( 13 | crossAxisAlignment: CrossAxisAlignment.start, 14 | children: [ 15 | Text( 16 | 'Reins', 17 | style: Theme.of(context).textTheme.titleLarge?.copyWith( 18 | fontWeight: FontWeight.bold, 19 | ), 20 | ), 21 | ListTile( 22 | leading: Icon(Icons.rate_review), 23 | title: Text('Review Reins'), 24 | subtitle: Text('Share your feedback'), 25 | onTap: () async { 26 | if (await InAppReview.instance.isAvailable() && Platform.isIOS) { 27 | InAppReview.instance.openStoreListing(appStoreId: "6739738501"); 28 | } else { 29 | launchUrlString('https://github.com/ibrahimcetin/reins'); 30 | } 31 | }, 32 | ), 33 | ListTile( 34 | leading: Icon(Icons.share), 35 | title: Text('Share Reins'), 36 | subtitle: Text('Share Reins with your friends'), 37 | onTap: () { 38 | Share.share( 39 | 'Check out Reins: https://github.com/ibrahimcetin/reins', 40 | ); 41 | }, 42 | ), 43 | if (Platform.isAndroid || Platform.isIOS) 44 | ListTile( 45 | leading: Icon(Icons.desktop_mac_outlined), 46 | title: Text('Try Desktop App'), 47 | subtitle: Text('Available on macOS and Windows'), 48 | onTap: () { 49 | launchUrlString('https://github.com/ibrahimcetin/reins/releases'); 50 | }, 51 | ), 52 | if (Platform.isMacOS || Platform.isLinux || Platform.isWindows) 53 | ListTile( 54 | leading: Icon(Icons.phone_iphone_outlined), 55 | title: Text('Try Mobile App'), 56 | subtitle: Text('Available on Android and iOS'), 57 | onTap: () { 58 | launchUrlString('https://github.com/ibrahimcetin/reins'); 59 | }, 60 | ), 61 | ListTile( 62 | leading: Icon(Icons.code), 63 | title: Text('Go to Source Code'), 64 | subtitle: Text('View on GitHub'), 65 | onTap: () { 66 | launchUrlString('https://github.com/ibrahimcetin/reins'); 67 | }, 68 | ), 69 | ListTile( 70 | leading: Icon(Icons.star), 71 | title: Text('Give a Star on GitHub'), 72 | subtitle: Text('Support the project'), 73 | onTap: () { 74 | launchUrlString('https://github.com/ibrahimcetin/reins'); 75 | }, 76 | ), 77 | Row( 78 | mainAxisAlignment: MainAxisAlignment.center, 79 | spacing: 5, 80 | children: [ 81 | Icon(Icons.favorite, color: Colors.red, size: 16), 82 | Text("Thanks for using Reins!"), 83 | ], 84 | ), 85 | ], 86 | ); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /lib/Pages/chat_page/subwidgets/chat_welcome.dart: -------------------------------------------------------------------------------- 1 | import 'package:animated_text_kit/animated_text_kit.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:reins/Models/settings_route_arguments.dart'; 4 | 5 | class ChatWelcome extends StatelessWidget { 6 | final CrossFadeState showingState; 7 | 8 | final void Function()? onFirstChildFinished; 9 | 10 | final double secondChildScale; 11 | final void Function()? onSecondChildScaleEnd; 12 | 13 | const ChatWelcome({ 14 | super.key, 15 | required this.showingState, 16 | this.onFirstChildFinished, 17 | required this.secondChildScale, 18 | this.onSecondChildScaleEnd, 19 | }); 20 | 21 | @override 22 | Widget build(BuildContext context) { 23 | return AnimatedCrossFade( 24 | crossFadeState: showingState, 25 | duration: const Duration(milliseconds: 150), 26 | firstChild: _ChatWelcomeText( 27 | onFinished: onFirstChildFinished, 28 | ), 29 | secondChild: AnimatedScale( 30 | scale: secondChildScale, 31 | duration: const Duration(milliseconds: 100), 32 | onEnd: onSecondChildScaleEnd, 33 | child: _ChatConfigureServerAddressButton(), 34 | ), 35 | layoutBuilder: (topChild, topChildKey, bottomChild, bottomChildKey) { 36 | return Stack( 37 | alignment: Alignment.center, 38 | children: [ 39 | Positioned( 40 | key: topChildKey, 41 | child: Padding( 42 | padding: EdgeInsets.symmetric(horizontal: 8.0), 43 | child: topChild, 44 | ), 45 | ), 46 | Positioned( 47 | key: bottomChildKey, 48 | child: Padding( 49 | padding: EdgeInsets.symmetric(horizontal: 8.0), 50 | child: bottomChild, 51 | ), 52 | ), 53 | ], 54 | ); 55 | }, 56 | ); 57 | } 58 | } 59 | 60 | class _ChatWelcomeText extends StatelessWidget { 61 | final void Function()? onFinished; 62 | 63 | const _ChatWelcomeText({this.onFinished}); 64 | 65 | @override 66 | Widget build(BuildContext context) { 67 | return AnimatedTextKit( 68 | animatedTexts: [ 69 | TyperAnimatedText( 70 | 'Welcome to Reins!', 71 | speed: const Duration(milliseconds: 100), 72 | ), 73 | TyperAnimatedText( 74 | 'Configure a server address to start.', 75 | speed: const Duration(milliseconds: 100), 76 | ), 77 | ], 78 | displayFullTextOnTap: true, 79 | isRepeatingAnimation: false, 80 | pause: Duration(milliseconds: 1500), 81 | stopPauseOnTap: true, 82 | onFinished: onFinished, 83 | ); 84 | } 85 | } 86 | 87 | class _ChatConfigureServerAddressButton extends StatelessWidget { 88 | @override 89 | Widget build(BuildContext context) { 90 | return OutlinedButton.icon( 91 | icon: const Icon( 92 | Icons.warning_amber_rounded, 93 | color: Colors.amber, 94 | ), 95 | label: Text('Tap to configure a server address'), 96 | iconAlignment: IconAlignment.start, 97 | onPressed: () { 98 | Navigator.pushNamed( 99 | context, 100 | '/settings', 101 | arguments: SettingsRouteArguments(autoFocusServerAddress: true), 102 | ); 103 | }, 104 | ); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /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 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /windows/runner/Runner.rc: -------------------------------------------------------------------------------- 1 | // Microsoft Visual C++ generated resource script. 2 | // 3 | #pragma code_page(65001) 4 | #include "resource.h" 5 | 6 | #define APSTUDIO_READONLY_SYMBOLS 7 | ///////////////////////////////////////////////////////////////////////////// 8 | // 9 | // Generated from the TEXTINCLUDE 2 resource. 10 | // 11 | #include "winres.h" 12 | 13 | ///////////////////////////////////////////////////////////////////////////// 14 | #undef APSTUDIO_READONLY_SYMBOLS 15 | 16 | ///////////////////////////////////////////////////////////////////////////// 17 | // English (United States) resources 18 | 19 | #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) 20 | LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US 21 | 22 | #ifdef APSTUDIO_INVOKED 23 | ///////////////////////////////////////////////////////////////////////////// 24 | // 25 | // TEXTINCLUDE 26 | // 27 | 28 | 1 TEXTINCLUDE 29 | BEGIN 30 | "resource.h\0" 31 | END 32 | 33 | 2 TEXTINCLUDE 34 | BEGIN 35 | "#include ""winres.h""\r\n" 36 | "\0" 37 | END 38 | 39 | 3 TEXTINCLUDE 40 | BEGIN 41 | "\r\n" 42 | "\0" 43 | END 44 | 45 | #endif // APSTUDIO_INVOKED 46 | 47 | 48 | ///////////////////////////////////////////////////////////////////////////// 49 | // 50 | // Icon 51 | // 52 | 53 | // Icon with lowest ID value placed first to ensure application icon 54 | // remains consistent on all systems. 55 | IDI_APP_ICON ICON "resources\\app_icon.ico" 56 | 57 | 58 | ///////////////////////////////////////////////////////////////////////////// 59 | // 60 | // Version 61 | // 62 | 63 | #if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) 64 | #define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD 65 | #else 66 | #define VERSION_AS_NUMBER 1,0,0,0 67 | #endif 68 | 69 | #if defined(FLUTTER_VERSION) 70 | #define VERSION_AS_STRING FLUTTER_VERSION 71 | #else 72 | #define VERSION_AS_STRING "1.0.0" 73 | #endif 74 | 75 | VS_VERSION_INFO VERSIONINFO 76 | FILEVERSION VERSION_AS_NUMBER 77 | PRODUCTVERSION VERSION_AS_NUMBER 78 | FILEFLAGSMASK VS_FFI_FILEFLAGSMASK 79 | #ifdef _DEBUG 80 | FILEFLAGS VS_FF_DEBUG 81 | #else 82 | FILEFLAGS 0x0L 83 | #endif 84 | FILEOS VOS__WINDOWS32 85 | FILETYPE VFT_APP 86 | FILESUBTYPE 0x0L 87 | BEGIN 88 | BLOCK "StringFileInfo" 89 | BEGIN 90 | BLOCK "040904e4" 91 | BEGIN 92 | VALUE "CompanyName", "dev.ibrahimcetin" "\0" 93 | VALUE "FileDescription", "reins" "\0" 94 | VALUE "FileVersion", VERSION_AS_STRING "\0" 95 | VALUE "InternalName", "reins" "\0" 96 | VALUE "LegalCopyright", "Copyright (C) 2024 dev.ibrahimcetin. All rights reserved." "\0" 97 | VALUE "OriginalFilename", "reins.exe" "\0" 98 | VALUE "ProductName", "reins" "\0" 99 | VALUE "ProductVersion", VERSION_AS_STRING "\0" 100 | END 101 | END 102 | BLOCK "VarFileInfo" 103 | BEGIN 104 | VALUE "Translation", 0x409, 1252 105 | END 106 | END 107 | 108 | #endif // English (United States) resources 109 | ///////////////////////////////////////////////////////////////////////////// 110 | 111 | 112 | 113 | #ifndef APSTUDIO_INVOKED 114 | ///////////////////////////////////////////////////////////////////////////// 115 | // 116 | // Generated from the TEXTINCLUDE 3 resource. 117 | // 118 | 119 | 120 | ///////////////////////////////////////////////////////////////////////////// 121 | #endif // not APSTUDIO_INVOKED 122 | -------------------------------------------------------------------------------- /lib/Widgets/chat_app_bar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:google_fonts/google_fonts.dart'; 3 | import 'package:reins/Constants/constants.dart'; 4 | import 'package:reins/Widgets/chat_configure_bottom_sheet.dart'; 5 | import 'package:reins/Widgets/ollama_bottom_sheet_header.dart'; 6 | import 'package:reins/Widgets/selection_bottom_sheet.dart'; 7 | import 'package:provider/provider.dart'; 8 | import 'package:reins/Providers/chat_provider.dart'; 9 | import 'package:hive_flutter/hive_flutter.dart'; 10 | import 'package:responsive_framework/responsive_framework.dart'; 11 | 12 | class ChatAppBar extends StatelessWidget implements PreferredSizeWidget { 13 | const ChatAppBar({super.key}); 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | final chatProvider = Provider.of(context); 18 | 19 | return AppBar( 20 | title: Column( 21 | children: [ 22 | Text(AppConstants.appName, style: GoogleFonts.pacifico()), 23 | if (chatProvider.currentChat != null) 24 | InkWell( 25 | onTap: () { 26 | _handleModelSelectionButton(context); 27 | }, 28 | customBorder: StadiumBorder(), 29 | child: Padding( 30 | padding: const EdgeInsets.symmetric(horizontal: 8.0), 31 | child: Text( 32 | chatProvider.currentChat!.model, 33 | style: GoogleFonts.kodeMono( 34 | textStyle: Theme.of(context).textTheme.labelSmall, 35 | ), 36 | ), 37 | ), 38 | ), 39 | ], 40 | ), 41 | actions: [ 42 | IconButton( 43 | icon: const Icon(Icons.tune), 44 | onPressed: () { 45 | _handleConfigureButton(context); 46 | }, 47 | ), 48 | ], 49 | forceMaterialTransparency: !ResponsiveBreakpoints.of(context).isMobile, 50 | ); 51 | } 52 | 53 | Future _handleModelSelectionButton(BuildContext context) async { 54 | final chatProvider = Provider.of(context, listen: false); 55 | 56 | final selectedModelName = await showSelectionBottomSheet( 57 | key: ValueKey("${Hive.box('settings').get('serverAddress')}-string"), 58 | context: context, 59 | header: OllamaBottomSheetHeader(title: "Change The Model"), 60 | fetchItems: () async { 61 | final models = await chatProvider.fetchAvailableModels(); 62 | 63 | return models.map((model) => model.name).toList(); 64 | }, 65 | currentSelection: chatProvider.currentChat!.model, 66 | ); 67 | 68 | await chatProvider.updateCurrentChat(newModel: selectedModelName); 69 | } 70 | 71 | Future _handleConfigureButton(BuildContext context) async { 72 | final chatProvider = Provider.of(context, listen: false); 73 | 74 | final arguments = chatProvider.currentChatConfiguration; 75 | 76 | final ChatConfigureBottomSheetAction? action = await showModalBottomSheet( 77 | context: context, 78 | isScrollControlled: true, 79 | builder: (BuildContext context) { 80 | return Padding( 81 | padding: MediaQuery.of(context).viewInsets, 82 | child: ChatConfigureBottomSheet(arguments: arguments), 83 | ); 84 | }, 85 | ); 86 | 87 | // If the user deletes the chat, we don't need to update the chat. 88 | if (action == ChatConfigureBottomSheetAction.delete) return; 89 | 90 | await chatProvider.updateCurrentChat( 91 | newSystemPrompt: arguments.systemPrompt, 92 | newOptions: arguments.chatOptions, 93 | ); 94 | } 95 | 96 | @override 97 | Size get preferredSize => const Size.fromHeight(kToolbarHeight); 98 | } 99 | -------------------------------------------------------------------------------- /test/ollama_modefile_generator_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:reins/Models/ollama_chat.dart'; 2 | import 'package:reins/Models/ollama_message.dart'; 3 | import 'package:test/test.dart'; 4 | import 'package:reins/Services/ollama_modelfile_generator.dart'; 5 | 6 | void main() { 7 | final generator = OllamaModelfileGenerator(); 8 | 9 | test('Test generate modelfile from empty chat', () async { 10 | final chat = OllamaChat( 11 | model: 'llama3.2:latest', 12 | systemPrompt: 13 | 'You are Mario from super mario bros, acting as an assistant.', 14 | options: OllamaChatOptions(), 15 | title: 'New Chat', 16 | ); 17 | 18 | final result = await generator.generate(chat, []); 19 | 20 | expect(result, 21 | 'FROM llama3.2:latest\nSYSTEM """You are Mario from super mario bros, acting as an assistant."""\n'); 22 | }); 23 | 24 | test('Test generate modelfile from chat with options', () async { 25 | final chat = OllamaChat( 26 | model: 'llama3.2:latest', 27 | systemPrompt: 28 | 'You are Mario from super mario bros, acting as an assistant.', 29 | options: OllamaChatOptions()..temperature = 0.5, 30 | title: 'New Chat', 31 | ); 32 | 33 | final result = await generator.generate(chat, []); 34 | 35 | expect(result, 36 | 'FROM llama3.2:latest\nSYSTEM """You are Mario from super mario bros, acting as an assistant."""\nPARAMETER temperature 0.5\n'); 37 | }); 38 | 39 | test('Test generate modelfile from chat with messages', () async { 40 | final chat = OllamaChat( 41 | model: 'llama3.2:latest', 42 | systemPrompt: 43 | 'You are Mario from super mario bros, acting as an assistant.', 44 | options: OllamaChatOptions()..temperature = 0.5, 45 | title: 'New Chat', 46 | ); 47 | final messages = [ 48 | OllamaMessage('Hello!', role: OllamaMessageRole.user), 49 | OllamaMessage('How can I help you?', role: OllamaMessageRole.assistant), 50 | ]; 51 | 52 | final result = await generator.generate(chat, messages); 53 | 54 | expect(result, 55 | 'FROM llama3.2:latest\nSYSTEM """You are Mario from super mario bros, acting as an assistant."""\nPARAMETER temperature 0.5\nMESSAGE user Hello!\nMESSAGE assistant How can I help you?\n'); 56 | }); 57 | 58 | test('Test generate modelfile with all configured options', () async { 59 | final options = OllamaChatOptions() 60 | ..mirostat = 1 61 | ..mirostatEta = 0.2 62 | ..mirostatTau = 4.0 63 | ..contextSize = 1024 64 | ..repeatLastN = 32 65 | ..repeatPenalty = 1.2 66 | ..temperature = 0.5 67 | ..seed = 42 68 | ..tailFreeSampling = 0.9 69 | ..maxTokens = 100 70 | ..topK = 50 71 | ..topP = 0.6 72 | ..minP = 0.1; 73 | 74 | final chat = OllamaChat( 75 | model: 'llama3.2:latest:latest', 76 | systemPrompt: 77 | 'You are Mario from super mario bros, acting as an assistant.', 78 | options: options, 79 | title: 'New Chat', 80 | ); 81 | final messages = [ 82 | OllamaMessage('Hello!', role: OllamaMessageRole.user), 83 | OllamaMessage('How can I help you?', role: OllamaMessageRole.assistant), 84 | ]; 85 | 86 | final result = await generator.generate(chat, messages); 87 | 88 | expect(result, 89 | 'FROM llama3.2:latest:latest\nSYSTEM """You are Mario from super mario bros, acting as an assistant."""\nPARAMETER mirostat 1\nPARAMETER mirostat_eta 0.2\nPARAMETER mirostat_tau 4.0\nPARAMETER num_ctx 1024\nPARAMETER repeat_last_n 32\nPARAMETER repeat_penalty 1.2\nPARAMETER temperature 0.5\nPARAMETER seed 42\nPARAMETER tfs_z 0.9\nPARAMETER num_predict 100\nPARAMETER top_k 50\nPARAMETER top_p 0.6\nPARAMETER min_p 0.1\nMESSAGE user Hello!\nMESSAGE assistant How can I help you?\n'); 90 | }); 91 | } 92 | -------------------------------------------------------------------------------- /windows/runner/win32_window.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_WIN32_WINDOW_H_ 2 | #define RUNNER_WIN32_WINDOW_H_ 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | // A class abstraction for a high DPI-aware Win32 Window. Intended to be 11 | // inherited from by classes that wish to specialize with custom 12 | // rendering and input handling 13 | class Win32Window { 14 | public: 15 | struct Point { 16 | unsigned int x; 17 | unsigned int y; 18 | Point(unsigned int x, unsigned int y) : x(x), y(y) {} 19 | }; 20 | 21 | struct Size { 22 | unsigned int width; 23 | unsigned int height; 24 | Size(unsigned int width, unsigned int height) 25 | : width(width), height(height) {} 26 | }; 27 | 28 | Win32Window(); 29 | virtual ~Win32Window(); 30 | 31 | // Creates a win32 window with |title| that is positioned and sized using 32 | // |origin| and |size|. New windows are created on the default monitor. Window 33 | // sizes are specified to the OS in physical pixels, hence to ensure a 34 | // consistent size this function will scale the inputted width and height as 35 | // as appropriate for the default monitor. The window is invisible until 36 | // |Show| is called. Returns true if the window was created successfully. 37 | bool Create(const std::wstring& title, const Point& origin, const Size& size); 38 | 39 | // Show the current window. Returns true if the window was successfully shown. 40 | bool Show(); 41 | 42 | // Release OS resources associated with window. 43 | void Destroy(); 44 | 45 | // Inserts |content| into the window tree. 46 | void SetChildContent(HWND content); 47 | 48 | // Returns the backing Window handle to enable clients to set icon and other 49 | // window properties. Returns nullptr if the window has been destroyed. 50 | HWND GetHandle(); 51 | 52 | // If true, closing this window will quit the application. 53 | void SetQuitOnClose(bool quit_on_close); 54 | 55 | // Return a RECT representing the bounds of the current client area. 56 | RECT GetClientArea(); 57 | 58 | protected: 59 | // Processes and route salient window messages for mouse handling, 60 | // size change and DPI. Delegates handling of these to member overloads that 61 | // inheriting classes can handle. 62 | virtual LRESULT MessageHandler(HWND window, 63 | UINT const message, 64 | WPARAM const wparam, 65 | LPARAM const lparam) noexcept; 66 | 67 | // Called when CreateAndShow is called, allowing subclass window-related 68 | // setup. Subclasses should return false if setup fails. 69 | virtual bool OnCreate(); 70 | 71 | // Called when Destroy is called. 72 | virtual void OnDestroy(); 73 | 74 | private: 75 | friend class WindowClassRegistrar; 76 | 77 | // OS callback called by message pump. Handles the WM_NCCREATE message which 78 | // is passed when the non-client area is being created and enables automatic 79 | // non-client DPI scaling so that the non-client area automatically 80 | // responds to changes in DPI. All other messages are handled by 81 | // MessageHandler. 82 | static LRESULT CALLBACK WndProc(HWND const window, 83 | UINT const message, 84 | WPARAM const wparam, 85 | LPARAM const lparam) noexcept; 86 | 87 | // Retrieves a class instance pointer for |window| 88 | static Win32Window* GetThisFromHandle(HWND const window) noexcept; 89 | 90 | // Update the window frame's theme to match the system theme. 91 | static void UpdateTheme(HWND const window); 92 | 93 | bool quit_on_close_ = false; 94 | 95 | // window handle for top level window. 96 | HWND window_handle_ = nullptr; 97 | 98 | // window handle for hosted content. 99 | HWND child_content_ = nullptr; 100 | }; 101 | 102 | #endif // RUNNER_WIN32_WINDOW_H_ 103 | -------------------------------------------------------------------------------- /linux/flatpak/dev.ibrahimcetin.reins.metainfo.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | dev.ibrahimcetin.reins 4 | 5 | Reins: Chat for Ollama 6 | Private AI Chat 7 | 8 | 9 |

10 | Reins is a powerful chat client for your self-hosted Ollama AI models. Connect remotely, customize prompts, adjust parameters, and manage multiple conversations with ease. 11 |

12 |

13 | Key Features: 14 |

15 |
    16 |
  • Connect to remote Ollama servers
  • 17 |
  • Custom system prompts per chat
  • 18 |
  • Edit and regenerate responses
  • 19 |
  • Send images
  • 20 |
  • Fine-tune model parameters (temperature, context size, etc.) for each chat
  • 21 |
  • Switch models mid-conversation
  • 22 |
  • Create custom models from chats
  • 23 |
  • Real-time message streaming
  • 24 |
25 |

26 | Requires a self-hosted Ollama Server. 27 |

28 |
29 | 30 | dev.ibrahimcetin.reins 31 | dev.ibrahimcetin.reins.desktop 32 | 33 | 34 | İbrahim Çetin 35 | 36 | 37 | 38 | Utility 39 | Development 40 | Chat 41 | 42 | 43 | CC0-1.0 44 | GPL-3.0-only 45 | 46 | 47 | pointing 48 | keyboard 49 | touch 50 | 51 | 52 | 53 | 54 | https://reins.ibrahimcetin.dev 55 | https://github.com/ibrahimcetin/reins 56 | https://github.com/ibrahimcetin/reins/issues 57 | 58 | 59 | 60 | https://reins.ibrahimcetin.dev/assets/screenshots/desktop/1.png 61 | Chatting with a model using Reins 62 | 63 | 64 | https://reins.ibrahimcetin.dev/assets/screenshots/desktop/2.png 65 | Configure your chat's options 66 | 67 | 68 | https://reins.ibrahimcetin.dev/assets/screenshots/desktop/3.png 69 | Advanced chat options 70 | 71 | 72 | https://reins.ibrahimcetin.dev/assets/screenshots/desktop/4.png 73 | Dynamically switch LLM models 74 | 75 | 76 | https://reins.ibrahimcetin.dev/assets/screenshots/desktop/5.png 77 | Select a model for the chat 78 | 79 | 80 | https://reins.ibrahimcetin.dev/assets/screenshots/desktop/6.png 81 | Edit and regenerate prompt 82 | 83 | 84 | https://reins.ibrahimcetin.dev/assets/screenshots/desktop/7.png 85 | Customize app theme as you want 86 | 87 | 88 | https://reins.ibrahimcetin.dev/assets/screenshots/desktop/8.png 89 | Open source and privacy prioritized 90 | 91 | 92 | 93 | 94 | 95 | https://github.com/ibrahimcetin/reins/releases/tag/v1.3.2 96 | 97 |

Fixes

98 |
    99 |
  • Fixed an issue where the chat could scroll up automatically while generating a response.
  • 100 |
101 |
102 |
103 |
104 |
-------------------------------------------------------------------------------- /lib/Pages/chat_page/subwidgets/chat_bubble/chat_bubble_actions.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'package:provider/provider.dart'; 4 | import 'package:reins/Models/ollama_message.dart'; 5 | import 'package:reins/Providers/chat_provider.dart'; 6 | 7 | import 'chat_bubble_bottom_sheet.dart'; 8 | 9 | class ChatBubbleActions { 10 | final OllamaMessage message; 11 | 12 | ChatBubbleActions(this.message); 13 | 14 | void handleCopy() { 15 | Clipboard.setData(ClipboardData(text: message.content)); 16 | } 17 | 18 | void handleSelectText(BuildContext context) { 19 | showModalBottomSheet( 20 | context: context, 21 | constraints: BoxConstraints( 22 | maxHeight: MediaQuery.of(context).size.height * 0.9, 23 | ), 24 | isScrollControlled: true, 25 | builder: (context) { 26 | return ChatBubbleBottomSheet( 27 | title: 'Select Text', 28 | child: SelectableText( 29 | message.content, 30 | style: Theme.of(context).textTheme.bodyLarge, 31 | ), 32 | ); 33 | }, 34 | ); 35 | } 36 | 37 | void handleRegenerate(BuildContext context) { 38 | final chatProvider = Provider.of(context, listen: false); 39 | 40 | chatProvider.regenerateMessage(message); 41 | } 42 | 43 | void handleEdit(BuildContext context) { 44 | final chatProvider = Provider.of(context, listen: false); 45 | 46 | showModalBottomSheet( 47 | context: context, 48 | constraints: BoxConstraints( 49 | maxHeight: MediaQuery.of(context).size.height * 0.9, 50 | ), 51 | isScrollControlled: true, 52 | isDismissible: false, 53 | enableDrag: false, 54 | builder: (context) { 55 | String textFieldText = message.content; 56 | 57 | return ChatBubbleBottomSheet( 58 | title: 'Edit Message', 59 | actions: [ 60 | TextButton( 61 | onPressed: () => Navigator.pop(context), 62 | child: const Text('Cancel'), 63 | ), 64 | TextButton( 65 | onPressed: () async { 66 | if (textFieldText.isNotEmpty) { 67 | await chatProvider.updateMessage( 68 | message, 69 | newContent: textFieldText, 70 | ); 71 | if (context.mounted) Navigator.pop(context, textFieldText); 72 | } 73 | }, 74 | child: const Text('Save'), 75 | ), 76 | ], 77 | child: TextFormField( 78 | initialValue: textFieldText, 79 | onChanged: (value) => textFieldText = value, 80 | autofocus: true, 81 | maxLines: null, 82 | expands: true, 83 | textAlignVertical: TextAlignVertical.top, 84 | textCapitalization: TextCapitalization.sentences, 85 | decoration: InputDecoration(border: OutlineInputBorder()), 86 | ), 87 | ); 88 | }, 89 | ); 90 | } 91 | 92 | void handleDelete(BuildContext context) { 93 | final chatProvider = Provider.of(context, listen: false); 94 | 95 | showDialog( 96 | context: context, 97 | builder: (context) { 98 | return AlertDialog( 99 | title: const Text('Delete Message?'), 100 | content: const Text('This action cannot be undone.'), 101 | actions: [ 102 | TextButton( 103 | onPressed: () => Navigator.pop(context), 104 | child: const Text('Cancel'), 105 | ), 106 | TextButton( 107 | onPressed: () async { 108 | await chatProvider.deleteMessage(message); 109 | if (context.mounted) Navigator.pop(context); 110 | }, 111 | child: const Text( 112 | 'Delete', 113 | style: TextStyle(color: Colors.red), 114 | ), 115 | ), 116 | ], 117 | ); 118 | }, 119 | ); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /windows/flutter/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file controls Flutter-level build steps. It should not be edited. 2 | cmake_minimum_required(VERSION 3.14) 3 | 4 | set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") 5 | 6 | # Configuration provided via flutter tool. 7 | include(${EPHEMERAL_DIR}/generated_config.cmake) 8 | 9 | # TODO: Move the rest of this into files in ephemeral. See 10 | # https://github.com/flutter/flutter/issues/57146. 11 | set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") 12 | 13 | # Set fallback configurations for older versions of the flutter tool. 14 | if (NOT DEFINED FLUTTER_TARGET_PLATFORM) 15 | set(FLUTTER_TARGET_PLATFORM "windows-x64") 16 | endif() 17 | 18 | # === Flutter Library === 19 | set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") 20 | 21 | # Published to parent scope for install step. 22 | set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) 23 | set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) 24 | set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) 25 | set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) 26 | 27 | list(APPEND FLUTTER_LIBRARY_HEADERS 28 | "flutter_export.h" 29 | "flutter_windows.h" 30 | "flutter_messenger.h" 31 | "flutter_plugin_registrar.h" 32 | "flutter_texture_registrar.h" 33 | ) 34 | list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") 35 | add_library(flutter INTERFACE) 36 | target_include_directories(flutter INTERFACE 37 | "${EPHEMERAL_DIR}" 38 | ) 39 | target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") 40 | add_dependencies(flutter flutter_assemble) 41 | 42 | # === Wrapper === 43 | list(APPEND CPP_WRAPPER_SOURCES_CORE 44 | "core_implementations.cc" 45 | "standard_codec.cc" 46 | ) 47 | list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") 48 | list(APPEND CPP_WRAPPER_SOURCES_PLUGIN 49 | "plugin_registrar.cc" 50 | ) 51 | list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") 52 | list(APPEND CPP_WRAPPER_SOURCES_APP 53 | "flutter_engine.cc" 54 | "flutter_view_controller.cc" 55 | ) 56 | list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") 57 | 58 | # Wrapper sources needed for a plugin. 59 | add_library(flutter_wrapper_plugin STATIC 60 | ${CPP_WRAPPER_SOURCES_CORE} 61 | ${CPP_WRAPPER_SOURCES_PLUGIN} 62 | ) 63 | apply_standard_settings(flutter_wrapper_plugin) 64 | set_target_properties(flutter_wrapper_plugin PROPERTIES 65 | POSITION_INDEPENDENT_CODE ON) 66 | set_target_properties(flutter_wrapper_plugin PROPERTIES 67 | CXX_VISIBILITY_PRESET hidden) 68 | target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) 69 | target_include_directories(flutter_wrapper_plugin PUBLIC 70 | "${WRAPPER_ROOT}/include" 71 | ) 72 | add_dependencies(flutter_wrapper_plugin flutter_assemble) 73 | 74 | # Wrapper sources needed for the runner. 75 | add_library(flutter_wrapper_app STATIC 76 | ${CPP_WRAPPER_SOURCES_CORE} 77 | ${CPP_WRAPPER_SOURCES_APP} 78 | ) 79 | apply_standard_settings(flutter_wrapper_app) 80 | target_link_libraries(flutter_wrapper_app PUBLIC flutter) 81 | target_include_directories(flutter_wrapper_app PUBLIC 82 | "${WRAPPER_ROOT}/include" 83 | ) 84 | add_dependencies(flutter_wrapper_app flutter_assemble) 85 | 86 | # === Flutter tool backend === 87 | # _phony_ is a non-existent file to force this command to run every time, 88 | # since currently there's no way to get a full input/output list from the 89 | # flutter tool. 90 | set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") 91 | set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) 92 | add_custom_command( 93 | OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} 94 | ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} 95 | ${CPP_WRAPPER_SOURCES_APP} 96 | ${PHONY_OUTPUT} 97 | COMMAND ${CMAKE_COMMAND} -E env 98 | ${FLUTTER_TOOL_ENVIRONMENT} 99 | "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" 100 | ${FLUTTER_TARGET_PLATFORM} $ 101 | VERBATIM 102 | ) 103 | add_custom_target(flutter_assemble DEPENDS 104 | "${FLUTTER_LIBRARY}" 105 | ${FLUTTER_LIBRARY_HEADERS} 106 | ${CPP_WRAPPER_SOURCES_CORE} 107 | ${CPP_WRAPPER_SOURCES_PLUGIN} 108 | ${CPP_WRAPPER_SOURCES_APP} 109 | ) 110 | -------------------------------------------------------------------------------- /windows/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Project-level configuration. 2 | cmake_minimum_required(VERSION 3.14) 3 | project(reins LANGUAGES CXX) 4 | 5 | # The name of the executable created for the application. Change this to change 6 | # the on-disk name of your application. 7 | set(BINARY_NAME "reins") 8 | 9 | # Explicitly opt in to modern CMake behaviors to avoid warnings with recent 10 | # versions of CMake. 11 | cmake_policy(VERSION 3.14...3.25) 12 | 13 | # Define build configuration option. 14 | get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) 15 | if(IS_MULTICONFIG) 16 | set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" 17 | CACHE STRING "" FORCE) 18 | else() 19 | if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) 20 | set(CMAKE_BUILD_TYPE "Debug" CACHE 21 | STRING "Flutter build mode" FORCE) 22 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS 23 | "Debug" "Profile" "Release") 24 | endif() 25 | endif() 26 | # Define settings for the Profile build mode. 27 | set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") 28 | set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") 29 | set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") 30 | set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") 31 | 32 | # Use Unicode for all projects. 33 | add_definitions(-DUNICODE -D_UNICODE) 34 | 35 | # Compilation settings that should be applied to most targets. 36 | # 37 | # Be cautious about adding new options here, as plugins use this function by 38 | # default. In most cases, you should add new options to specific targets instead 39 | # of modifying this function. 40 | function(APPLY_STANDARD_SETTINGS TARGET) 41 | target_compile_features(${TARGET} PUBLIC cxx_std_17) 42 | target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") 43 | target_compile_options(${TARGET} PRIVATE /EHsc) 44 | target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") 45 | target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") 46 | endfunction() 47 | 48 | # Flutter library and tool build rules. 49 | set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") 50 | add_subdirectory(${FLUTTER_MANAGED_DIR}) 51 | 52 | # Application build; see runner/CMakeLists.txt. 53 | add_subdirectory("runner") 54 | 55 | 56 | # Generated plugin build rules, which manage building the plugins and adding 57 | # them to the application. 58 | include(flutter/generated_plugins.cmake) 59 | 60 | 61 | # === Installation === 62 | # Support files are copied into place next to the executable, so that it can 63 | # run in place. This is done instead of making a separate bundle (as on Linux) 64 | # so that building and running from within Visual Studio will work. 65 | set(BUILD_BUNDLE_DIR "$") 66 | # Make the "install" step default, as it's required to run. 67 | set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) 68 | if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) 69 | set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) 70 | endif() 71 | 72 | set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") 73 | set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") 74 | 75 | install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" 76 | COMPONENT Runtime) 77 | 78 | install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" 79 | COMPONENT Runtime) 80 | 81 | install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 82 | COMPONENT Runtime) 83 | 84 | if(PLUGIN_BUNDLED_LIBRARIES) 85 | install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" 86 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 87 | COMPONENT Runtime) 88 | endif() 89 | 90 | # Copy the native assets provided by the build.dart from all packages. 91 | set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/") 92 | install(DIRECTORY "${NATIVE_ASSETS_DIR}" 93 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 94 | COMPONENT Runtime) 95 | 96 | # Fully re-copy the assets directory on each build to avoid having stale files 97 | # from a previous install. 98 | set(FLUTTER_ASSET_DIR_NAME "flutter_assets") 99 | install(CODE " 100 | file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") 101 | " COMPONENT Runtime) 102 | install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" 103 | DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) 104 | 105 | # Install the AOT library on non-Debug builds only. 106 | install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" 107 | CONFIGURATIONS Profile;Release 108 | COMPONENT Runtime) 109 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:in_app_review/in_app_review.dart'; 3 | import 'package:reins/Constants/constants.dart'; 4 | import 'package:reins/Models/settings_route_arguments.dart'; 5 | import 'package:reins/Pages/chat_page/chat_page_view_model.dart'; 6 | import 'package:reins/Pages/main_page.dart'; 7 | import 'package:reins/Pages/settings_page/settings_page.dart'; 8 | import 'package:reins/Providers/chat_provider.dart'; 9 | import 'package:reins/Services/services.dart'; 10 | import 'package:reins/Utils/material_color_adapter.dart'; 11 | import 'package:provider/provider.dart'; 12 | import 'package:hive_flutter/hive_flutter.dart'; 13 | import 'package:reins/Utils/request_review_helper.dart'; 14 | import 'package:responsive_framework/responsive_framework.dart'; 15 | import 'dart:io' show Platform; 16 | import 'package:sqflite_common_ffi/sqflite_ffi.dart'; 17 | 18 | void main() async { 19 | WidgetsFlutterBinding.ensureInitialized(); 20 | 21 | if (Platform.isWindows || Platform.isLinux) { 22 | sqfliteFfiInit(); 23 | databaseFactory = databaseFactoryFfi; 24 | } 25 | 26 | // Initialize PathManager 27 | await PathManager.initialize(); 28 | 29 | // Initialize Hive 30 | if (Platform.isLinux) { 31 | Hive.init(PathManager.instance.documentsDirectory.path); 32 | } else { 33 | await Hive.initFlutter(); 34 | } 35 | 36 | Hive.registerAdapter(MaterialColorAdapter()); 37 | 38 | await Hive.openBox('settings'); 39 | 40 | // Initialize RequestReviewHelper and request review if needed 41 | final reviewHelper = await RequestReviewHelper.initialize(); 42 | 43 | await reviewHelper.incrementCount(isLaunch: true); 44 | 45 | final inAppReview = InAppReview.instance; 46 | if (await inAppReview.isAvailable() && reviewHelper.shouldRequestReview()) { 47 | await inAppReview.requestReview(); 48 | } 49 | 50 | runApp( 51 | MultiProvider( 52 | providers: [ 53 | Provider(create: (_) => OllamaService()), 54 | Provider(create: (_) => DatabaseService()), 55 | Provider(create: (_) => PermissionService()), 56 | Provider(create: (_) => ImageService()), 57 | ChangeNotifierProvider( 58 | create: (context) => ChatProvider( 59 | ollamaService: context.read(), 60 | databaseService: context.read(), 61 | ), 62 | ), 63 | Provider( 64 | create: (context) => ChatPageViewModel( 65 | ollamaService: context.read(), 66 | databaseService: context.read(), 67 | permissionService: context.read(), 68 | imageService: context.read(), 69 | ), 70 | ), 71 | ], 72 | child: const ReinsApp(), 73 | ), 74 | ); 75 | } 76 | 77 | class ReinsApp extends StatelessWidget { 78 | const ReinsApp({super.key}); 79 | 80 | @override 81 | Widget build(BuildContext context) { 82 | return ValueListenableBuilder( 83 | valueListenable: Hive.box('settings').listenable( 84 | keys: ['color', 'brightness'], 85 | ), 86 | builder: (context, box, _) { 87 | return MaterialApp( 88 | title: AppConstants.appName, 89 | theme: ThemeData( 90 | colorScheme: ColorScheme.fromSeed( 91 | brightness: _brightness ?? MediaQuery.platformBrightnessOf(context), 92 | dynamicSchemeVariant: DynamicSchemeVariant.neutral, 93 | seedColor: box.get('color', defaultValue: Colors.grey), 94 | ), 95 | appBarTheme: const AppBarTheme(centerTitle: true), 96 | useMaterial3: true, 97 | ), 98 | builder: (context, child) => ResponsiveBreakpoints.builder( 99 | breakpoints: [ 100 | const Breakpoint(start: 0, end: 450, name: MOBILE), 101 | const Breakpoint(start: 451, end: 800, name: TABLET), 102 | const Breakpoint(start: 801, end: 1920, name: DESKTOP), 103 | ], 104 | useShortestSide: true, 105 | child: child!, 106 | ), 107 | onGenerateRoute: (settings) { 108 | if (settings.name == '/') { 109 | return MaterialPageRoute( 110 | builder: (context) => const ReinsMainPage(), 111 | ); 112 | } 113 | 114 | if (settings.name == '/settings') { 115 | final args = settings.arguments as SettingsRouteArguments?; 116 | 117 | return MaterialPageRoute( 118 | builder: (context) => SettingsPage(arguments: args), 119 | ); 120 | } 121 | 122 | assert(false, 'Need to implement ${settings.name}'); 123 | return null; 124 | }, 125 | ); 126 | }, 127 | ); 128 | } 129 | 130 | Brightness? get _brightness { 131 | final brightnessValue = Hive.box('settings').get('brightness'); 132 | if (brightnessValue == null) return null; 133 | return brightnessValue == 1 ? Brightness.light : Brightness.dark; 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 11 | 14 | 15 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 33 | 39 | 40 | 41 | 42 | 43 | 48 | 49 | 55 | 56 | 57 | 58 | 61 | 67 | 68 | 69 | 70 | 71 | 82 | 84 | 90 | 91 | 92 | 93 | 99 | 101 | 107 | 108 | 109 | 110 | 112 | 113 | 116 | 117 | 118 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 11 | 14 | 15 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 56 | 57 | 58 | 59 | 62 | 68 | 69 | 70 | 71 | 72 | 84 | 86 | 92 | 93 | 94 | 95 | 101 | 103 | 109 | 110 | 111 | 112 | 114 | 115 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /lib/Pages/chat_page/subwidgets/chat_bubble/chat_bubble.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_markdown/flutter_markdown.dart'; 3 | import 'package:google_fonts/google_fonts.dart'; 4 | import 'package:markdown/markdown.dart' as md; 5 | import 'package:reins/Models/ollama_message.dart'; 6 | import 'package:url_launcher/url_launcher_string.dart'; 7 | 8 | import 'chat_bubble_actions.dart'; 9 | import 'chat_bubble_image.dart'; 10 | import 'chat_bubble_menu.dart'; 11 | import 'chat_bubble_think_block.dart'; 12 | 13 | class ChatBubble extends StatelessWidget { 14 | final OllamaMessage message; 15 | 16 | const ChatBubble({ 17 | super.key, 18 | required this.message, 19 | }); 20 | 21 | @override 22 | Widget build(BuildContext context) { 23 | final actions = ChatBubbleActions(message); 24 | 25 | return ChatBubbleMenu( 26 | menuChildren: [ 27 | MenuItemButton( 28 | onPressed: actions.handleCopy, 29 | leadingIcon: Icon(Icons.copy_outlined), 30 | child: const Text('Copy'), 31 | ), 32 | MenuItemButton( 33 | onPressed: () => actions.handleSelectText(context), 34 | leadingIcon: Icon(Icons.select_all_outlined), 35 | child: const Text('Select Text'), 36 | ), 37 | MenuItemButton( 38 | onPressed: () => actions.handleRegenerate(context), 39 | leadingIcon: Icon(Icons.refresh_outlined), 40 | child: const Text('Regenerate'), 41 | ), 42 | Divider(), 43 | MenuItemButton( 44 | onPressed: () => actions.handleEdit(context), 45 | closeOnActivate: false, 46 | leadingIcon: Icon(Icons.edit_outlined), 47 | child: const Text('Edit'), 48 | ), 49 | MenuItemButton( 50 | onPressed: () => actions.handleDelete(context), 51 | leadingIcon: Icon(Icons.delete_outline), 52 | child: const Text('Delete'), 53 | ), 54 | ], 55 | child: _ChatBubbleBody(message: message), 56 | ); 57 | } 58 | } 59 | 60 | class _ChatBubbleBody extends StatelessWidget { 61 | final OllamaMessage message; 62 | 63 | const _ChatBubbleBody({super.key, required this.message}); 64 | 65 | @override 66 | Widget build(BuildContext context) { 67 | return Padding( 68 | padding: const EdgeInsets.symmetric(horizontal: 25.0, vertical: 15.0), 69 | child: Column( 70 | spacing: 8, 71 | crossAxisAlignment: bubbleAlignment, 72 | children: [ 73 | // If the message has an image attachment, display it 74 | if (message.images != null && message.images!.isNotEmpty) 75 | Wrap( 76 | spacing: 8, 77 | runSpacing: 8, 78 | children: message.images! 79 | .map((imageFile) => ChatBubbleImage(imageFile: imageFile)) 80 | .toList(), 81 | ), 82 | Container( 83 | padding: isSentFromUser ? const EdgeInsets.all(10.0) : null, 84 | constraints: BoxConstraints( 85 | maxWidth: isSentFromUser 86 | ? MediaQuery.of(context).size.width * 0.8 87 | : double.infinity, 88 | ), 89 | decoration: BoxDecoration( 90 | color: isSentFromUser 91 | ? Theme.of(context).colorScheme.primaryContainer 92 | : Theme.of(context).colorScheme.surface, 93 | borderRadius: BorderRadius.circular(10.0), 94 | ), 95 | child: MarkdownBody( 96 | data: message.content, 97 | selectable: true, 98 | softLineBreak: true, 99 | styleSheet: MarkdownStyleSheet( 100 | textScaler: TextScaler.linear(1.18), 101 | code: GoogleFonts.sourceCodePro(), 102 | ), 103 | builders: {'think': ThinkBlockBuilder()}, 104 | extensionSet: md.ExtensionSet( 105 | [ 106 | ThinkBlockSyntax(), 107 | ...md.ExtensionSet.gitHubFlavored.blockSyntaxes 108 | ], 109 | [ 110 | md.EmojiSyntax(), 111 | ...md.ExtensionSet.gitHubFlavored.inlineSyntaxes 112 | ], 113 | ), 114 | onTapLink: (text, href, title) => launchUrlString(href!), 115 | ), 116 | ), 117 | Text( 118 | TimeOfDay.fromDateTime(message.createdAt.toLocal()).format(context), 119 | style: TextStyle( 120 | color: Theme.of(context).colorScheme.onSurfaceVariant, 121 | ), 122 | ), 123 | ], 124 | ), 125 | ); 126 | } 127 | 128 | /// Returns true if the message is sent from the user. 129 | bool get isSentFromUser => message.role == OllamaMessageRole.user; 130 | 131 | /// Returns the alignment of the bubble. 132 | /// 133 | /// If the message is sent from the user, the alignment is [Alignment.centerRight]. 134 | /// Otherwise, the alignment is [Alignment.centerLeft]. 135 | CrossAxisAlignment get bubbleAlignment => 136 | isSentFromUser ? CrossAxisAlignment.end : CrossAxisAlignment.start; 137 | } 138 | -------------------------------------------------------------------------------- /linux/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Project-level configuration. 2 | cmake_minimum_required(VERSION 3.13) 3 | project(runner LANGUAGES CXX) 4 | 5 | # The name of the executable created for the application. Change this to change 6 | # the on-disk name of your application. 7 | set(BINARY_NAME "reins") 8 | # The unique GTK application identifier for this application. See: 9 | # https://wiki.gnome.org/HowDoI/ChooseApplicationID 10 | set(APPLICATION_ID "dev.ibrahimcetin.reins") 11 | 12 | # Explicitly opt in to modern CMake behaviors to avoid warnings with recent 13 | # versions of CMake. 14 | cmake_policy(SET CMP0063 NEW) 15 | 16 | # Load bundled libraries from the lib/ directory relative to the binary. 17 | set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") 18 | 19 | # Root filesystem for cross-building. 20 | if(FLUTTER_TARGET_PLATFORM_SYSROOT) 21 | set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) 22 | set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) 23 | set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) 24 | set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) 25 | set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) 26 | set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) 27 | endif() 28 | 29 | # Define build configuration options. 30 | if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) 31 | set(CMAKE_BUILD_TYPE "Debug" CACHE 32 | STRING "Flutter build mode" FORCE) 33 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS 34 | "Debug" "Profile" "Release") 35 | endif() 36 | 37 | # Compilation settings that should be applied to most targets. 38 | # 39 | # Be cautious about adding new options here, as plugins use this function by 40 | # default. In most cases, you should add new options to specific targets instead 41 | # of modifying this function. 42 | function(APPLY_STANDARD_SETTINGS TARGET) 43 | target_compile_features(${TARGET} PUBLIC cxx_std_14) 44 | target_compile_options(${TARGET} PRIVATE -Wall -Werror) 45 | target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") 46 | target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") 47 | endfunction() 48 | 49 | # Flutter library and tool build rules. 50 | set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") 51 | add_subdirectory(${FLUTTER_MANAGED_DIR}) 52 | 53 | # System-level dependencies. 54 | find_package(PkgConfig REQUIRED) 55 | pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) 56 | 57 | # Application build; see runner/CMakeLists.txt. 58 | add_subdirectory("runner") 59 | 60 | # Run the Flutter tool portions of the build. This must not be removed. 61 | add_dependencies(${BINARY_NAME} flutter_assemble) 62 | 63 | # Only the install-generated bundle's copy of the executable will launch 64 | # correctly, since the resources must in the right relative locations. To avoid 65 | # people trying to run the unbundled copy, put it in a subdirectory instead of 66 | # the default top-level location. 67 | set_target_properties(${BINARY_NAME} 68 | PROPERTIES 69 | RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" 70 | ) 71 | 72 | 73 | # Generated plugin build rules, which manage building the plugins and adding 74 | # them to the application. 75 | include(flutter/generated_plugins.cmake) 76 | 77 | 78 | # === Installation === 79 | # By default, "installing" just makes a relocatable bundle in the build 80 | # directory. 81 | set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") 82 | if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) 83 | set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) 84 | endif() 85 | 86 | # Start with a clean build bundle directory every time. 87 | install(CODE " 88 | file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") 89 | " COMPONENT Runtime) 90 | 91 | set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") 92 | set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") 93 | 94 | install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" 95 | COMPONENT Runtime) 96 | 97 | install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" 98 | COMPONENT Runtime) 99 | 100 | install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 101 | COMPONENT Runtime) 102 | 103 | foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) 104 | install(FILES "${bundled_library}" 105 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 106 | COMPONENT Runtime) 107 | endforeach(bundled_library) 108 | 109 | # Copy the native assets provided by the build.dart from all packages. 110 | set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/") 111 | install(DIRECTORY "${NATIVE_ASSETS_DIR}" 112 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 113 | COMPONENT Runtime) 114 | 115 | # Fully re-copy the assets directory on each build to avoid having stale files 116 | # from a previous install. 117 | set(FLUTTER_ASSET_DIR_NAME "flutter_assets") 118 | install(CODE " 119 | file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") 120 | " COMPONENT Runtime) 121 | install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" 122 | DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) 123 | 124 | # Install the AOT library on non-Debug builds only. 125 | if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") 126 | install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 127 | COMPONENT Runtime) 128 | endif() 129 | --------------------------------------------------------------------------------