├── .gitignore
├── LICENSE
├── README.md
├── README_JP.md
├── README_KR.md
├── analysis_options.yaml
├── android
├── .gitignore
├── app
│ ├── build.gradle
│ └── src
│ │ ├── debug
│ │ └── AndroidManifest.xml
│ │ ├── main
│ │ ├── AndroidManifest.xml
│ │ ├── kotlin
│ │ │ └── com
│ │ │ │ └── example
│ │ │ │ └── my_ollama
│ │ │ │ └── MainActivity.kt
│ │ └── res
│ │ │ ├── drawable-v21
│ │ │ └── launch_background.xml
│ │ │ ├── drawable
│ │ │ └── launch_background.xml
│ │ │ ├── mipmap-hdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-mdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xhdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── values-night
│ │ │ └── styles.xml
│ │ │ └── values
│ │ │ └── styles.xml
│ │ └── profile
│ │ └── AndroidManifest.xml
├── build.gradle
├── gradle.properties
├── gradle
│ └── wrapper
│ │ └── gradle-wrapper.properties
├── my_ollama_android.iml
└── settings.gradle
├── assets
├── images
│ ├── appstore.png
│ ├── claude.png
│ ├── ollama.png
│ ├── openai.png
│ └── pdfthumb.png
└── translations
│ ├── en-US.json
│ ├── ja-JP.json
│ └── ko-KR.json
├── changelog.md
├── devtools_options.yaml
├── image.jpg
├── image_en.jpg
├── image_jp.jpg
├── ios
├── .gitignore
├── Flutter
│ ├── AppFrameworkInfo.plist
│ ├── Debug.xcconfig
│ └── Release.xcconfig
├── Info.plist
├── Podfile
├── Podfile.lock
├── Runner.xcodeproj
│ ├── project.pbxproj
│ ├── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ ├── IDEWorkspaceChecks.plist
│ │ │ └── WorkspaceSettings.xcsettings
│ └── xcshareddata
│ │ └── xcschemes
│ │ └── Runner.xcscheme
├── Runner.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ ├── IDEWorkspaceChecks.plist
│ │ └── WorkspaceSettings.xcsettings
├── Runner
│ ├── AppDelegate.swift
│ ├── Assets.xcassets
│ │ ├── AppIcon.appiconset
│ │ │ ├── 100.png
│ │ │ ├── 1024.png
│ │ │ ├── 114.png
│ │ │ ├── 120.png
│ │ │ ├── 128.png
│ │ │ ├── 144.png
│ │ │ ├── 152.png
│ │ │ ├── 16.png
│ │ │ ├── 167.png
│ │ │ ├── 180.png
│ │ │ ├── 20.png
│ │ │ ├── 256.png
│ │ │ ├── 29.png
│ │ │ ├── 32.png
│ │ │ ├── 40.png
│ │ │ ├── 50.png
│ │ │ ├── 512.png
│ │ │ ├── 57.png
│ │ │ ├── 58.png
│ │ │ ├── 60.png
│ │ │ ├── 64.png
│ │ │ ├── 72.png
│ │ │ ├── 76.png
│ │ │ ├── 80.png
│ │ │ ├── 87.png
│ │ │ └── Contents.json
│ │ └── LaunchImage.imageset
│ │ │ ├── Contents.json
│ │ │ ├── LaunchImage.png
│ │ │ ├── LaunchImage@2x.png
│ │ │ ├── LaunchImage@3x.png
│ │ │ └── README.md
│ ├── Base.lproj
│ │ ├── LaunchScreen.storyboard
│ │ └── Main.storyboard
│ ├── Info.plist
│ ├── Runner-Bridging-Header.h
│ ├── en.lproj
│ │ └── InfoPlist.strings
│ ├── ja.lproj
│ │ └── InfoPlist.strings
│ └── ko.lproj
│ │ └── InfoPlist.strings
└── RunnerTests
│ └── RunnerTests.swift
├── ipad.png
├── lib
├── helpers
│ └── event_bus.dart
├── main.dart
├── models
│ ├── chat_session.dart
│ └── model_manager.dart
├── provider
│ └── main_provider.dart
├── services
│ └── chat_service.dart
├── utils
│ └── platform_utils.dart
├── views
│ ├── chat_view.dart
│ ├── desktop_view.dart
│ ├── drawer.dart
│ └── settings.dart
└── widgets
│ ├── app_title.dart
│ ├── dialogs.dart
│ ├── drop_menu.dart
│ ├── list_header.dart
│ ├── model_selector.dart
│ ├── text_fields.dart
│ └── title_list.dart
├── macos
├── .gitignore
├── Flutter
│ ├── Flutter-Debug.xcconfig
│ ├── Flutter-Release.xcconfig
│ └── GeneratedPluginRegistrant.swift
├── Podfile
├── Podfile.lock
├── Runner.xcodeproj
│ ├── project.pbxproj
│ ├── project.xcworkspace
│ │ └── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
│ └── xcshareddata
│ │ └── xcschemes
│ │ └── Runner.xcscheme
├── Runner.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
├── Runner
│ ├── AppDelegate.swift
│ ├── Assets.xcassets
│ │ └── AppIcon.appiconset
│ │ │ ├── 1024.png
│ │ │ ├── 128.png
│ │ │ ├── 16.png
│ │ │ ├── 256.png
│ │ │ ├── 32.png
│ │ │ ├── 512.png
│ │ │ ├── 64.png
│ │ │ └── Contents.json
│ ├── Base.lproj
│ │ └── MainMenu.xib
│ ├── Configs
│ │ ├── AppInfo.xcconfig
│ │ ├── Debug.xcconfig
│ │ ├── Release.xcconfig
│ │ └── Warnings.xcconfig
│ ├── DebugProfile.entitlements
│ ├── Info.plist
│ ├── MainFlutterWindow.swift
│ └── Release.entitlements
└── RunnerTests
│ └── RunnerTests.swift
├── my_ollama.iml
├── pubspec.yaml
└── test
└── widget_test.dart
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://www.dartlang.org/guides/libraries/private-files
2 |
3 | # Files and directories created by pub
4 | .dart_tool/
5 | .packages
6 | build/
7 | # If you're building an application, you may want to check-in your pubspec.lock
8 | pubspec.lock
9 |
10 | # Directory created by dartdoc
11 | # If you don't generate documentation locally you can remove this line.
12 | doc/api/
13 |
14 | # dotenv environment variables file
15 | .env*
16 |
17 | # Avoid committing generated Javascript files:
18 | *.dart.js
19 | *.info.json # Produced by the --dump-info flag.
20 | *.js # When generated by dart2js. Don't specify *.js if your
21 | # project includes source files written in JavaScript.
22 | *.js_
23 | *.js.deps
24 | *.js.map
25 |
26 | .flutter-plugins
27 | .flutter-plugins-dependencies
28 |
29 | .idea/
30 | .DS_Store
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | # ✨ MyOllama ✨
5 |
6 | _Ollama-based LLM mobile client_
7 |
8 | [한국어](./README_KR.md) •
9 | [日本語](./README_JP.md)
10 |
11 |
12 |
13 | # MyOllama
14 |
15 | MyOllama is a mobile client app that allows you to connect to a computer with Ollama installed and interact with the Large Language Model (LLM). You can download and build the source code or download the MyOllama app from the [Apple App Store](https://apps.apple.com/us/app/my-ollama/id6738298481).
16 |
17 | ## Introduction
18 |
19 | Ollama is open source software that makes it easy to run Large Language Models (LLMs) on your local machine.
20 | You can use MyOllama to access Ollama and utilize various LLMs. MyOllama - Run LLMs on your own computer through the Ollama program, so you can talk to AI models without paying a fee.
21 |
22 | 
23 |
24 | ## Key features
25 |
26 | - Remote LLM access: Connect to the Ollama host via IP address
27 | - Custom prompts: support for setting custom instructions
28 | - Supports various open source LLMs (Llama, Gemma, Qwen, Mistral, etc.)
29 | - Customizable instruction settings
30 | - Supports image recognition (only on models that support it)
31 | - Intuitive chat-like UI
32 | - Conversation history: save and manage chat sessions
33 | - Supports iPhone, iPad, Android, Mac Desktop
34 | - Supports Korean, English, and Japanese
35 | - Support Markdown format
36 |
37 | 
38 |
39 | ## How to use
40 |
41 | 1. Install Ollama on your computer (macOS, Windows, Linux supported). You can see how to install Ollama on [Ollama GitHub](https://ollama.com/download).
42 | 2. Download the source and build it with Flutter, or download the MyOllama app from the [App Store](https://apps.apple.com/us/app/my-ollama/id6738298481).
43 | 3. Install the desired model in Ollama. [Download model](https://ollama.com/search)
44 | 4. Change the settings to make Ollama remotely accessible. See: [link](http://practical.kr/?p=809)
45 | 5. Launch the MyOllama app and enter the IP address of the computer where Ollama is installed.
46 | 6. Select the desired AI model and start a conversation.
47 |
48 | ## System requirements
49 |
50 | - Computer with Ollama installed
51 | - Network connection
52 |
53 | ## Advantages
54 |
55 | - This app is designed for developers and researchers who want to efficiently utilize open-source LLMs. It can be utilized for various technical experiments such as API calls, prompt engineering, model performance testing, etc.
56 | - Advanced AI features available for free
57 | - Supports a wide range of LLM models
58 | - Privacy-protected (runs on your local machine)
59 | - Versatile for programming, creative work, casual questions, etc.
60 | - Organized to keep conversations in context
61 |
62 | ## Notes
63 |
64 | - This app requires a computer with Ollama installed.
65 | - You are responsible for setting up and managing your Ollama host. Be aware of security settings.
66 |
67 | ## Download App
68 |
69 | - For those who have difficulty building, you can download the app from the link below.
70 | - [https://apps.apple.com/kr/app/my-ollama/id6738298481](https://apps.apple.com/kr/app/my-ollama/id6738298481)
71 |
72 | ## License
73 |
74 | MyOllama is licensed under the GNU license. For more information, please refer to the [LICENSE](LICENSE) file.
75 |
76 | ## Contact
77 |
78 | For questions or bug reports about MyOllama, please send an email to rtlink.park@gmail.com.
79 |
80 |
--------------------------------------------------------------------------------
/README_JP.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # ✨ マイオラマ ✨
4 |
5 | _OllamaベースのLLMモバイルクライアント_
6 |
7 | [ENGLISH](./README.md) •
8 | [한국어](./README_KR.md)
9 |
10 |
11 |
12 | # MyOllama
13 |
14 | MyOllamaは、Ollamaがインストールされたコンピュータに接続し、大規模言語モデル(LLM)と対話できるモバイルクライアントアプリです。ソースコードをダウンロードしてビルドするか、[アップルアプリストア](https://apps.apple.com/us/app/my-ollama/id6738298481)からMyOllamaアプリをダウンロードすることができます。
15 |
16 | ## 紹介
17 |
18 | Ollamaは大規模言語モデル(Large Language Models, LLMs)をローカルコンピュータで簡単に実行できるようにするオープンソースソフトウェアです。
19 | MyOllamaを使用してOllamaに接続し、様々なLLMを活用することができます。MyOllama - Ollamaプログラムを通じて自分のコンピュータでLLMを実行するので、別途使用料なしでAIモデルと対話することができます。
20 |
21 | 
22 |
23 | ## 主な機能
24 |
25 | - リモートLLM接続:IPアドレスでOllamaホストに接続します。
26 | - カスタムプロンプト: ユーザー定義のInstruction設定サポート
27 | - 様々なオープンソースLLMをサポート(Llama, Gemma, Qwen, Mistralなど)
28 | - ユーザー定義のInstruction設定可能
29 | - 画像認識機能をサポート(該当機能をサポートするモデルに限る)
30 | - 直感的なチャット形式のUI
31 | - 会話記録:チャットセッションの保存と管理
32 | - Supports iPhone, iPad, Android, Mac Desktop
33 | - 韓国語、English、日本語をサポート
34 |
35 | 
36 |
37 | ## 使い方
38 |
39 | 1. Ollamaをパソコンにインストールします(macOS、Windows、Linuxをサポート)。Ollamaのインストール方法は[Ollama GitHub](https://ollama.com/download)で確認することができます。
40 | 2. ソースをダウンロードしてFlutterを使ってビルドするか、[App Store](https://apps.apple.com/us/app/my-ollama/id6738298481)からMyOllamaアプリをダウンロードします。
41 | 3. Ollamaに好きなモデルをインストールします。[モデルダウンロード](https://ollama.com/search)
42 | 4. Ollamaをリモートアクセスができるように設定を変更します。参照ください: [リンク](http://practical.kr/?p=809)
43 | 5. MyOllamaアプリを実行し、OllamaがインストールされているコンピュータのIPアドレスを入力します。
44 | 6. 希望のAIモデルを選択して会話を開始します。
45 |
46 | ## システム要件
47 |
48 | - Ollamaがインストールされているコンピュータ
49 | - ネットワーク接続
50 |
51 | ## 利点
52 |
53 | - このアプリは、オープンソースのLLMを効率的に活用したい開発者や研究者向けに設計されています。API呼び出し、プロンプトエンジニアリング、モデルの性能テストなど、様々な技術的な実験に活用できます。
54 | - 無料で高度なAI機能を使用可能
55 | - 様々なLLMモデルをサポート
56 | - プライバシー保護 (ローカルコンピュータで実行)
57 | - プログラミング、クリエイティブな作業、日常的な質問など、様々な用途に活用できます。
58 | - 会話の文脈を継続できるように構成
59 |
60 | ## 注意事項
61 |
62 | - このアプリを使用するには、必ずOllamaがインストールされたコンピュータが必要です。
63 | - Ollamaホストの設定と管理はユーザーの責任で行ってください。セキュリティ設定にご注意ください。
64 |
65 | ## アプリのダウンロード
66 |
67 | - ビルドが難しい方は下記のリンクからアプリをダウンロードすることができます。
68 | - [https://apps.apple.com/kr/app/my-ollama/id6738298481](https://apps.apple.com/kr/app/my-ollama/id6738298481)
69 |
70 | ## ライセンス
71 |
72 | MyOllamaはGNUライセンスに従います。詳細は[LICENSE](LICENSE)ファイルを参照してください。
73 |
74 | ## お問い合わせ
75 |
76 | MyOllamaに関するお問い合わせやバグ報告は rtlink.park@gmail.com までメールを送ってください。
77 |
78 | DeepL.com(無料版)で翻訳しました。
79 |
--------------------------------------------------------------------------------
/README_KR.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # ✨ 마이올라마 ✨
4 |
5 | _Ollama 기반 LLM 모바일 클라이언트_
6 |
7 | [ENGLISH](./README.md) •
8 | [日本語](./README_JP.md)
9 |
10 |
11 |
12 | # 마이올라마
13 |
14 | MyOllama는 Ollama가 설치된 컴퓨터에 접속하여 대규모 언어 모델(LLM)과 상호작용할 수 있는 모바일 클라이언트 앱입니다. 소스코드를 다운받아 빌드 하거나 [애플 앱스토어](https://apps.apple.com/us/app/my-ollama/id6738298481)에서 MyOllama 앱을 다운로드할 수 있습니다.
15 |
16 | ## 소개
17 |
18 | Ollama는 대규모 언어 모델(Large Language Models, LLMs)을 로컬 컴퓨터에서 쉽게 실행할 수 있게 해주는 오픈 소스 소프트웨어입니다.
19 | MyOllama를 사용하여 Ollama에 접속하여 다양한 LLM을 활용할 수 있습니다. MyOllama - Ollama 프로그램을 통해 자신의 컴퓨터에서 LLM을 실행하므로, 별도의 사용료 없이 AI 모델과 대화할 수 있습니다.
20 |
21 | 
22 |
23 | ## 주요 기능
24 |
25 | - 원격 LLM 접속: IP 주소를 통해 Ollama 호스트에 연결
26 | - 커스텀 프롬프트: 사용자 정의 Instruction 설정 지원
27 | - 다양한 오픈 소스 LLM 지원 (Llama, Gemma, Qwen, Mistral 등)
28 | - 사용자 정의 Instruction 설정 가능
29 | - 이미지 인식 기능 지원 (해당 기능을 지원하는 모델에 한함)
30 | - 직관적인 채팅 형식의 UI
31 | - 대화 기록: 채팅 세션 저장 및 관리
32 | - iPhone, iPad, Android, Mac Desktop 지원
33 | - 한국어, English, 日本語를 지원
34 |
35 | 
36 |
37 | ## 사용 방법
38 |
39 | 1. Ollama를 컴퓨터에 설치합니다 (macOS, Windows, Linux 지원). Ollama 설치 방법은 [Ollama GitHub](https://ollama.com/download)에서 확인할 수 있습니다.
40 | 2. 소스를 다운 받아서 Flutter를 이용하여 빌드하거나, [App Store](https://apps.apple.com/us/app/my-ollama/id6738298481)에서 MyOllama 앱을 다운로드합니다.
41 | 3. Ollama에 원하는 모델을 설치합니다. [모델다운로드](https://ollama.com/search)
42 | 4. Ollama를 원격 접속이 가능하도록 설정을 변경합니다. 참조: [링크](http://practical.kr/?p=809)
43 | 5. MyOllama 앱을 실행하고 Ollama가 설치된 컴퓨터의 IP 주소를 입력합니다.
44 | 6. 원하는 AI 모델을 선택하고 대화를 시작합니다.
45 |
46 | ## 시스템 요구사항
47 |
48 | - Ollama가 설치된 컴퓨터
49 | - 네트워크 연결
50 |
51 | ## 장점
52 |
53 | - 이 앱은 오픈소스 LLM을 효율적으로 활용하고자 하는 개발자 및 연구자를 위해 설계되었습니다. API 호출, 프롬프트 엔지니어링, 모델 성능 테스트 등 다양한 기술적 실험에 활용 가능합니다.
54 | - 무료로 고급 AI 기능 사용 가능
55 | - 다양한 LLM 모델 지원
56 | - 프라이버시 보호 (로컬 컴퓨터에서 실행)
57 | - 프로그래밍, 창의적 작업, 일상적인 질문 등 다양한 용도로 활용 가능
58 | - 대화의 맥락을 이어갈수 있도록 구성
59 |
60 | ## 주의사항
61 |
62 | - 이 앱을 사용하려면 반드시 Ollama가 설치된 컴퓨터가 필요합니다.
63 | - Ollama 호스트 설정 및 관리는 사용자 책임입니다. 보안 설정에 유의하세요.
64 |
65 | ## App 다운로드
66 |
67 | - 빌드가 어려운 분들은 아래 링크에서 앱을 다운 받을수 있습니다.
68 | - [https://apps.apple.com/kr/app/my-ollama/id6738298481](https://apps.apple.com/kr/app/my-ollama/id6738298481)
69 |
70 | ## 라이선스
71 |
72 | MyOllama는 GNU 라이선스를 따릅니다. 자세한 내용은 [LICENSE](LICENSE) 파일을 참조해 주세요.
73 |
74 | ## 연락처
75 |
76 | MyOllama에 대한 문의나 버그 신고는 rtlink.park@gmail.com 으로 메일을 보내주세요.
77 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 | android {
9 | namespace = "com.example.my_ollama"
10 | compileSdk = flutter.compileSdkVersion
11 | ndkVersion = flutter.ndkVersion
12 |
13 | compileOptions {
14 | sourceCompatibility = JavaVersion.VERSION_1_8
15 | targetCompatibility = JavaVersion.VERSION_1_8
16 | }
17 |
18 | kotlinOptions {
19 | jvmTarget = JavaVersion.VERSION_1_8
20 | }
21 |
22 | defaultConfig {
23 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
24 | applicationId = "com.example.my_ollama"
25 | // You can update the following values to match your application needs.
26 | // For more information, see: https://flutter.dev/to/review-gradle-config.
27 | minSdk = flutter.minSdkVersion
28 | targetSdk = flutter.targetSdkVersion
29 | versionCode = flutter.versionCode
30 | versionName = flutter.versionName
31 | }
32 |
33 | buildTypes {
34 | release {
35 | // TODO: Add your own signing config for the release build.
36 | // Signing with the debug keys for now, so `flutter run --release` works.
37 | signingConfig = signingConfigs.debug
38 | }
39 | }
40 | }
41 |
42 | flutter {
43 | source = "../.."
44 | }
45 |
--------------------------------------------------------------------------------
/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
17 |
21 |
25 |
26 |
27 |
28 |
29 |
30 |
32 |
35 |
36 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/android/app/src/main/kotlin/com/example/my_ollama/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.example.my_ollama
2 |
3 | import io.flutter.embedding.android.FlutterActivity
4 |
5 | class MainActivity: FlutterActivity()
6 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-v21/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bipark/my_ollama_app/95a479cd7ab5513fd491247f95b51054d4a6a288/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bipark/my_ollama_app/95a479cd7ab5513fd491247f95b51054d4a6a288/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bipark/my_ollama_app/95a479cd7ab5513fd491247f95b51054d4a6a288/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bipark/my_ollama_app/95a479cd7ab5513fd491247f95b51054d4a6a288/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bipark/my_ollama_app/95a479cd7ab5513fd491247f95b51054d4a6a288/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/values-night/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/android/app/src/profile/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | 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 |
--------------------------------------------------------------------------------
/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError
2 | android.useAndroidX=true
3 | android.enableJetifier=true
4 |
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | zipStoreBase=GRADLE_USER_HOME
4 | zipStorePath=wrapper/dists
5 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-all.zip
6 |
--------------------------------------------------------------------------------
/android/my_ollama_android.iml:
--------------------------------------------------------------------------------
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 |
--------------------------------------------------------------------------------
/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.7.2' apply false
22 | id "org.jetbrains.kotlin.android" version "1.8.22" apply false
23 | }
24 |
25 | include ":app"
26 |
--------------------------------------------------------------------------------
/assets/images/appstore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bipark/my_ollama_app/95a479cd7ab5513fd491247f95b51054d4a6a288/assets/images/appstore.png
--------------------------------------------------------------------------------
/assets/images/claude.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bipark/my_ollama_app/95a479cd7ab5513fd491247f95b51054d4a6a288/assets/images/claude.png
--------------------------------------------------------------------------------
/assets/images/ollama.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bipark/my_ollama_app/95a479cd7ab5513fd491247f95b51054d4a6a288/assets/images/ollama.png
--------------------------------------------------------------------------------
/assets/images/openai.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bipark/my_ollama_app/95a479cd7ab5513fd491247f95b51054d4a6a288/assets/images/openai.png
--------------------------------------------------------------------------------
/assets/images/pdfthumb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bipark/my_ollama_app/95a479cd7ab5513fd491247f95b51054d4a6a288/assets/images/pdfthumb.png
--------------------------------------------------------------------------------
/assets/translations/en-US.json:
--------------------------------------------------------------------------------
1 | {
2 | "l_settings": "Settings",
3 | "l_ollama_setting": "Olama settings",
4 | "l_server_address": "Server address",
5 | "l_instructions": "LLM instructions",
6 | "l_temperature": "Temperature",
7 | "l_download": "Download",
8 | "l_download_ollama": "Download Olama server to your computer from the link",
9 | "l_howto": "How to",
10 | "l_howto_1": "How to allow external access to the Olama server",
11 | "l_app_info": "App information",
12 | "l_open_source": "Open source",
13 | "l_open_comment": "MyOlama is an open source project. You can check the source code at the link",
14 | "l_delete": "Delete",
15 | "l_delete_question": "Do you want to delete the selected data?",
16 | "l_delete_all": "Delete all data",
17 | "l_myollama": "MyOllama",
18 | "l_version": "Version: ",
19 | "l_input_question" : "Enter a question",
20 | "l_no_conversation" : "Start a new conversation\nLLMs that support images can also attach images",
21 | "l_no_server": "No server is connected. \nPlease connect to a server in the settings and try again",
22 | "l_error_1": "An error occurred while communicating with the server, please check the server address in settings and try again",
23 | "l_confirm": "Confirm",
24 | "l_cancel": "Cancel",
25 | "l_no_model": "No model",
26 | "l_q1": "What is MyOlama?",
27 | "l_a1": "MyOlama is a client app that communicates with the Olama server. Users can ask questions to the LLM installed on the server through MyOlama",
28 | "l_q2": "How do I connect to the Olama server?",
29 | "l_a2": "Enter the address of the Olama server in the settings and save it, then you can connect to the server from the main screen",
30 | "l_q3": "How do I analyze images?",
31 | "l_a3": "The ollama server should have a pre-installed LLM that can analyze images, for example, llama3.2. \n\nThen you can select an image in the app, ask questions and get answers.",
32 | "l_success": "Connection success",
33 | "l_error": "Connection failure",
34 | "l_success_url": "You have been connected to the server",
35 | "l_error_url": "Unable to connect to the server",
36 | "l_invalid_url": "Invalid address",
37 | "l_copyed": "Copied",
38 | "l_server_check": "Server Check",
39 | "l_new_chat": "New chat",
40 | "l_share_all": "Share all",
41 | "l_search": "Search"
42 | }
--------------------------------------------------------------------------------
/assets/translations/ja-JP.json:
--------------------------------------------------------------------------------
1 | {
2 | "l_settings": "設定",
3 | "l_ollama_setting": "オラマの設定",
4 | "l_server_address": "サーバーアドレス",
5 | "l_instructions": "LLMの指示",
6 | "l_temperature": "温度",
7 | "l_download": "ダウンロード",
8 | "l_download_ollama": "リンクからオラマサーバーをパソコンにダウンロードできます。",
9 | "l_howto": "使い方",
10 | "l_howto_1": "ollamaサーバーに外部アクセスを許可する方法",
11 | "l_app_info": "アプリ情報",
12 | "l_open_source": "オープンソース",
13 | "l_open_comment": "マイオラマはオープンソースプロジェクトです。リンクからソースコードを確認することができます。",
14 | "l_delete": "削除",
15 | "l_delete_question": "選択したデータを削除しますか?",
16 | "l_delete_all": "全データを削除します",
17 | "l_myollama": "マイオラマ",
18 | "l_version": "バージョン:",
19 | "l_input_question" : "質問を入力してください",
20 | "l_no_conversation" : "新しい会話を開始します\n画像をサポートするLLMは画像を添付することもできます",
21 | "l_no_server": "接続されているサーバーがありません。\n設定でサーバーに接続して再試行してください",
22 | "l_error_1": "サーバーとの通信中にエラーが発生しました。 設定でサーバーのアドレスを確認して再試行してください",
23 | "l_confirm": "確認",
24 | "l_cancel": "キャンセル",
25 | "l_no_model": "モデルがありません",
26 | "l_q1": "マイオラマとは何ですか?",
27 | "l_a1": "MyOlamaはOlamaサーバーと通信するクライアントアプリです。ユーザーはマイオラマを通じてサーバーにインストールされたLLMに質問をすることができます。",
28 | "l_q2": "Olamaサーバーに接続するにはどうすればいいですか?",
29 | "l_a2": "設定でOlamaサーバーのアドレスを入力して保存してください。 その後,メイン画面からサーバーに接続することができます。",
30 | "l_q3": "どのように画像を分析するのですか?",
31 | "l_a3": "llamaサーバーには,画像を分析できるLLMがプリインストールされている必要があります(例:llama3.2)。\n\nそして,アプリで画像を選択して質問して答えを得ることができます。",
32 | "l_success": "接続成功",
33 | "l_error": "接続失敗",
34 | "l_success_url": "サーバーに接続しました",
35 | "l_error_url": "サーバーに接続できません",
36 | "l_invalid_url": "無効なアドレスです。",
37 | "l_copyed": "コピー済み",
38 | "l_server_check": "サーバー接続状態の確認",
39 | "l_new_chat": "新しい会話",
40 | "l_share_all": "全体共有",
41 | "l_search": "検索"
42 | }
--------------------------------------------------------------------------------
/assets/translations/ko-KR.json:
--------------------------------------------------------------------------------
1 | {
2 | "l_settings": "설정",
3 | "l_ollama_setting": "올라마 설정",
4 | "l_server_address": "서버 주소",
5 | "l_instructions": "LLM 지침",
6 | "l_temperture": "온도",
7 | "l_download": "다운로드",
8 | "l_download_ollama": "링크에서 올라마 서버를 컴퓨터에 다운로드 받을수 있습니다.",
9 | "l_howto": "사용법",
10 | "l_howto_1": "올라마 서버에 외부 접속을 허용하는 방법",
11 | "l_app_info": "앱 정보",
12 | "l_open_source": "오픈소스",
13 | "l_open_comment": "마이올라마는 오픈소스 프로젝트입니다. 링크에서 소스코드를 확인할 수 있습니다.",
14 | "l_delete": "삭제",
15 | "l_delete_question": "선택한 데이터를 삭제할까요?",
16 | "l_delete_all": "전체 데이터를 삭제합니다",
17 | "l_myollama": "마이올라마",
18 | "l_version": "버전 : ",
19 | "l_input_question" : "질문을 입력하세요",
20 | "l_no_conversation" : "새 대화를 시작합니다\n이미지를 지원하는 LLM은 이미지를 첨부 할수도 있습니다",
21 | "l_no_server": "연결된 서버가 없습니다. \n설정에서 서버에 연결하고 다시 시도하세요",
22 | "l_error_1": "서버와 통신하는 동안 오류가 발생했습니다. 설정에서 서버 주소를 확인한 후 다시 시도하세요.",
23 | "l_confirm": "확인",
24 | "l_cancel": "취소",
25 | "l_no_model": "모델이 없습니다",
26 | "l_q1": "마이올라마는 무엇인가요?",
27 | "l_a1": "마이올라마는 올라마 서버와 통신하는 클라이언트 앱입니다. 사용자는 마이올라마를 통해 서버에 설치된 LLM에 질문을 할 수 있습니다.",
28 | "l_q2": "올라마 서버에 연결하려면 어떻게 하나요?",
29 | "l_a2": "설정에서 올라마 서버의 주소를 입력하고 저장하세요. 그런 다음 메인 화면에서 서버에 연결할 수 있습니다.",
30 | "l_q3": "이미지를 어떻게 분석하나요?",
31 | "l_a3": "ollama 서버에는 이미지를 분석할 수 있는 LLM이 사전 설치되어 있어야 합니다(예: llama3.2.). \n\n그리고 앱에서 이미지를 선택하고 질문하고 답변을 얻을 수 있습니다.",
32 | "l_success": "연결성공",
33 | "l_error": "연결실패",
34 | "l_success_url": "서버에 연결되었습니다.",
35 | "l_error_url": "서버에 연결할 수 없습니다.",
36 | "l_invalid_url": "유효하지 않은 주소입니다.",
37 | "l_copyed": "복사되었습니다",
38 | "l_server_check": "서버연결상태확인",
39 | "l_new_chat": "새 대화",
40 | "l_share_all": "전체 공유",
41 | "l_search": "검색"
42 | }
--------------------------------------------------------------------------------
/changelog.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | ### 2024/11/18 - v1.0.0
4 |
5 | - v1.0.0 open
6 |
7 | ### 2024/11/28 - v1.0.5
8 |
9 | - Check server connection status
10 | - Add message copy, share, delete functions
11 |
12 | ### 2024/11/29 - v1.0.6
13 |
14 | - Support Markdown format
15 |
16 | ### 2024/11/30 - v1.0.7
17 |
18 | - Change item menu shape
19 | - Add Share All menu
20 | - Add Re question menu
21 | - Add Web link function
22 | - Release Android Version
23 |
24 | ### 2024/12/04 - v1.0.8
25 |
26 | - Change the chat view orientation
27 | - Add token usage
28 | - Add Search function
29 |
30 | ### 2024/12/16 - v1.1.0
31 |
32 | - Support iPad
33 | - Support Mac Desktop
34 | - fix some bugs
35 |
36 | ### 2025/03/03 - 1.2.0
37 |
38 | - Add Chat Session
39 | - fix some bugs
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/image.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bipark/my_ollama_app/95a479cd7ab5513fd491247f95b51054d4a6a288/image.jpg
--------------------------------------------------------------------------------
/image_en.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bipark/my_ollama_app/95a479cd7ab5513fd491247f95b51054d4a6a288/image_en.jpg
--------------------------------------------------------------------------------
/image_jp.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bipark/my_ollama_app/95a479cd7ab5513fd491247f95b51054d4a6a288/image_jp.jpg
--------------------------------------------------------------------------------
/ios/.gitignore:
--------------------------------------------------------------------------------
1 | **/dgph
2 | *.mode1v3
3 | *.mode2v3
4 | *.moved-aside
5 | *.pbxuser
6 | *.perspectivev3
7 | **/*sync/
8 | .sconsign.dblite
9 | .tags*
10 | **/.vagrant/
11 | **/DerivedData/
12 | Icon?
13 | **/Pods/
14 | **/.symlinks/
15 | profile
16 | xcuserdata
17 | **/.generated/
18 | Flutter/App.framework
19 | Flutter/Flutter.framework
20 | Flutter/Flutter.podspec
21 | Flutter/Generated.xcconfig
22 | Flutter/ephemeral/
23 | Flutter/app.flx
24 | Flutter/app.zip
25 | Flutter/flutter_assets/
26 | Flutter/flutter_export_environment.sh
27 | ServiceDefinitions.json
28 | Runner/GeneratedPluginRegistrant.*
29 |
30 | # Exceptions to above rules.
31 | !default.mode1v3
32 | !default.mode2v3
33 | !default.pbxuser
34 | !default.perspectivev3
35 |
--------------------------------------------------------------------------------
/ios/Flutter/AppFrameworkInfo.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | App
9 | CFBundleIdentifier
10 | io.flutter.flutter.app
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | App
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1.0
23 | MinimumOSVersion
24 | 12.0
25 |
26 |
27 |
--------------------------------------------------------------------------------
/ios/Flutter/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/ios/Flutter/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/ios/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CADisableMinimumFrameDurationOnPhone
6 |
7 | CFBundleDevelopmentRegion
8 | $(DEVELOPMENT_LANGUAGE)
9 | CFBundleDisplayName
10 | Crack2023
11 | CFBundleExecutable
12 | $(EXECUTABLE_NAME)
13 | CFBundleIdentifier
14 | $(PRODUCT_BUNDLE_IDENTIFIER)
15 | CFBundleInfoDictionaryVersion
16 | 6.0
17 | CFBundleName
18 | crack2023
19 | CFBundlePackageType
20 | APPL
21 | CFBundleShortVersionString
22 | $(FLUTTER_BUILD_NAME)
23 | CFBundleSignature
24 | ????
25 | CFBundleURLTypes
26 |
27 |
28 | CFBundleTypeRole
29 | Editor
30 | CFBundleURLSchemes
31 |
32 | com.googleusercontent.apps.270642908804-35rke892o8frgd8j8pqbecn6f8hvqum4
33 |
34 |
35 |
36 | CFBundleVersion
37 | $(FLUTTER_BUILD_NUMBER)
38 | LSRequiresIPhoneOS
39 |
40 | NSCameraUsageDescription
41 | Use my camera for crack pictures
42 | NSLocationWhenInUseUsageDescription
43 | Use location information to store measurement locations
44 | NSLocationAlwaysAndWhenInUseUsageDescription
45 | Use location information to store measurement locations
46 | NSPhotoLibraryUsageDescription
47 | Use photo albums to save crack pictures
48 | UIApplicationSupportsIndirectInputEvents
49 |
50 | UILaunchStoryboardName
51 | LaunchScreen
52 | UIMainStoryboardFile
53 | Main
54 | UISupportedInterfaceOrientations
55 |
56 | UIInterfaceOrientationPortrait
57 |
58 | UISupportedInterfaceOrientations~ipad
59 |
60 | UIInterfaceOrientationLandscapeLeft
61 | UIInterfaceOrientationLandscapeRight
62 | UIInterfaceOrientationPortrait
63 | UIInterfaceOrientationPortraitUpsideDown
64 |
65 | UIViewControllerBasedStatusBarAppearance
66 |
67 |
68 |
69 |
--------------------------------------------------------------------------------
/ios/Podfile:
--------------------------------------------------------------------------------
1 | # Uncomment this line to define a global platform for your project
2 | # platform :ios, '12.0'
3 |
4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true'
6 |
7 | project 'Runner', {
8 | 'Debug' => :debug,
9 | 'Profile' => :release,
10 | 'Release' => :release,
11 | }
12 |
13 | def flutter_root
14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
15 | unless File.exist?(generated_xcode_build_settings_path)
16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
17 | end
18 |
19 | File.foreach(generated_xcode_build_settings_path) do |line|
20 | matches = line.match(/FLUTTER_ROOT\=(.*)/)
21 | return matches[1].strip if matches
22 | end
23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
24 | end
25 |
26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
27 |
28 | flutter_ios_podfile_setup
29 |
30 | target 'Runner' do
31 | use_frameworks!
32 | use_modular_headers!
33 |
34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
35 | target 'RunnerTests' do
36 | inherit! :search_paths
37 | end
38 | end
39 |
40 | post_install do |installer|
41 | installer.pods_project.targets.each do |target|
42 | flutter_additional_ios_build_settings(target)
43 | end
44 | end
45 |
--------------------------------------------------------------------------------
/ios/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - app_settings (5.1.1):
3 | - Flutter
4 | - Flutter (1.0.0)
5 | - image_picker_ios (0.0.1):
6 | - Flutter
7 | - package_info_plus (0.4.5):
8 | - Flutter
9 | - path_provider_foundation (0.0.1):
10 | - Flutter
11 | - FlutterMacOS
12 | - share_plus (0.0.1):
13 | - Flutter
14 | - shared_preferences_foundation (0.0.1):
15 | - Flutter
16 | - FlutterMacOS
17 | - sqflite_darwin (0.0.4):
18 | - Flutter
19 | - FlutterMacOS
20 | - url_launcher_ios (0.0.1):
21 | - Flutter
22 |
23 | DEPENDENCIES:
24 | - app_settings (from `.symlinks/plugins/app_settings/ios`)
25 | - Flutter (from `Flutter`)
26 | - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
27 | - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
28 | - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
29 | - share_plus (from `.symlinks/plugins/share_plus/ios`)
30 | - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
31 | - sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`)
32 | - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
33 |
34 | EXTERNAL SOURCES:
35 | app_settings:
36 | :path: ".symlinks/plugins/app_settings/ios"
37 | Flutter:
38 | :path: Flutter
39 | image_picker_ios:
40 | :path: ".symlinks/plugins/image_picker_ios/ios"
41 | package_info_plus:
42 | :path: ".symlinks/plugins/package_info_plus/ios"
43 | path_provider_foundation:
44 | :path: ".symlinks/plugins/path_provider_foundation/darwin"
45 | share_plus:
46 | :path: ".symlinks/plugins/share_plus/ios"
47 | shared_preferences_foundation:
48 | :path: ".symlinks/plugins/shared_preferences_foundation/darwin"
49 | sqflite_darwin:
50 | :path: ".symlinks/plugins/sqflite_darwin/darwin"
51 | url_launcher_ios:
52 | :path: ".symlinks/plugins/url_launcher_ios/ios"
53 |
54 | SPEC CHECKSUMS:
55 | app_settings: 5127ae0678de1dcc19f2293271c51d37c89428b2
56 | Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
57 | image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a
58 | package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
59 | path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
60 | share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a
61 | shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
62 | sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
63 | url_launcher_ios: 694010445543906933d732453a59da0a173ae33d
64 |
65 | PODFILE CHECKSUM: 819463e6a0290f5a72f145ba7cde16e8b6ef0796
66 |
67 | COCOAPODS: 1.16.2
68 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
37 |
38 |
39 |
40 |
43 |
49 |
50 |
51 |
52 |
53 |
63 |
65 |
71 |
72 |
73 |
74 |
80 |
82 |
88 |
89 |
90 |
91 |
93 |
94 |
97 |
98 |
99 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import 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 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bipark/my_ollama_app/95a479cd7ab5513fd491247f95b51054d4a6a288/ios/Runner/Assets.xcassets/AppIcon.appiconset/100.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bipark/my_ollama_app/95a479cd7ab5513fd491247f95b51054d4a6a288/ios/Runner/Assets.xcassets/AppIcon.appiconset/1024.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/114.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bipark/my_ollama_app/95a479cd7ab5513fd491247f95b51054d4a6a288/ios/Runner/Assets.xcassets/AppIcon.appiconset/114.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bipark/my_ollama_app/95a479cd7ab5513fd491247f95b51054d4a6a288/ios/Runner/Assets.xcassets/AppIcon.appiconset/120.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bipark/my_ollama_app/95a479cd7ab5513fd491247f95b51054d4a6a288/ios/Runner/Assets.xcassets/AppIcon.appiconset/128.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bipark/my_ollama_app/95a479cd7ab5513fd491247f95b51054d4a6a288/ios/Runner/Assets.xcassets/AppIcon.appiconset/144.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bipark/my_ollama_app/95a479cd7ab5513fd491247f95b51054d4a6a288/ios/Runner/Assets.xcassets/AppIcon.appiconset/152.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bipark/my_ollama_app/95a479cd7ab5513fd491247f95b51054d4a6a288/ios/Runner/Assets.xcassets/AppIcon.appiconset/16.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/167.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bipark/my_ollama_app/95a479cd7ab5513fd491247f95b51054d4a6a288/ios/Runner/Assets.xcassets/AppIcon.appiconset/167.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/180.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bipark/my_ollama_app/95a479cd7ab5513fd491247f95b51054d4a6a288/ios/Runner/Assets.xcassets/AppIcon.appiconset/180.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/20.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bipark/my_ollama_app/95a479cd7ab5513fd491247f95b51054d4a6a288/ios/Runner/Assets.xcassets/AppIcon.appiconset/20.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bipark/my_ollama_app/95a479cd7ab5513fd491247f95b51054d4a6a288/ios/Runner/Assets.xcassets/AppIcon.appiconset/256.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/29.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bipark/my_ollama_app/95a479cd7ab5513fd491247f95b51054d4a6a288/ios/Runner/Assets.xcassets/AppIcon.appiconset/29.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bipark/my_ollama_app/95a479cd7ab5513fd491247f95b51054d4a6a288/ios/Runner/Assets.xcassets/AppIcon.appiconset/32.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bipark/my_ollama_app/95a479cd7ab5513fd491247f95b51054d4a6a288/ios/Runner/Assets.xcassets/AppIcon.appiconset/40.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/50.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bipark/my_ollama_app/95a479cd7ab5513fd491247f95b51054d4a6a288/ios/Runner/Assets.xcassets/AppIcon.appiconset/50.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bipark/my_ollama_app/95a479cd7ab5513fd491247f95b51054d4a6a288/ios/Runner/Assets.xcassets/AppIcon.appiconset/512.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/57.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bipark/my_ollama_app/95a479cd7ab5513fd491247f95b51054d4a6a288/ios/Runner/Assets.xcassets/AppIcon.appiconset/57.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/58.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bipark/my_ollama_app/95a479cd7ab5513fd491247f95b51054d4a6a288/ios/Runner/Assets.xcassets/AppIcon.appiconset/58.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/60.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bipark/my_ollama_app/95a479cd7ab5513fd491247f95b51054d4a6a288/ios/Runner/Assets.xcassets/AppIcon.appiconset/60.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bipark/my_ollama_app/95a479cd7ab5513fd491247f95b51054d4a6a288/ios/Runner/Assets.xcassets/AppIcon.appiconset/64.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bipark/my_ollama_app/95a479cd7ab5513fd491247f95b51054d4a6a288/ios/Runner/Assets.xcassets/AppIcon.appiconset/72.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bipark/my_ollama_app/95a479cd7ab5513fd491247f95b51054d4a6a288/ios/Runner/Assets.xcassets/AppIcon.appiconset/76.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/80.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bipark/my_ollama_app/95a479cd7ab5513fd491247f95b51054d4a6a288/ios/Runner/Assets.xcassets/AppIcon.appiconset/80.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/87.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bipark/my_ollama_app/95a479cd7ab5513fd491247f95b51054d4a6a288/ios/Runner/Assets.xcassets/AppIcon.appiconset/87.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {"images":[{"size":"60x60","expected-size":"180","filename":"180.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"40x40","expected-size":"80","filename":"80.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"40x40","expected-size":"120","filename":"120.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"60x60","expected-size":"120","filename":"120.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"57x57","expected-size":"57","filename":"57.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"1x"},{"size":"29x29","expected-size":"58","filename":"58.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"29x29","expected-size":"29","filename":"29.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"1x"},{"size":"29x29","expected-size":"87","filename":"87.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"57x57","expected-size":"114","filename":"114.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"20x20","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"20x20","expected-size":"60","filename":"60.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"1024x1024","filename":"1024.png","expected-size":"1024","idiom":"ios-marketing","folder":"Assets.xcassets/AppIcon.appiconset/","scale":"1x"},{"size":"40x40","expected-size":"80","filename":"80.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"72x72","expected-size":"72","filename":"72.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"76x76","expected-size":"152","filename":"152.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"50x50","expected-size":"100","filename":"100.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"29x29","expected-size":"58","filename":"58.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"76x76","expected-size":"76","filename":"76.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"29x29","expected-size":"29","filename":"29.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"50x50","expected-size":"50","filename":"50.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"72x72","expected-size":"144","filename":"144.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"40x40","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"83.5x83.5","expected-size":"167","filename":"167.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"20x20","expected-size":"20","filename":"20.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"20x20","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"128x128","expected-size":"128","filename":"128.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"1x"},{"size":"256x256","expected-size":"256","filename":"256.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"1x"},{"size":"128x128","expected-size":"256","filename":"256.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"2x"},{"size":"256x256","expected-size":"512","filename":"512.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"2x"},{"size":"32x32","expected-size":"32","filename":"32.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"1x"},{"size":"512x512","expected-size":"512","filename":"512.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"1x"},{"size":"16x16","expected-size":"16","filename":"16.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"1x"},{"size":"16x16","expected-size":"32","filename":"32.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"2x"},{"size":"32x32","expected-size":"64","filename":"64.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"2x"},{"size":"512x512","expected-size":"1024","filename":"1024.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"2x"}]}
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "LaunchImage.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "LaunchImage@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "LaunchImage@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bipark/my_ollama_app/95a479cd7ab5513fd491247f95b51054d4a6a288/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bipark/my_ollama_app/95a479cd7ab5513fd491247f95b51054d4a6a288/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bipark/my_ollama_app/95a479cd7ab5513fd491247f95b51054d4a6a288/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md:
--------------------------------------------------------------------------------
1 | # Launch Screen Assets
2 |
3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory.
4 |
5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/ios/Runner/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CADisableMinimumFrameDurationOnPhone
6 |
7 | CFBundleDevelopmentRegion
8 | $(DEVELOPMENT_LANGUAGE)
9 | CFBundleDisplayName
10 | My Ollama
11 | CFBundleExecutable
12 | $(EXECUTABLE_NAME)
13 | CFBundleIdentifier
14 | $(PRODUCT_BUNDLE_IDENTIFIER)
15 | CFBundleInfoDictionaryVersion
16 | 6.0
17 | CFBundleName
18 | my_ollama
19 | CFBundlePackageType
20 | APPL
21 | CFBundleShortVersionString
22 | $(FLUTTER_BUILD_NAME)
23 | CFBundleSignature
24 | ????
25 | CFBundleVersion
26 | $(FLUTTER_BUILD_NUMBER)
27 | LSRequiresIPhoneOS
28 |
29 | UIApplicationSupportsIndirectInputEvents
30 |
31 | UILaunchStoryboardName
32 | LaunchScreen
33 | UIMainStoryboardFile
34 | Main
35 | UISupportedInterfaceOrientations
36 |
37 | UIInterfaceOrientationPortrait
38 | UIInterfaceOrientationLandscapeLeft
39 | UIInterfaceOrientationLandscapeRight
40 |
41 | UISupportedInterfaceOrientations~ipad
42 |
43 | UIInterfaceOrientationPortrait
44 | UIInterfaceOrientationPortraitUpsideDown
45 | UIInterfaceOrientationLandscapeLeft
46 | UIInterfaceOrientationLandscapeRight
47 |
48 | NSAppTransportSecurity
49 |
50 | NSAllowsArbitraryLoads
51 |
52 |
53 | NSPhotoLibraryUsageDescription
54 | The app needs permission to use the photo album to analyze photos.
55 |
56 |
57 |
--------------------------------------------------------------------------------
/ios/Runner/Runner-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | #import "GeneratedPluginRegistrant.h"
2 |
--------------------------------------------------------------------------------
/ios/Runner/en.lproj/InfoPlist.strings:
--------------------------------------------------------------------------------
1 | /*
2 | infoPlist.strings
3 | Runner
4 |
5 | Created by BillyPark on 11/16/24.
6 |
7 | */
8 | CFBundleDisplayName = "My Ollama";
9 | NSPhotoLibraryUsageDescription = "The app needs permission to use the photo album to analyze photos.";
10 |
--------------------------------------------------------------------------------
/ios/Runner/ja.lproj/InfoPlist.strings:
--------------------------------------------------------------------------------
1 | /*
2 | infoPlist.strings
3 | Runner
4 |
5 | Created by BillyPark on 11/16/24.
6 |
7 | */
8 | CFBundleDisplayName = "マイ・オーラマ";
9 | NSPhotoLibraryUsageDescription = "アプリが写真を分析するためにフォトアルバムの使用権限が必要です。";
10 |
--------------------------------------------------------------------------------
/ios/Runner/ko.lproj/InfoPlist.strings:
--------------------------------------------------------------------------------
1 | /*
2 | infoPlist.strings
3 | Runner
4 |
5 | Created by BillyPark on 11/16/24.
6 |
7 | */
8 | CFBundleDisplayName = "마이올라마";
9 | NSPhotoLibraryUsageDescription = "앱이 사진을 분석하기 위해 포토앨범 사용 권한 필요합니다.";
10 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/ipad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bipark/my_ollama_app/95a479cd7ab5513fd491247f95b51054d4a6a288/ipad.png
--------------------------------------------------------------------------------
/lib/helpers/event_bus.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:event_bus/event_bus.dart';
3 |
4 | //----------------------------------------------------------------------------//
5 | class MyEventBus extends EventBus {
6 | static final MyEventBus _instance = MyEventBus._internal();
7 |
8 | factory MyEventBus() {
9 | return _instance;
10 | }
11 |
12 | MyEventBus._internal() {
13 | EventBus _instance = EventBus();
14 | }
15 | }
16 |
17 | class UserLogStatusChangeEvent {
18 | UserLogStatusChangeEvent();
19 | }
20 |
21 | class ChangeTitleEvent {
22 | String title;
23 | List action;
24 | ChangeTitleEvent(this.title, this.action);
25 | }
26 |
27 | class RefreshMainListEvent {
28 | RefreshMainListEvent();
29 | }
30 |
31 | class LoadHistoryGroupListEvent {
32 | LoadHistoryGroupListEvent();
33 | }
34 |
35 | class CloseDrawerEvent {
36 | CloseDrawerEvent();
37 | }
38 |
39 | class NewChatBeginEvent {
40 | NewChatBeginEvent();
41 | }
42 |
43 | class ReloadModelEvent {
44 | ReloadModelEvent();
45 | }
46 |
47 | class ChatDoneEvent {
48 | ChatDoneEvent();
49 | }
--------------------------------------------------------------------------------
/lib/main.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 | import 'package:flutter/material.dart';
3 | import 'package:provider/provider.dart';
4 | import 'package:easy_localization/easy_localization.dart';
5 | import 'package:sqflite_common_ffi/sqflite_ffi.dart';
6 |
7 | import 'views/drawer.dart';
8 | import 'views/desktop_view.dart';
9 | import 'provider/main_provider.dart';
10 | import 'utils/platform_utils.dart';
11 |
12 | //--------------------------------------------------------------------------//
13 | void main() async {
14 | WidgetsFlutterBinding.ensureInitialized();
15 | await EasyLocalization.ensureInitialized();
16 |
17 | if (Platform.isWindows) {
18 | sqfliteFfiInit();
19 | databaseFactory = databaseFactoryFfi;
20 | }
21 |
22 | runApp(MultiProvider(
23 | providers: [
24 | ChangeNotifierProvider(create: (_) => MainProvider()),
25 | ],
26 | child: EasyLocalization(
27 | supportedLocales: [
28 | Locale('en', 'US'),
29 | Locale('ko', 'KR'),
30 | Locale('ja', 'JP')
31 | ],
32 | path: 'assets/translations',
33 | fallbackLocale: Locale('en', 'US'),
34 | child: MyOllama()),
35 | ));
36 | }
37 |
38 | //--------------------------------------------------------------------------//
39 | class MyOllama extends StatelessWidget {
40 | @override
41 | Widget build(BuildContext context) {
42 | return MaterialApp(
43 | debugShowCheckedModeBanner: false,
44 | supportedLocales: context.supportedLocales,
45 | locale: context.locale,
46 | localizationsDelegates: context.localizationDelegates,
47 | theme: ThemeData(
48 | colorScheme: ColorScheme.fromSeed(seedColor: Colors.indigo),
49 | appBarTheme: AppBarTheme(
50 | backgroundColor: Colors.indigo,
51 | foregroundColor: Colors.white,
52 | ),
53 | ),
54 | home: InitializationWrapper(),
55 | );
56 | }
57 | }
58 |
59 | //--------------------------------------------------------------------------//
60 | class InitializationWrapper extends StatefulWidget {
61 | @override
62 | _InitializationWrapperState createState() => _InitializationWrapperState();
63 | }
64 |
65 | //--------------------------------------------------------------------------//
66 | class _InitializationWrapperState extends State {
67 | @override
68 | void initState() {
69 | super.initState();
70 | Future.delayed(Duration.zero, () {
71 | setLocale(context);
72 | _initializeApp();
73 | });
74 | }
75 |
76 | Future setLocale(BuildContext context) async {
77 | String currentLocale = Intl.getCurrentLocale();
78 | List parts = currentLocale.split('_');
79 |
80 | switch (parts[0]) {
81 | case 'ko':
82 | context.setLocale(const Locale('ko', 'KR'));
83 | break;
84 | case 'ja':
85 | context.setLocale(const Locale('ja', 'JP'));
86 | break;
87 | default:
88 | context.setLocale(const Locale('en', 'US'));
89 | break;
90 | }
91 | }
92 |
93 | Future _initializeApp() async {
94 | final provider = Provider.of(context, listen: false);
95 | await provider.initialize();
96 | }
97 |
98 | @override
99 | Widget build(BuildContext context) {
100 | return Consumer(
101 | builder: (context, provider, _) {
102 | if (!provider.isInitialized) {
103 | return Scaffold(
104 | body: Center(
105 | child: Column(
106 | mainAxisAlignment: MainAxisAlignment.center,
107 | children: [
108 | CircularProgressIndicator(),
109 | SizedBox(height: 20),
110 | Text('Initializing...').tr(),
111 | ],
112 | ),
113 | ),
114 | );
115 | }
116 |
117 | return isDesktopOrTablet() ? DesktopView() : MyDrawer();
118 | },
119 | );
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/lib/models/chat_session.dart:
--------------------------------------------------------------------------------
1 | import 'package:ollama_dart/ollama_dart.dart';
2 | import 'package:flutter/material.dart';
3 | import 'dart:async';
4 |
5 | class ChatSession {
6 | final String chatId;
7 | final String modelName;
8 | final OllamaClient client;
9 | final StreamController messageController;
10 | bool isActive;
11 | bool hasError = false;
12 | String? errorMessage;
13 |
14 | ChatSession({
15 | required this.chatId,
16 | required this.modelName,
17 | required this.client,
18 | }) : messageController = StreamController.broadcast(),
19 | isActive = true;
20 |
21 | void dispose() {
22 | if (!messageController.isClosed) {
23 | messageController.close();
24 | }
25 | isActive = false;
26 | }
27 |
28 | void addMessage(String content, {bool isError = false}) {
29 | if (!isActive || messageController.isClosed) return;
30 |
31 | if (isError) {
32 | hasError = true;
33 | errorMessage = content;
34 | }
35 |
36 | messageController.add(ChatSessionUpdate(
37 | content: content,
38 | isError: isError,
39 | timestamp: DateTime.now(),
40 | ));
41 | }
42 |
43 | Stream get messageStream => messageController.stream;
44 | }
45 |
46 | class ChatSessionUpdate {
47 | final String content;
48 | final bool isError;
49 | final DateTime timestamp;
50 |
51 | ChatSessionUpdate({
52 | required this.content,
53 | this.isError = false,
54 | required this.timestamp,
55 | });
56 | }
57 |
--------------------------------------------------------------------------------
/lib/models/model_manager.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 |
3 | import 'package:easy_localization/easy_localization.dart';
4 | import 'package:sqflite/sqflite.dart';
5 | import 'package:intl/intl.dart';
6 | import 'package:uuid/uuid.dart';
7 |
8 | import 'package:path/path.dart';
9 | import '../provider/main_provider.dart';
10 |
11 |
12 | class QDatabase {
13 | static final QDatabase _instance = QDatabase._init();
14 | static Database? _database;
15 |
16 | final sql_questions = '''
17 | CREATE TABLE IF NOT EXISTS questions(
18 | id INTEGER PRIMARY KEY AUTOINCREMENT,
19 | groupid TEXT NOT NULL,
20 | instruction TEXT,
21 | question TEXT,
22 | answer TEXT,
23 | image TEXT,
24 | created TEXT,
25 | engine TEXT
26 | );''';
27 | //--------------------------------------------------------------------------//
28 | QDatabase._init() {
29 | }
30 |
31 | //--------------------------------------------------------------------------//
32 | factory QDatabase() {
33 | return _instance;
34 | }
35 |
36 | //--------------------------------------------------------------------------//
37 | Future init() async {
38 | if (_database != null) return _database!;
39 | _database = await _initDB('my_ollama.db');
40 | _makeFirstRow();
41 | return _database!;
42 | }
43 |
44 | //--------------------------------------------------------------------------//
45 | Future _initDB(String filePath) async {
46 |
47 | final dbPath = await getDatabasesPath();
48 | final path = dbPath + "/" + filePath;
49 | print("DB Path: $path");
50 |
51 | final options = OpenDatabaseOptions(
52 | version: 1,
53 | onCreate: _createDB
54 | );
55 |
56 | return await databaseFactory.openDatabase(path, options: options);
57 | }
58 |
59 | //--------------------------------------------------------------------------//
60 | Future _makeFirstRow() async {
61 | final db = await _instance.init();
62 |
63 | final groupid = Uuid().v4();
64 | final questions = await db.rawQuery("SELECT * FROM questions");
65 | if (questions.length == 0) {
66 | insertQuestion(groupid, "", tr("l_q1"), tr("l_a1"), null, "ollama");
67 | insertQuestion(groupid, "", tr("l_q2"), tr("l_a2"), null, "ollama");
68 | insertQuestion(groupid, "", tr("l_q3"), tr("l_a3"), null, "ollama");
69 | }
70 | }
71 |
72 | //--------------------------------------------------------------------------//
73 | Future _createDB(Database db, int version) async {
74 | try {
75 | // Master
76 | await db.execute(sql_questions);
77 | } catch (e) {
78 | print("Error Creating TABLES: $e");
79 | }
80 | }
81 |
82 | //--------------------------------------------------------------------------//
83 | Future _onUpgrade(Database db, int oldVersion, int newVersion) async {
84 | try {
85 | } catch (e) {
86 | print("Error Upgrading TABLES: $e");
87 | }
88 | }
89 |
90 | //--------------------------------------------------------------------------//
91 | Future insertQuestion(String groupid, String instruction, String question, String answer, String? image, String engine) async {
92 | try {
93 | String created = DateFormat('yyyy-MM-dd HH:mm:ss').format(DateTime.now());
94 | final db = await _instance.init();
95 | await db.rawInsert('INSERT INTO questions(groupid, instruction, question, answer, image, created, engine) VALUES(?, ?, ?, ?, ?, ?, ?)',
96 | [groupid, instruction, question, answer, image, created, engine]);
97 | } catch (e) {
98 | print("Error Inserting Master: $e");
99 | }
100 | }
101 |
102 | //--------------------------------------------------------------------------//
103 | Future getTitles() async {
104 | final db = await _instance.init();
105 | return await db.rawQuery("select * from questions group by groupid order by id desc");
106 | }
107 |
108 | //--------------------------------------------------------------------------//
109 | Future getDetails(String groupid) async {
110 | final db = await _instance.init();
111 | List details = await db.rawQuery("SELECT * FROM questions WHERE groupid = ? ORDER BY created DESC", [groupid]);
112 | return details;
113 | }
114 |
115 | //--------------------------------------------------------------------------//
116 | Future getDetailsById(int id) async {
117 | final db = await _instance.init();
118 | List details = await db.rawQuery("SELECT * FROM questions WHERE id = ?", [id]);
119 | return details;
120 | }
121 |
122 | //--------------------------------------------------------------------------//
123 | Future searchKeywords(String keyword) async {
124 | final db = await _instance.init();
125 | final sql = "SELECT * FROM questions m "+
126 | "WHERE question LIKE ? OR answer LIKE ? "+
127 | "ORDER BY created DESC";
128 | return await db.rawQuery(sql, ["%$keyword%", "%$keyword%"]);
129 | }
130 |
131 | //--------------------------------------------------------------------------//
132 | Future deleteQuestions(String groupid) async {
133 | try {
134 | final db = await _instance.init();
135 | await db.rawDelete('DELETE FROM questions WHERE groupid = ?', [groupid]);
136 | } catch (e) {
137 | print("Error Deleting Master: $e");
138 | }
139 | }
140 |
141 | //--------------------------------------------------------------------------//
142 | Future deleteRecord(int id) async {
143 | try {
144 | final db = await _instance.init();
145 | await db.rawDelete('DELETE FROM questions WHERE id = ?', [id]);
146 | } catch (e) {
147 | print("Error Deleting Master: $e");
148 | }
149 | }
150 |
151 | //--------------------------------------------------------------------------//
152 | Future deleteAllRecords() async {
153 | try {
154 | final db = await _instance.init();
155 | await db.rawDelete('DELETE FROM questions', []);
156 | } catch (e) {
157 | print("Error Deleting Master: $e");
158 | }
159 | }
160 |
161 | }
--------------------------------------------------------------------------------
/lib/provider/main_provider.dart:
--------------------------------------------------------------------------------
1 | import 'dart:ffi';
2 |
3 | import 'package:flutter/foundation.dart';
4 |
5 | import 'package:ollama_dart/ollama_dart.dart';
6 | import 'package:uuid/uuid.dart';
7 | import 'package:shared_preferences/shared_preferences.dart';
8 | import 'package:package_info_plus/package_info_plus.dart';
9 | import 'package:http/http.dart' as http;
10 |
11 | import '../models/model_manager.dart';
12 | import '../models/chat_session.dart';
13 | import '../helpers/event_bus.dart';
14 |
15 | final FEED_IMAGE_SIZE = 200.0;
16 |
17 | class MainProvider with ChangeNotifier {
18 | final _prefs = SharedPreferencesAsync();
19 | PackageInfo? _packageInfo;
20 | QDatabase qdb = QDatabase();
21 |
22 | bool isInitialized = false;
23 | bool serveConnected = false;
24 | String version = "1.0.0";
25 | int buildNumber = 0;
26 |
27 | String baseUrl = "http://192.168.0.1:11434";
28 | Map activeSessions = {};
29 | List? modelList;
30 | String? selectedModel;
31 |
32 | String instruction = "You are a helpful assistant.";
33 | String curGroupId = Uuid().v4();
34 | double temperature = 0.5;
35 |
36 | //--------------------------------------------------------------------------//
37 | Future initialize() async {
38 | await loadPreferences();
39 | await checkServerConnection();
40 | await _initPackageInfo();
41 | await qdb.init();
42 |
43 | isInitialized = true;
44 | notifyListeners();
45 | }
46 |
47 | //--------------------------------------------------------------------------//
48 | Future loadPreferences() async {
49 | temperature = await _prefs.getDouble("temperature") ?? 0.5;
50 | baseUrl = await _prefs.getString("baseUrl") ?? baseUrl;
51 | instruction = await _prefs.getString("instruction") ?? instruction;
52 | notifyListeners();
53 | }
54 |
55 | //--------------------------------------------------------------------------//
56 | Future checkServerConnection() async {
57 | if (await _isOllamaOpen()) {
58 | serveConnected = true;
59 | return await _loadModels();
60 | } else {
61 | return false;
62 | }
63 | }
64 |
65 | //--------------------------------------------------------------------------//
66 | Future _loadModels() async {
67 | try {
68 | final client = OllamaClient(baseUrl: baseUrl + "/api");
69 | final res = await client.listModels();
70 | if (res.models != null) {
71 | modelList = res.models!;
72 | serveConnected = modelList!.isNotEmpty;
73 | }
74 | notifyListeners();
75 | return true;
76 | } catch (e) {
77 | print(e);
78 | modelList = [];
79 | notifyListeners();
80 | return false;
81 | }
82 | }
83 |
84 | //--------------------------------------------------------------------------//
85 | Future _isOllamaOpen() async {
86 | try {
87 | final response =
88 | await http.get(Uri.parse(baseUrl)).timeout(Duration(seconds: 1));
89 | return response.statusCode == 200;
90 | } catch (e) {
91 | return false;
92 | }
93 | }
94 |
95 | //--------------------------------------------------------------------------//
96 | ChatSession createChatSession(String modelName) {
97 | Future.microtask(() {
98 | _cleanupInactiveSessions();
99 | });
100 |
101 | final chatId = Uuid().v4();
102 | final client = OllamaClient(baseUrl: baseUrl + "/api");
103 |
104 | final session = ChatSession(
105 | chatId: chatId,
106 | modelName: modelName,
107 | client: client,
108 | );
109 |
110 | activeSessions[chatId] = session;
111 |
112 | Future.microtask(() {
113 | notifyListeners();
114 | });
115 |
116 | return session;
117 | }
118 |
119 | //--------------------------------------------------------------------------//
120 | void _cleanupInactiveSessions() {
121 | final inactiveSessions = activeSessions.entries
122 | .where((entry) => !entry.value.isActive)
123 | .map((entry) => entry.key)
124 | .toList();
125 |
126 | for (final sessionId in inactiveSessions) {
127 | disposeChatSession(sessionId);
128 | }
129 | }
130 |
131 | //--------------------------------------------------------------------------//
132 | void disposeChatSession(String chatId) {
133 | final session = activeSessions[chatId];
134 | if (session != null) {
135 | try {
136 | session.dispose();
137 | } catch (e) {
138 | print("Error disposing session: $e");
139 | }
140 | activeSessions.remove(chatId);
141 |
142 | Future.microtask(() {
143 | notifyListeners();
144 | });
145 | }
146 | }
147 |
148 | //--------------------------------------------------------------------------//
149 | void disposeAllSessions() {
150 | for (final session in activeSessions.values) {
151 | try {
152 | session.dispose();
153 | } catch (e) {
154 | print("Error disposing session: $e");
155 | }
156 | }
157 | activeSessions.clear();
158 |
159 | Future.microtask(() {
160 | notifyListeners();
161 | });
162 | }
163 |
164 | //--------------------------------------------------------------------------//
165 | void setSelectedModel(String model) {
166 | selectedModel = model;
167 | _prefs.setString("selectedModel", model);
168 |
169 | Future.microtask(() {
170 | notifyListeners();
171 | });
172 | }
173 |
174 | //--------------------------------------------------------------------------//
175 | Future _initPackageInfo() async {
176 | _packageInfo = await PackageInfo.fromPlatform();
177 | version = _packageInfo!.version;
178 | buildNumber = int.parse(_packageInfo!.buildNumber);
179 | }
180 |
181 | //--------------------------------------------------------------------------//
182 | void setTemperature(double temp) {
183 | temperature = temp;
184 | _prefs.setDouble("temperature", temp);
185 | notifyListeners();
186 | }
187 |
188 | //--------------------------------------------------------------------------//
189 | Future setBaseUrl(String url) async {
190 | baseUrl = url;
191 | _prefs.setString("baseUrl", url);
192 |
193 | // Dispose all active sessions
194 | disposeAllSessions();
195 |
196 | return await checkServerConnection();
197 | }
198 |
199 | //--------------------------------------------------------------------------//
200 | void setInstruction(String instruction) {
201 | this.instruction = instruction;
202 | _prefs.setString("instruction", instruction);
203 | notifyListeners();
204 | }
205 | }
206 |
--------------------------------------------------------------------------------
/lib/services/chat_service.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:uuid/uuid.dart';
3 | import 'package:ollama_dart/ollama_dart.dart' as ollama;
4 | import 'package:flutter_chat_types/flutter_chat_types.dart' as types;
5 | import 'package:easy_localization/easy_localization.dart';
6 | import 'package:provider/provider.dart';
7 | import 'dart:async';
8 |
9 | import '../provider/main_provider.dart';
10 | import '../models/chat_session.dart';
11 | import '../helpers/event_bus.dart';
12 |
13 | class ChatService {
14 | final BuildContext context;
15 | final List questions;
16 | final List messages;
17 | final types.User answerer;
18 | final types.User user;
19 | final Function setState;
20 | String? selectedImage;
21 | ChatSession? _chatSession;
22 | StreamSubscription? _sessionSubscription;
23 |
24 | Function(String)? onMessageUpdate;
25 |
26 | ChatService({
27 | required this.context,
28 | required this.questions,
29 | required this.messages,
30 | required this.answerer,
31 | required this.user,
32 | required this.setState,
33 | this.selectedImage,
34 | this.onMessageUpdate,
35 | });
36 |
37 | void dispose() {
38 | _cleanupCurrentSession();
39 | }
40 |
41 | void _cleanupCurrentSession() {
42 | _sessionSubscription?.cancel();
43 | _sessionSubscription = null;
44 |
45 | if (_chatSession != null) {
46 | final provider = context.read();
47 | provider.disposeChatSession(_chatSession!.chatId);
48 | _chatSession = null;
49 | }
50 | }
51 |
52 | Future startGeneration({
53 | required String question,
54 | required String modelName,
55 | required Function(String) onMessageUpdate,
56 | required Function(String) onError,
57 | required Function() onComplete,
58 | }) async {
59 | _cleanupCurrentSession();
60 | final provider = context.read();
61 | _chatSession = provider.createChatSession(modelName);
62 |
63 | List qmsg = [];
64 |
65 | questions.forEach((item) {
66 | qmsg.add(ollama.Message(
67 | role: ollama.MessageRole.system,
68 | content: item,
69 | ));
70 | });
71 |
72 | qmsg.add(ollama.Message(
73 | role: ollama.MessageRole.system,
74 | content: provider.instruction,
75 | ));
76 |
77 | qmsg.add(ollama.Message(
78 | role: ollama.MessageRole.user,
79 | content: question,
80 | ));
81 |
82 | try {
83 | final stream = _chatSession!.client.generateChatCompletionStream(
84 | request: ollama.GenerateChatCompletionRequest(
85 | model: modelName,
86 | messages: qmsg,
87 | keepAlive: 5,
88 | options: ollama.RequestOptions(
89 | temperature: provider.temperature,
90 | ),
91 | ),
92 | );
93 |
94 | bool receivedResponse = false;
95 | Timer? timeoutTimer = Timer(const Duration(seconds: 30), () {
96 | if (!receivedResponse) {
97 | onError(tr("l_timeout"));
98 | }
99 | });
100 |
101 | String answer = '';
102 | int tokens = 0;
103 | final stopwatch = Stopwatch()..start();
104 |
105 | try {
106 | await for (final response in stream) {
107 | if (!receivedResponse) {
108 | receivedResponse = true;
109 | if (timeoutTimer != null && timeoutTimer.isActive) {
110 | timeoutTimer.cancel();
111 | timeoutTimer = null;
112 | }
113 |
114 | onMessageUpdate("Processing...");
115 | }
116 |
117 | final token = response.message.content ?? '';
118 |
119 | if (token.isNotEmpty) {
120 | tokens++;
121 | answer += token;
122 | onMessageUpdate(answer);
123 |
124 | await Future.delayed(Duration(milliseconds: 1));
125 | }
126 | }
127 |
128 | if (timeoutTimer != null && timeoutTimer.isActive) {
129 | timeoutTimer.cancel();
130 | timeoutTimer = null;
131 | }
132 |
133 | stopwatch.stop();
134 | final seconds = stopwatch.elapsedMilliseconds / 1000;
135 | final tokensPerSecond = tokens / seconds;
136 |
137 | answer += "\n\n _MyOllama - $modelName " +
138 | "\nToken/Sec: ${tokensPerSecond.toStringAsFixed(1)}" +
139 | "_";
140 |
141 | onMessageUpdate(answer);
142 |
143 | await provider.qdb.insertQuestion(provider.curGroupId,
144 | provider.instruction, question, answer, selectedImage, modelName);
145 |
146 | MyEventBus().fire(ChatDoneEvent());
147 |
148 | selectedImage = null;
149 |
150 | onComplete();
151 | } catch (e) {
152 | onError(e.toString());
153 | }
154 | } catch (e) {
155 | onError(e.toString());
156 | }
157 | }
158 | }
159 |
--------------------------------------------------------------------------------
/lib/utils/platform_utils.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 | import 'package:flutter/widgets.dart';
3 |
4 | bool isDesktopOrTablet() {
5 | if (Platform.isWindows || Platform.isMacOS || Platform.isLinux) {
6 | return true;
7 | }
8 |
9 | // Check if running on iPad
10 | if (Platform.isIOS) {
11 | final window = WidgetsBinding.instance.window;
12 | final size = window.physicalSize;
13 | final pixelRatio = window.devicePixelRatio;
14 | final width = size.width / pixelRatio;
15 |
16 | // iPad typically has a width greater than 768 points
17 | return width >= 768;
18 | }
19 |
20 | return false;
21 | }
22 |
--------------------------------------------------------------------------------
/lib/views/chat_view.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 | import 'dart:io';
3 |
4 | import 'package:flutter/material.dart';
5 | import 'package:flutter/services.dart';
6 | import 'package:flutter/scheduler.dart';
7 | import 'package:provider/provider.dart';
8 | import 'package:easy_localization/easy_localization.dart';
9 | import 'package:flutter_styled_toast/flutter_styled_toast.dart';
10 | import 'package:share_plus/share_plus.dart';
11 | import 'package:flutter_markdown/flutter_markdown.dart';
12 | import 'package:url_launcher/url_launcher.dart';
13 |
14 | import 'package:uuid/uuid.dart';
15 | import 'package:flutter_chat_ui/flutter_chat_ui.dart';
16 | import 'package:flutter_chat_types/flutter_chat_types.dart' as types;
17 | import 'package:image_picker/image_picker.dart';
18 |
19 | import '../provider/main_provider.dart';
20 | import '../helpers/event_bus.dart';
21 | import '../widgets/dialogs.dart';
22 | import '../widgets/drop_menu.dart';
23 | import '../services/chat_service.dart';
24 |
25 | import 'settings.dart';
26 | import '../utils/platform_utils.dart';
27 |
28 | class ChatView extends StatefulWidget {
29 | const ChatView({Key? key}) : super(key: key);
30 |
31 | @override
32 | createState() => _ChatViewState();
33 | }
34 |
35 | class _ChatViewState extends State {
36 | final ImagePicker _picker = ImagePicker();
37 | List _messages = [];
38 | List _questions = [];
39 | final _user = const types.User(id: '82091008-a484-4a89-ae75-a22bf8d6f3ac');
40 | final _answerer =
41 | const types.User(id: '82091008-a484-4a89-ae75-a22bf8d6f3ad');
42 | String? _selectedImage;
43 | TextEditingController _questionController = TextEditingController();
44 | bool _showWait = false;
45 | bool _isProcessed = false;
46 | late final ChatService _chatService;
47 | String _currentGroupId = '';
48 |
49 | //--------------------------------------------------------------------------//
50 | @override
51 | void initState() {
52 | super.initState();
53 | _initEventConnector();
54 | _init();
55 | }
56 |
57 | @override
58 | void dispose() {
59 | _questionController.dispose();
60 | _chatService.dispose();
61 | super.dispose();
62 | }
63 |
64 | //--------------------------------------------------------------------------//
65 | Future _init() async {
66 | _chatService = ChatService(
67 | context: context,
68 | questions: _questions,
69 | messages: _messages,
70 | answerer: _answerer,
71 | user: _user,
72 | setState: () => setState(() {}),
73 | onMessageUpdate: _handleMessageUpdate);
74 |
75 | final rightMenu = [
76 | IconButton(onPressed: _newNote, icon: Icon(Icons.add)),
77 | DropMenu(_reloadServerModel, _newNote, _shareAll, _showSettings)
78 | ];
79 | MyEventBus().fire(ChangeTitleEvent(tr("l_myollama"), rightMenu));
80 | }
81 |
82 | //--------------------------------------------------------------------------//
83 | void _showSettings() {
84 | // 설정 열기 전 현재 서버 주소 저장
85 | final provider = context.read();
86 | final oldServerUrl = provider.baseUrl;
87 |
88 | if (isDesktopOrTablet()) {
89 | showDialog(
90 | context: context,
91 | builder: (BuildContext context) {
92 | final size = MediaQuery.of(context).size;
93 | return Dialog(
94 | child: Container(
95 | width: size.width * 0.8,
96 | height: size.height * 0.8,
97 | padding: EdgeInsets.all(16),
98 | child: Column(
99 | crossAxisAlignment: CrossAxisAlignment.start,
100 | children: [
101 | Row(
102 | children: [
103 | Text(tr("l_settings"),
104 | style: TextStyle(
105 | fontSize: 20, fontWeight: FontWeight.bold)),
106 | Spacer(),
107 | IconButton(
108 | icon: Icon(Icons.close),
109 | onPressed: () {
110 | Navigator.of(context).pop();
111 |
112 | // 설정 창이 닫힐 때 서버 주소 변경 확인
113 | final newServerUrl = provider.baseUrl;
114 | if (oldServerUrl != newServerUrl) {
115 | // 서버 주소가 변경되었으면 모델 리로드
116 | MyEventBus().fire(ReloadModelEvent());
117 | }
118 | },
119 | ),
120 | ],
121 | ),
122 | SizedBox(height: 16),
123 | Expanded(
124 | child: MySettings(),
125 | ),
126 | ],
127 | ),
128 | ),
129 | );
130 | },
131 | );
132 | } else {
133 | Navigator.push(
134 | context,
135 | MaterialPageRoute(builder: (context) => const MySettings()),
136 | ).then((_) {
137 | final newServerUrl = provider.baseUrl;
138 | if (oldServerUrl != newServerUrl) {
139 | // 서버 주소가 변경되었으면 모델 리로드
140 | MyEventBus().fire(ReloadModelEvent());
141 | }
142 | });
143 | }
144 | }
145 |
146 | //--------------------------------------------------------------------------//
147 | void _initEventConnector() async {
148 | MyEventBus().on().listen((event) {
149 | _loadHistoryChats();
150 | });
151 | MyEventBus().on().listen((event) {
152 | _newNote();
153 | });
154 | MyEventBus().on().listen((event) {
155 | _loadHistoryChats();
156 | _isProcessed = false;
157 | setState(() {});
158 | });
159 | }
160 |
161 | //--------------------------------------------------------------------------//
162 | void _reloadServerModel() async {
163 | final result = await context.read().checkServerConnection();
164 | MyEventBus().fire(ReloadModelEvent());
165 | setState(() {});
166 |
167 | if (result) {
168 | showToast(tr("l_success"),
169 | context: context, position: StyledToastPosition.center);
170 | } else {
171 | showToast(tr("l_error_url"),
172 | context: context, position: StyledToastPosition.center);
173 | }
174 | }
175 |
176 | //--------------------------------------------------------------------------//
177 | void _loadHistoryChats() async {
178 | _showWait = true;
179 | setState(() {});
180 |
181 | _messages = [];
182 | _questions = await context
183 | .read()
184 | .qdb
185 | .getDetails(context.read().curGroupId);
186 | _questions.reversed.forEach((item) {
187 | if (item["image"] != null) {
188 | _makeImageMessageAdd(item["image"]);
189 | }
190 | int qtime = DateTime.parse(item["created"]).millisecondsSinceEpoch;
191 | _makeMessageAdd(_user, item["question"], Uuid().v4(), qtime, item["id"]);
192 |
193 | String answer = item["answer"];
194 | _makeMessageAdd(_answerer, answer, Uuid().v4(), qtime, item["id"]);
195 | });
196 |
197 | _showWait = false;
198 | if (mounted) setState(() {});
199 | }
200 |
201 | //--------------------------------------------------------------------------//
202 | void _makeMessageAdd(types.User user, String text, String unique, int time,
203 | [int? id = null]) async {
204 | final msg = types.TextMessage(
205 | author: user,
206 | createdAt: time,
207 | id: unique,
208 | text: text,
209 | metadata: {'id': id},
210 | );
211 |
212 | _messages.insert(0, msg);
213 | if (mounted) setState(() {});
214 | }
215 |
216 | //--------------------------------------------------------------------------//
217 | void _shareAll() {
218 | String sharedData = "";
219 | if (_questions.length == 0) return;
220 |
221 | _questions.forEach((item) {
222 | sharedData += item["question"] +
223 | "\n\n" +
224 | item["answer"] +
225 | "\n\n" +
226 | item["engine"] +
227 | "\n" +
228 | item["created"] +
229 | "\n\n";
230 | });
231 | Share.share(sharedData);
232 | }
233 |
234 | //--------------------------------------------------------------------------//
235 | void _newNote([String? question]) {
236 | final provider = context.read();
237 |
238 | provider.curGroupId = Uuid().v4();
239 | _messages = [];
240 | _questions = [];
241 |
242 | if (question != null) {
243 | _questionController.text = question;
244 | } else {
245 | _questionController.clear();
246 | }
247 |
248 | setState(() {});
249 | }
250 |
251 | //--------------------------------------------------------------------------//
252 | void _handleMessageUpdate(String content) {
253 | // 디버그 로그
254 | if (_messages.isEmpty) {
255 | return;
256 | }
257 |
258 | if (!mounted) {
259 | return;
260 | }
261 |
262 | // 첫 번째 메시지가 응답자(AI)의 메시지인지 확인
263 | if (_messages[0].author.id != _answerer.id) {
264 | return;
265 | }
266 |
267 | // 현재 메시지 텍스트 확인
268 | final currentText = (_messages[0] as types.TextMessage).text;
269 | if (currentText == content) {
270 | return;
271 | }
272 |
273 | setState(() {
274 | _messages[0] =
275 | (_messages[0] as types.TextMessage).copyWith(text: content);
276 | });
277 | }
278 |
279 | //--------------------------------------------------------------------------//
280 | void _beginAsking(types.PartialText message) async {
281 | // 질문이 입력되면 키보드 감추기
282 | FocusScope.of(context).unfocus();
283 |
284 | final provider = context.read();
285 | String question = message.text;
286 |
287 | if (question.isEmpty) return;
288 |
289 | // 이미 처리 중인 경우 중복 요청 방지
290 | if (_isProcessed) {
291 | return;
292 | }
293 |
294 | // 사용자 메시지 추가
295 | types.TextMessage ask = types.TextMessage(
296 | author: _user,
297 | createdAt: DateTime.now().millisecondsSinceEpoch,
298 | id: const Uuid().v4(),
299 | text: question,
300 | );
301 |
302 | setState(() {
303 | _messages.insert(0, ask);
304 | _isProcessed = true; // 처리 중 상태 설정
305 | });
306 |
307 | // 선택된 모델 확인
308 | String? modelToUse = provider.selectedModel;
309 |
310 | // 선택된 모델이 없으면 첫 번째 모델 사용
311 | if (modelToUse == null && provider.modelList!.isNotEmpty) {
312 | modelToUse = provider.modelList!.first.model;
313 | if (modelToUse != null) {
314 | provider.setSelectedModel(modelToUse);
315 | } else {
316 | setState(() {
317 | _isProcessed = false;
318 | });
319 | showToast(tr("l_error_no_models"),
320 | context: context, position: StyledToastPosition.center);
321 | return;
322 | }
323 | }
324 |
325 | if (mounted) {
326 | _chatService.selectedImage = _selectedImage;
327 |
328 | try {
329 | // 응답 메시지 미리 생성
330 | types.TextMessage ansMessage = types.TextMessage(
331 | author: _answerer,
332 | createdAt: DateTime.now().millisecondsSinceEpoch,
333 | id: const Uuid().v4(),
334 | text: "...",
335 | );
336 |
337 | setState(() {
338 | _messages.insert(0, ansMessage);
339 | });
340 |
341 | // 채팅 서비스를 통해 응답 생성 시작
342 | await _chatService.startGeneration(
343 | question: question,
344 | modelName: modelToUse!,
345 | onMessageUpdate: (content) {
346 | // 디버그 로그 추가
347 | _handleMessageUpdate(content);
348 | },
349 | onError: (error) {
350 | // 오류 메시지로 UI 업데이트
351 | if (_messages.isNotEmpty &&
352 | _messages[0].author.id == _answerer.id) {
353 | setState(() {
354 | _messages[0] = (_messages[0] as types.TextMessage).copyWith(
355 | text: "오류: $error",
356 | );
357 | });
358 | }
359 |
360 | ScaffoldMessenger.of(context).showSnackBar(
361 | SnackBar(content: Text(error)),
362 | );
363 | setState(() {
364 | _isProcessed = false;
365 | });
366 | },
367 | onComplete: () {
368 | setState(() {
369 | _isProcessed = false;
370 | });
371 | },
372 | );
373 | } catch (e) {
374 | // 오류 메시지로 UI 업데이트
375 | if (_messages.isNotEmpty && _messages[0].author.id == _answerer.id) {
376 | setState(() {
377 | _messages[0] = (_messages[0] as types.TextMessage).copyWith(
378 | text: "오류: $e",
379 | );
380 | });
381 | }
382 |
383 | setState(() {
384 | _isProcessed = false;
385 | });
386 | ScaffoldMessenger.of(context).showSnackBar(
387 | SnackBar(content: Text("오류: $e")),
388 | );
389 | }
390 | }
391 | }
392 |
393 | //--------------------------------------------------------------------------//
394 | void _handleAttachmentPressed() async {
395 | XFile? image = await _picker.pickImage(
396 | source: ImageSource.gallery,
397 | imageQuality: 80,
398 | maxWidth: 500,
399 | maxHeight: 500);
400 | if (image != null) {
401 | File file = File(image.path);
402 | List imageBytes = file.readAsBytesSync();
403 | _selectedImage = base64Encode(imageBytes);
404 | _makeImageMessageAdd(_selectedImage!);
405 | }
406 | }
407 |
408 | //--------------------------------------------------------------------------//
409 | void _handlePreviewDataFetched(
410 | types.TextMessage message, types.PreviewData previewData) {
411 | final index = _messages.indexWhere((element) => element.id == message.id);
412 | final updatedMessage = (_messages[index] as types.TextMessage).copyWith(
413 | previewData: previewData,
414 | );
415 |
416 | setState(() {
417 | _messages[index] = updatedMessage;
418 | });
419 | }
420 |
421 | //--------------------------------------------------------------------------//
422 | void _makeImageMessageAdd(String base64image) {
423 | final msg = types.CustomMessage(
424 | author: _answerer,
425 | id: Uuid().v4(),
426 | metadata: {'data': base64image, 'type': 'image'},
427 | );
428 |
429 | _messages.insert(0, msg);
430 | if (mounted) setState(() {});
431 | }
432 |
433 | //--------------------------------------------------------------------------//
434 | Widget _customMessageBuilder(types.CustomMessage message,
435 | {required int messageWidth}) {
436 | if (message.metadata!['type'] == 'image') {
437 | return RepaintBoundary(
438 | child: Image.memory(
439 | base64Decode(message.metadata!['data']),
440 | fit: BoxFit.contain,
441 | ),
442 | );
443 | }
444 | return const SizedBox();
445 | }
446 |
447 | //--------------------------------------------------------------------------//
448 | void _menuRunner(int number, types.TextMessage message) async {
449 | final provider = context.read();
450 |
451 | final id = message.metadata!['id'];
452 | final record = await provider.qdb.getDetailsById(id);
453 | if (record.length > 0) {
454 | final question = record[0]["question"];
455 | final answer = record[0]["answer"];
456 | final created = record[0]["created"];
457 | final model = record[0]["engine"];
458 | final sharedData = "$question\n\n$answer\n\n$model\n$created";
459 |
460 | if (number == 0) {
461 | _newNote();
462 | } else if (number == 1) {
463 | Clipboard.setData(ClipboardData(text: sharedData));
464 | showToast(tr("l_copyed"),
465 | context: context, position: StyledToastPosition.center);
466 | } else if (number == 2) {
467 | Share.share(sharedData);
468 | } else if (number == 3) {
469 | final result = await AskDialog.show(context,
470 | title: tr("l_delete"), message: tr("l_delete_question"));
471 | if (result == true) {
472 | await provider.qdb.deleteRecord(id);
473 | // check is last data?
474 | provider.qdb
475 | .getDetails(context.read().curGroupId)
476 | .then((value) {
477 | if (value.length == 0) {
478 | MyEventBus().fire(RefreshMainListEvent());
479 | _newNote();
480 | } else {
481 | _loadHistoryChats();
482 | }
483 | });
484 | }
485 | }
486 | }
487 | }
488 |
489 | //--------------------------------------------------------------------------//
490 | Widget _messageMenu(types.TextMessage message) {
491 | return Row(
492 | mainAxisAlignment: MainAxisAlignment.end,
493 | children: [
494 | IconButton(
495 | onPressed: () {
496 | _menuRunner(0, message);
497 | },
498 | icon: Icon(
499 | Icons.open_in_new,
500 | color: Colors.white,
501 | )),
502 | IconButton(
503 | onPressed: () {
504 | _menuRunner(1, message);
505 | },
506 | icon: Icon(
507 | Icons.copy,
508 | color: Colors.white,
509 | )),
510 | IconButton(
511 | onPressed: () {
512 | _menuRunner(2, message);
513 | },
514 | icon: Icon(
515 | Icons.share,
516 | color: Colors.white,
517 | )),
518 | IconButton(
519 | onPressed: () {
520 | _menuRunner(3, message);
521 | },
522 | icon: Icon(
523 | Icons.delete_outline,
524 | color: Colors.white,
525 | )),
526 | ],
527 | );
528 | }
529 |
530 | //--------------------------------------------------------------------------//
531 | Widget _textMessageBuilder(types.TextMessage message,
532 | {required int messageWidth, bool? showName}) {
533 | final isAnswer = message.author.id == _answerer.id;
534 |
535 | return Container(
536 | margin: EdgeInsets.all(16),
537 | child: Column(
538 | children: [
539 | MarkdownBody(
540 | data: message.text,
541 | selectable: true,
542 | onTapLink: (text, href, title) {
543 | launchUrl(Uri.parse(href!));
544 | },
545 | ),
546 | if (isAnswer && !_isProcessed)
547 | Column(
548 | children: [
549 | Divider(
550 | color: Colors.black12,
551 | ),
552 | _messageMenu(message)
553 | ],
554 | )
555 | ],
556 | ));
557 | }
558 |
559 | //--------------------------------------------------------------------------//
560 | Widget _chatUI() {
561 | final provider = context.read();
562 |
563 | return Chat(
564 | theme: DefaultChatTheme(
565 | primaryColor: Colors.white,
566 | secondaryColor: Colors.orangeAccent,
567 | inputBackgroundColor: Colors.indigo,
568 | inputTextCursorColor: Colors.white,
569 | backgroundColor: Colors.grey.shade200,
570 | messageInsetsHorizontal: 16,
571 | messageInsetsVertical: 10,
572 | ),
573 | inputOptions: InputOptions(
574 | textEditingController: _questionController,
575 | ),
576 | messages: _messages,
577 | onAttachmentPressed: _handleAttachmentPressed,
578 | onPreviewDataFetched: _handlePreviewDataFetched,
579 | onSendPressed: _beginAsking,
580 | user: _user,
581 | l10n: ChatL10nEn(
582 | inputPlaceholder: tr("l_input_question"),
583 | emptyChatPlaceholder: provider.serveConnected
584 | ? tr("l_no_conversation")
585 | : tr("l_no_server")),
586 | customMessageBuilder: _customMessageBuilder,
587 | textMessageBuilder: (message,
588 | {required messageWidth, required showName}) =>
589 | _textMessageBuilder(message,
590 | messageWidth: messageWidth, showName: showName),
591 | );
592 | }
593 |
594 | //--------------------------------------------------------------------------//
595 | @override
596 | Widget build(BuildContext context) {
597 | return Scaffold(
598 | body: Stack(
599 | children: [
600 | GestureDetector(
601 | onTap: () {
602 | // 화면의 빈 공간을 터치하면 키보드 숨기기
603 | FocusScope.of(context).unfocus();
604 | },
605 | child: _chatUI(),
606 | ),
607 | _showWait ? Center(child: CircularProgressIndicator()) : SizedBox()
608 | ],
609 | ),
610 | );
611 | }
612 | }
613 |
--------------------------------------------------------------------------------
/lib/views/desktop_view.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:split_view/split_view.dart';
3 | import 'package:easy_localization/easy_localization.dart';
4 |
5 | import 'chat_view.dart';
6 | import '../widgets/title_list.dart';
7 | import '../widgets/model_selector.dart';
8 | import '../widgets/list_header.dart';
9 | import '../widgets/app_title.dart';
10 | import '../helpers/event_bus.dart';
11 |
12 | class DesktopView extends StatefulWidget {
13 | const DesktopView({Key? key}) : super(key: key);
14 |
15 | @override
16 | createState() => _DesktopViewState();
17 | }
18 |
19 | class _DesktopViewState extends State {
20 | final double _initialWeight = 0.35;
21 | List? _action;
22 |
23 | @override
24 | void initState() {
25 | super.initState();
26 | _initEventConnector();
27 | }
28 |
29 | @override
30 | void dispose() {
31 | super.dispose();
32 | }
33 |
34 | //--------------------------------------------------------------------------//
35 | void _initEventConnector() async {
36 | MyEventBus().on().listen((event) {
37 | if (mounted) {
38 | _action = event.action;
39 | setState(() {});
40 | }
41 | });
42 | }
43 |
44 | //--------------------------------------------------------------------------//
45 | PreferredSizeWidget _buildToolbar() {
46 | return AppBar(
47 | title: Row(
48 | children: [
49 | AppTitle(textColor: Colors.white),
50 | SizedBox(width: 20),
51 | ModelSelector(),
52 | Spacer(),
53 | ],
54 | ),
55 | backgroundColor: Theme.of(context).primaryColor,
56 | elevation: 1,
57 | actions: _action,
58 | );
59 | }
60 |
61 | //--------------------------------------------------------------------------//
62 | @override
63 | Widget build(BuildContext context) {
64 | return Scaffold(
65 | appBar: _buildToolbar(),
66 | body: SplitView(
67 | viewMode: SplitViewMode.Horizontal,
68 | indicator: SplitIndicator(
69 | viewMode: SplitViewMode.Horizontal,
70 | color: Colors.grey,
71 | ),
72 | activeIndicator: SplitIndicator(
73 | viewMode: SplitViewMode.Horizontal,
74 | color: Colors.blue,
75 | ),
76 | controller:
77 | SplitViewController(weights: [_initialWeight, 1 - _initialWeight]),
78 | children: [
79 | // Left panel (1 part)
80 | Container(
81 | color: Theme.of(context).colorScheme.background,
82 | child: TitleList(),
83 | ),
84 | // Right panel (4 parts)
85 | Container(
86 | color: Theme.of(context).colorScheme.background,
87 | child: ChatView(),
88 | ),
89 | ],
90 | ),
91 | );
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/lib/views/drawer.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:provider/provider.dart';
3 | import 'package:flutter_advanced_drawer/flutter_advanced_drawer.dart';
4 | import 'package:ollama_dart/ollama_dart.dart';
5 | import 'package:easy_localization/easy_localization.dart';
6 |
7 | import '../provider/main_provider.dart';
8 | import '../helpers/event_bus.dart';
9 | import '../widgets/title_list.dart';
10 | import '../widgets/model_selector.dart';
11 | import '../widgets/list_header.dart';
12 |
13 | import 'chat_view.dart';
14 |
15 | class MyDrawer extends StatefulWidget {
16 | const MyDrawer({super.key});
17 |
18 | @override
19 | State createState() => _MyDrawerState();
20 | }
21 |
22 | class _MyDrawerState extends State {
23 | final _drawer = AdvancedDrawerController();
24 | List? _action;
25 | Widget? _currentWidget;
26 |
27 | //--------------------------------------------------------------------------//
28 | @override
29 | void initState() {
30 | super.initState();
31 | _initEventConnector();
32 | _currentWidget = const ChatView();
33 | }
34 |
35 | //--------------------------------------------------------------------------//
36 | void _initEventConnector() async {
37 | MyEventBus().on().listen((event) {
38 | if (mounted) {
39 | _action = event.action;
40 | setState(() {});
41 | }
42 | });
43 |
44 | MyEventBus().on().listen((event) {
45 | _drawer.hideDrawer();
46 | });
47 |
48 | MyEventBus().on().listen((event) {
49 | setState(() {}); // Just refresh the state to rebuild ModelSelector
50 | });
51 | }
52 |
53 | //--------------------------------------------------------------------------//
54 | void _handleMenuButtonPressed() {
55 | _drawer.showDrawer();
56 | }
57 |
58 | //--------------------------------------------------------------------------//
59 | PreferredSizeWidget _appbar() {
60 | return AppBar(
61 | title: const ModelSelector(),
62 | leading: IconButton(
63 | onPressed: _handleMenuButtonPressed,
64 | icon: ValueListenableBuilder(
65 | valueListenable: _drawer,
66 | builder: (_, value, __) {
67 | return AnimatedSwitcher(
68 | duration: const Duration(milliseconds: 250),
69 | child: Icon(
70 | value.visible ? Icons.clear : Icons.menu,
71 | key: ValueKey(value.visible),
72 | ),
73 | );
74 | },
75 | ),
76 | ),
77 | actions: _action,
78 | );
79 | }
80 |
81 | //--------------------------------------------------------------------------//
82 | Widget _listContainer() {
83 | return Container(
84 | color: Colors.white,
85 | child: const Column(
86 | crossAxisAlignment: CrossAxisAlignment.center,
87 | mainAxisAlignment: MainAxisAlignment.start,
88 | children: [
89 | ListHeader(),
90 | Expanded(child: TitleList()),
91 | ],
92 | ),
93 | );
94 | }
95 |
96 | //--------------------------------------------------------------------------//
97 | @override
98 | Widget build(BuildContext context) {
99 | return AdvancedDrawer(
100 | backdropColor: Colors.indigo,
101 | controller: _drawer,
102 | animateChildDecoration: true,
103 | rtlOpening: false,
104 | openScale: 1.0,
105 | openRatio: 0.8,
106 | disabledGestures: true,
107 | child: Scaffold(
108 | appBar: _appbar(),
109 | body: Container(child: _currentWidget),
110 | ),
111 | drawer: SafeArea(
112 | child: _listContainer(),
113 | ),
114 | );
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/lib/views/settings.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 | import 'package:flutter/material.dart';
3 | import 'package:easy_localization/easy_localization.dart';
4 | import 'package:app_settings/app_settings.dart';
5 | import 'package:url_launcher/url_launcher.dart';
6 | import 'package:provider/provider.dart';
7 | import 'package:flutter_styled_toast/flutter_styled_toast.dart';
8 | import '../utils/platform_utils.dart';
9 |
10 | import '../widgets/text_fields.dart';
11 | import '../provider/main_provider.dart';
12 | import '../widgets/dialogs.dart';
13 | import '../helpers/event_bus.dart';
14 |
15 | final title_style = TextStyle(fontSize: 16, color: Colors.grey[500], fontWeight: FontWeight.bold);
16 | final linkStyle = TextStyle(fontSize: 15, color: Colors.blueAccent, fontWeight: FontWeight.bold);
17 | final TextStyle midSizeStyle = TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: Colors.black54);
18 | final TextStyle dvcSubStyle = TextStyle(fontSize: 13, fontWeight: FontWeight.w400, color: Colors.black38);
19 |
20 | class MySettings extends StatefulWidget {
21 | const MySettings({Key? key}) : super(key: key);
22 |
23 | @override
24 | createState()=>_MySettingsState();
25 | }
26 |
27 | class _MySettingsState extends State {
28 | final server_address = TextEditingController();
29 | final instruction = TextEditingController();
30 | final temperature = TextEditingController();
31 |
32 | @override
33 | void initState() {
34 | super.initState();
35 | _loadPreferences();
36 | }
37 |
38 | @override
39 | void dispose() {
40 | super.dispose();
41 | }
42 |
43 | //--------------------------------------------------------------------------//
44 | void _loadPreferences() {
45 | final provider = context.read();
46 | provider.loadPreferences();
47 |
48 | server_address.text = provider.baseUrl;
49 | instruction.text = provider.instruction;
50 | temperature.text = provider.temperature.toString();
51 | }
52 |
53 | //--------------------------------------------------------------------------//
54 | void _deleteAllRecords() async {
55 | final provider = context.read();
56 | final result = await AskDialog.show(context, title: 'Delete all data', message: 'Are you sure?');
57 | if (result == true) {
58 | await provider.qdb.deleteAllRecords();
59 | MyEventBus().fire(NewChatBeginEvent());
60 | MyEventBus().fire(RefreshMainListEvent());
61 | }
62 | }
63 |
64 |
65 | //--------------------------------------------------------------------------//
66 | Widget ActionCardPanel(IconData leadIcon, String title, String? subtitle, IconData trailIcon, Function action) {
67 | return Card(
68 | child: ListTile(
69 | leading: Icon(leadIcon),
70 | title: Text(title, style: midSizeStyle),
71 | subtitle: subtitle != null ? Text(subtitle, style: dvcSubStyle) : null,
72 | trailing: Icon(trailIcon),
73 | onTap: action as void Function()?,
74 | ),
75 | );
76 | }
77 |
78 | //--------------------------------------------------------------------------//
79 | bool _isValidUrl(String url) {
80 | final urlPattern = RegExp(
81 | r'^(http|https):\/\/([\w-]+\.)+[\w-]+(:\d+)?(\/[\w- .\/?%&=]*)?$',
82 | caseSensitive: false,
83 | multiLine: false,
84 | );
85 | return urlPattern.hasMatch(url);
86 | }
87 |
88 | //--------------------------------------------------------------------------//
89 | @override
90 | Widget build(BuildContext context) {
91 | final provider = context.read();
92 | final version = provider.version + ' (' + provider.buildNumber.toString() + ')';
93 | final isDesktop = isDesktopOrTablet(); // Use the new function
94 |
95 | final widgets = [
96 | ListTile(title: Text(tr("l_ollama_setting"), style: title_style)),
97 | Row(
98 | children: [
99 | Expanded(
100 | child:QTextField(tr("l_server_address"), server_address, (_){})
101 | ),
102 | IconButton(
103 | icon: Icon(Icons.network_check, size: 30,),
104 | onPressed: () async {
105 | if (_isValidUrl(server_address.text)) {
106 | final reached = await provider.setBaseUrl(server_address.text);
107 | if (reached) {
108 | MyEventBus().fire(ReloadModelEvent());
109 | showToast(tr("l_success"), context: context, position: StyledToastPosition.center);
110 | } else {
111 | showToast(tr("l_error_url"), context: context, position: StyledToastPosition.center);
112 | }
113 | } else {
114 | AskDialog.show(context, title: tr("l_error"), message: tr("l_invalid_url"));
115 | }
116 | },
117 | )
118 | ],
119 | ),
120 | QTextField(tr("l_instructions"), instruction, (String value){
121 | provider.setInstruction(value);
122 | }, maxLines: 5),
123 | QTextField(tr("l_temp"), temperature, (String value){
124 | provider.setTemperature(double.parse(value));
125 | }),
126 | ActionCardPanel(Icons.download_for_offline_outlined, tr("l_download"), tr("l_download_ollama"), Icons.arrow_forward_ios, () {
127 | launchUrl(Uri.parse("https://ollama.com/download"));
128 | }),
129 | ActionCardPanel(Icons.help_outline, tr("l_howto"), tr("l_howto_1"), Icons.arrow_forward_ios, () {
130 | launchUrl(Uri.parse("http://practical.kr/?p=809"));
131 | }),
132 | ListTile(title: Text(tr("l_app_info"), style: title_style)),
133 | ActionCardPanel(Icons.memory, tr("l_open_source"), tr("l_open_comment"), Icons.arrow_forward_ios, () {
134 | launchUrl(Uri.parse("https://github.com/bipark/my_ollama_app"));
135 | }),
136 | ActionCardPanel(Icons.delete_forever_outlined, tr("l_delete"), tr("l_delete_all"), Icons.arrow_forward_ios, () {
137 | _deleteAllRecords();
138 | }),
139 | ActionCardPanel(Icons.settings_applications_outlined, tr("l_app_info"), null, Icons.arrow_forward_ios, (){
140 | AppSettings.openAppSettings();
141 | }),
142 | ActionCardPanel(Icons.info, tr("l_myollama"), tr("l_version") + version, Icons.arrow_forward_ios, () {
143 | launchUrl(Uri.parse("http://practical.kr"));
144 | }),
145 | ];
146 |
147 | final content = Container(
148 | padding: const EdgeInsets.all(10.0),
149 | child: Material(
150 | child: CustomScrollView(
151 | slivers: [
152 | SliverList(
153 | delegate: SliverChildBuilderDelegate(
154 | (context, index) => widgets[index],
155 | childCount: widgets.length,
156 | ),
157 | ),
158 | ],
159 | ),
160 | ),
161 | );
162 |
163 | if (isDesktop) {
164 | return content;
165 | }
166 |
167 | return Scaffold(
168 | appBar: AppBar(
169 | title: Row(
170 | children: [
171 | Text(tr("l_settings"), style: TextStyle(color: Colors.white, fontSize: 17, fontWeight: FontWeight.bold))
172 | ],
173 | ),
174 | actions: [
175 | IconButton(
176 | onPressed: () {
177 | _loadPreferences();
178 | },
179 | icon: Icon(Icons.refresh, color: Colors.white)
180 | ),
181 | ],
182 | ),
183 | body: GestureDetector(
184 | onTap: () {
185 | FocusScope.of(context).requestFocus(FocusNode());
186 | },
187 | child: content,
188 | ),
189 | );
190 | }
191 | }
192 |
--------------------------------------------------------------------------------
/lib/widgets/app_title.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:easy_localization/easy_localization.dart';
3 |
4 | class AppTitle extends StatelessWidget {
5 | final double? fontSize;
6 | final Color? textColor;
7 |
8 | const AppTitle({
9 | super.key,
10 | this.fontSize = 17,
11 | this.textColor = Colors.white,
12 | });
13 |
14 | @override
15 | Widget build(BuildContext context) {
16 | return Row(
17 | children: [
18 | SizedBox(width: 10),
19 | Text(tr("l_myollama"),
20 | style: TextStyle(
21 | fontSize: fontSize,
22 | fontWeight: FontWeight.bold,
23 | color: textColor,
24 | )),
25 | ],
26 | );
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/lib/widgets/dialogs.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class AskDialog extends StatelessWidget {
4 | final String title;
5 | final String message;
6 | final String confirmLabel;
7 | final String cancelLabel;
8 | final VoidCallback? onConfirm;
9 | final VoidCallback? onCancel;
10 | final bool barrierDismissible;
11 |
12 | const AskDialog({
13 | super.key,
14 | this.title = 'Confirm',
15 | this.message = 'Are you sure?',
16 | this.confirmLabel = 'OK',
17 | this.cancelLabel = 'Cancel',
18 | this.onConfirm,
19 | this.onCancel,
20 | this.barrierDismissible = true,
21 | });
22 |
23 | static Future show(
24 | BuildContext context, {
25 | String title = 'Confirm',
26 | String message = 'Are you sure?',
27 | String confirmLabel = 'OK',
28 | String cancelLabel = 'Cancel',
29 | VoidCallback? onConfirm,
30 | VoidCallback? onCancel,
31 | bool barrierDismissible = true,
32 | }) {
33 | return showDialog(
34 | context: context,
35 | barrierDismissible: barrierDismissible,
36 | builder: (context) => AskDialog(
37 | title: title,
38 | message: message,
39 | confirmLabel: confirmLabel,
40 | cancelLabel: cancelLabel,
41 | onConfirm: onConfirm,
42 | onCancel: onCancel,
43 | barrierDismissible: barrierDismissible,
44 | ),
45 | );
46 | }
47 |
48 | @override
49 | Widget build(BuildContext context) {
50 | return AlertDialog(
51 | shape: RoundedRectangleBorder(
52 | borderRadius: BorderRadius.circular(10.0),
53 | ),
54 | title: Text(title, style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
55 | content: Text(message),
56 | actions: [
57 | TextButton(
58 | onPressed: () {
59 | onCancel?.call();
60 | Navigator.of(context).pop(false);
61 | },
62 | child: Text(cancelLabel),
63 | ),
64 | FilledButton(
65 | onPressed: () {
66 | onConfirm?.call();
67 | Navigator.of(context).pop(true);
68 | },
69 | child: Text(confirmLabel),
70 | ),
71 | ],
72 | );
73 | }
74 | }
--------------------------------------------------------------------------------
/lib/widgets/drop_menu.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:easy_localization/easy_localization.dart';
3 |
4 | class DropMenu extends StatefulWidget {
5 | final Function netCheck;
6 | final Function newNote;
7 | final Function shareAll;
8 | final Function showSettings;
9 | const DropMenu(this.netCheck, this.newNote, this.shareAll, this.showSettings, {super.key});
10 |
11 | @override
12 | State createState() => _DropMenuState();
13 | }
14 |
15 | class _DropMenuState extends State {
16 | final FocusNode _buttonFocusNode = FocusNode(debugLabel: 'DropMenu');
17 |
18 | @override
19 | void dispose() {
20 | _buttonFocusNode.dispose();
21 | super.dispose();
22 | }
23 |
24 | @override
25 | Widget build(BuildContext context) {
26 | return MenuAnchor(
27 | childFocusNode: _buttonFocusNode,
28 | menuChildren: [
29 | MenuItemButton(
30 | child: Row(
31 | children: [
32 | const Icon(Icons.network_check),
33 | const SizedBox(width: 8),
34 | Text(tr("l_server_check")),
35 | ],
36 | ),
37 | onPressed: (){
38 | widget.netCheck();
39 | },
40 | ),
41 | MenuItemButton(
42 | child: Row(
43 | children: [
44 | const Icon(Icons.add),
45 | const SizedBox(width: 8),
46 | Text(tr("l_new_chat")),
47 | ],
48 | ),
49 | onPressed: () {
50 | widget.newNote();
51 | },
52 | ),
53 | MenuItemButton(
54 | child: Row(
55 | children: [
56 | const Icon(Icons.share),
57 | const SizedBox(width: 8),
58 | Text(tr("l_share_all")),
59 | ],
60 | ),
61 | onPressed: () {
62 | widget.shareAll();
63 | },
64 | ),
65 | MenuItemButton(
66 | child: Row(
67 | children: [
68 | const Icon(Icons.settings),
69 | const SizedBox(width: 8),
70 | Text(tr("l_settings")),
71 | ],
72 | ),
73 | onPressed: () {
74 | widget.showSettings();
75 | },
76 | ),
77 | ],
78 | builder: (_, MenuController controller, Widget? child) {
79 | return IconButton(
80 | focusNode: _buttonFocusNode,
81 | onPressed: () {
82 | if (controller.isOpen) {
83 | controller.close();
84 | } else {
85 | controller.open();
86 | }
87 | },
88 | icon: const Icon(Icons.more_vert),
89 | );
90 | },
91 | );
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/lib/widgets/list_header.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:easy_localization/easy_localization.dart';
3 |
4 | import '../helpers/event_bus.dart';
5 | import 'app_title.dart';
6 |
7 | class ListHeader extends StatelessWidget {
8 | const ListHeader({super.key});
9 |
10 | @override
11 | Widget build(BuildContext context) {
12 | return Container(
13 | decoration: BoxDecoration(
14 | color: Colors.indigo,
15 | border: Border(bottom: BorderSide(color: Colors.grey.shade200)),
16 | ),
17 | height: 56,
18 | child: Row(
19 | children: [
20 | AppTitle(),
21 | Spacer(),
22 | IconButton(
23 | onPressed: () {
24 | MyEventBus().fire(RefreshMainListEvent());
25 | },
26 | icon: Icon(Icons.refresh, color: Colors.white)
27 | ),
28 | ],
29 | ),
30 | );
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/lib/widgets/model_selector.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:provider/provider.dart';
3 | import 'package:ollama_dart/ollama_dart.dart';
4 | import 'package:easy_localization/easy_localization.dart';
5 |
6 | import '../provider/main_provider.dart';
7 | import '../helpers/event_bus.dart';
8 |
9 | class ModelSelector extends StatefulWidget {
10 | const ModelSelector({super.key});
11 |
12 | @override
13 | State createState() => _ModelSelectorState();
14 | }
15 |
16 | class _ModelSelectorState extends State {
17 | final MenuController _menuController = MenuController();
18 | List _models = [];
19 | bool _isLoading = true;
20 |
21 | @override
22 | void initState() {
23 | super.initState();
24 | _initEventConnector();
25 | WidgetsBinding.instance.addPostFrameCallback((_) {
26 | _loadModels();
27 | });
28 | }
29 |
30 | //--------------------------------------------------------------------------//
31 | void _initEventConnector() async {
32 | MyEventBus().on().listen((event) {
33 | _loadModels();
34 | });
35 | }
36 |
37 | //--------------------------------------------------------------------------//
38 | void _loadModels() {
39 | setState(() => _isLoading = true);
40 |
41 | final provider = context.read();
42 | if (provider.modelList != null && provider.modelList!.isNotEmpty) {
43 | _models = provider.modelList!
44 | .map((Model e) => e.model)
45 | .where((model) => model != null)
46 | .map((model) => model!)
47 | .toList();
48 |
49 | if (provider.selectedModel == null && _models.isNotEmpty) {
50 | Future.microtask(() {
51 | provider.setSelectedModel(_models.first);
52 | });
53 | }
54 | } else {
55 | _models = [];
56 | }
57 |
58 | setState(() => _isLoading = false);
59 | }
60 |
61 | @override
62 | Widget build(BuildContext context) {
63 | return Consumer(
64 | builder: (context, provider, child) {
65 | if (_isLoading) {
66 | return SizedBox(
67 | width: 24,
68 | height: 24,
69 | child: CircularProgressIndicator(
70 | strokeWidth: 2,
71 | valueColor: AlwaysStoppedAnimation(Colors.white),
72 | ),
73 | );
74 | }
75 |
76 | if (_models.isEmpty) {
77 | return TextButton.icon(
78 | onPressed: null,
79 | icon: Text(tr("l_no_models"),
80 | style: TextStyle(
81 | color: Colors.red[300], fontWeight: FontWeight.bold)),
82 | label: Icon(Icons.error_outline, color: Colors.red[300]),
83 | );
84 | }
85 |
86 | final displayModel = provider.selectedModel ??
87 | (_models.isNotEmpty ? _models.first : tr("l_no_model"));
88 |
89 | return MenuAnchor(
90 | alignmentOffset: Offset(0, 8),
91 | controller: _menuController,
92 | menuChildren: [
93 | for (final String option in _models)
94 | MenuItemButton(
95 | onPressed: () {
96 | provider.setSelectedModel(option);
97 | _menuController.close();
98 | },
99 | child: Text(option, style: TextStyle(color: Colors.black)),
100 | ),
101 | ],
102 | builder: (context, controller, child) {
103 | return TextButton.icon(
104 | onPressed: () {
105 | if (controller.isOpen) {
106 | controller.close();
107 | } else {
108 | controller.open();
109 | }
110 | },
111 | icon: Text(displayModel,
112 | style: TextStyle(
113 | color: Colors.yellowAccent, fontWeight: FontWeight.bold)),
114 | label: Icon(Icons.arrow_drop_down, color: Colors.white),
115 | );
116 | },
117 | );
118 | },
119 | );
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/lib/widgets/text_fields.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 |
4 | class QTextField extends StatefulWidget {
5 | final String label;
6 | final Function(String) onChanged;
7 | final TextEditingController controller;
8 | final maxLines;
9 |
10 | QTextField(this.label, this.controller, this.onChanged, {this.maxLines = 1});
11 | @override
12 | _QTextFieldState createState() => _QTextFieldState();
13 | }
14 |
15 | class _QTextFieldState extends State {
16 | late FocusNode _focusNode;
17 |
18 | @override
19 | void initState() {
20 | super.initState();
21 | _focusNode = FocusNode();
22 | _focusNode.addListener(() {
23 | if (!_focusNode.hasFocus) {
24 | widget.onChanged(widget.controller.text);
25 | }
26 | });
27 | }
28 |
29 | @override
30 | void dispose() {
31 | _focusNode.dispose();
32 | super.dispose();
33 | }
34 |
35 | @override
36 | Widget build(BuildContext context) {
37 | return Container(
38 | padding: EdgeInsets.only(bottom: 20),
39 | child: TextField(
40 | keyboardType: TextInputType.multiline,
41 | controller: widget.controller,
42 | focusNode: _focusNode,
43 | maxLines: widget.maxLines,
44 | decoration: InputDecoration(
45 | isDense: true,
46 | labelText: widget.label,
47 | floatingLabelStyle: TextStyle(color: Colors.grey, fontSize: 16),
48 | floatingLabelBehavior: FloatingLabelBehavior.always,
49 | border: OutlineInputBorder(
50 | borderRadius: BorderRadius.circular(5),
51 | borderSide: BorderSide(color: Colors.black12)
52 | ),
53 | ),
54 | onChanged: widget.onChanged,
55 | )
56 | );
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/lib/widgets/title_list.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:provider/provider.dart';
3 | import 'package:easy_localization/easy_localization.dart';
4 |
5 | import '../provider/main_provider.dart';
6 | import '../helpers/event_bus.dart';
7 |
8 | import 'dialogs.dart';
9 |
10 | class TitleList extends StatefulWidget {
11 | const TitleList({Key? key}) : super(key: key);
12 |
13 | @override
14 | createState()=>_TitleListState();
15 | }
16 |
17 | class _TitleListState extends State {
18 | bool _showWait = false;
19 | List _titles = [];
20 | int _selectedIndex = 0;
21 | TextEditingController _search = TextEditingController();
22 |
23 |
24 | @override
25 | void initState() {
26 | super.initState();
27 | _initEventConnector();
28 | _loadData(refresh: true);
29 | }
30 |
31 | @override
32 | void dispose() {
33 | super.dispose();
34 | }
35 |
36 | //--------------------------------------------------------------------------//
37 | void _initEventConnector() async {
38 | MyEventBus().on().listen((event) {
39 | _loadData();
40 | });
41 | }
42 |
43 |
44 | //--------------------------------------------------------------------------//
45 | Future _loadData({bool refresh = false}) async {
46 | if (mounted) {
47 | _showWait = true;
48 | setState(() {});
49 |
50 | _titles = await context.read().qdb.getTitles();
51 | if (refresh) {
52 | if (_titles.length > 0) _selectTitle(0);
53 | }
54 |
55 | _showWait = false;
56 | setState(() {});
57 | }
58 | }
59 |
60 | //--------------------------------------------------------------------------//
61 | void _deleteQuestion(String groupid) async {
62 | final provider = context.read();
63 | final result = await AskDialog.show(context, title: tr("l_delete"), message: tr("l_delete_question"));
64 | if (result == true) {
65 | await provider.qdb.deleteQuestions(groupid);
66 | _loadData();
67 | _selectedIndex = 0;
68 | setState(() {});
69 | }
70 | }
71 |
72 | //--------------------------------------------------------------------------//
73 | Widget _titlePanel(int index) {
74 | String title = _titles[index]["question"];
75 | if (title.length > 70) {
76 | title = title.substring(0, 70) + "...";
77 | title = title.replaceAll("\n", " ");
78 | title = title.trimLeft();
79 | }
80 |
81 | return Container(
82 | color : _selectedIndex == index ? Colors.grey.shade200 : Colors.transparent,
83 | padding: EdgeInsets.fromLTRB(14, 6, 10, 6),
84 | child: Row(
85 | children: [
86 | Expanded(
87 | child : Column(
88 | crossAxisAlignment: CrossAxisAlignment.start,
89 | children: [
90 | Text(title, style: TextStyle(fontSize: 14, fontWeight: FontWeight.w400)),
91 | Text(_titles[index]["created"].toString(), style: TextStyle(fontSize: 12, color: Colors.grey))
92 | ]
93 | )
94 | ),
95 | Row(
96 | children: [
97 | IconButton(onPressed: () {
98 | _deleteQuestion(_titles[index]["groupid"]);
99 | }, icon: Icon(Icons.delete_outline, size: 20, color: Colors.black54,))
100 | ],
101 | )
102 | ],
103 | ),
104 | );
105 | }
106 |
107 | //--------------------------------------------------------------------------//
108 | void _selectTitle(int index) {
109 | final provider = context.read();
110 |
111 | _selectedIndex = index;
112 | provider.curGroupId = _titles[index]["groupid"];
113 | MyEventBus().fire(CloseDrawerEvent());
114 | MyEventBus().fire(LoadHistoryGroupListEvent());
115 | setState(() {});
116 | }
117 |
118 | //--------------------------------------------------------------------------//
119 | void _startSearch() async {
120 | _showWait = true;
121 | setState(() {});
122 |
123 | final provider = context.read();
124 | if (_search.text != "") {
125 | _titles = await provider.qdb.searchKeywords(_search.text);
126 | }
127 | _showWait = false;
128 | setState(() {});
129 | }
130 |
131 | //--------------------------------------------------------------------------//
132 | Widget _searchPanel() {
133 | return Container(
134 | margin: EdgeInsets.all(10),
135 | decoration: BoxDecoration(
136 | border: Border.all(width: 1, color: Colors.indigo),
137 | ),
138 | child: Row(
139 | children: [
140 | Expanded(
141 | child: TextField(
142 | controller: _search,
143 | decoration: InputDecoration(
144 | hintText: tr("l_search"),
145 | border: InputBorder.none,
146 | contentPadding: EdgeInsets.fromLTRB(10, 0, 10, 0),
147 | ),
148 | onSubmitted: (String value) {
149 | _startSearch();
150 | },
151 | ),
152 | ),
153 | IconButton(
154 | icon: Icon(Icons.search),
155 | onPressed: _startSearch,
156 | ),
157 | IconButton(
158 | icon: Icon(Icons.clear),
159 | onPressed: () {
160 | FocusScope.of(context).requestFocus(FocusNode());
161 | _search.text = "";
162 | _loadData();
163 | },
164 | )
165 | ],
166 | ),
167 | );
168 | }
169 |
170 | //--------------------------------------------------------------------------//
171 | Future _onRefresh() async {
172 | return _loadData(refresh: true);
173 | }
174 |
175 | //--------------------------------------------------------------------------//
176 | Widget _buildList() {
177 | if (_showWait) {
178 | return Center(child: CircularProgressIndicator());
179 | }
180 |
181 | if (_titles.isEmpty) {
182 | return RefreshIndicator(
183 | onRefresh: _onRefresh,
184 | child: ListView(
185 | children: [
186 | Container(
187 | height: 100,
188 | alignment: Alignment.center,
189 | child: Text(tr("l_no_items")),
190 | ),
191 | ],
192 | ),
193 | );
194 | }
195 |
196 | return RefreshIndicator(
197 | onRefresh: _onRefresh,
198 | child: ListView.builder(
199 | itemCount: _titles.length,
200 | itemBuilder: (context, index) {
201 | return Material(
202 | color: Colors.transparent,
203 | child: InkWell(
204 | onTap: () {
205 | _selectTitle(index);
206 | },
207 | child: _titlePanel(index),
208 | ),
209 | );
210 | },
211 | ),
212 | );
213 | }
214 |
215 | //--------------------------------------------------------------------------//
216 | @override
217 | Widget build(BuildContext context) {
218 | return Container(
219 | color: Colors.white,
220 | child: Column(
221 | children: [
222 | _searchPanel(),
223 | Expanded(
224 | child: _buildList(),
225 | ),
226 | ],
227 | ),
228 | );
229 | }
230 | }
231 |
--------------------------------------------------------------------------------
/macos/.gitignore:
--------------------------------------------------------------------------------
1 | # Flutter-related
2 | **/Flutter/ephemeral/
3 | **/Pods/
4 |
5 | # Xcode-related
6 | **/dgph
7 | **/xcuserdata/
8 |
--------------------------------------------------------------------------------
/macos/Flutter/Flutter-Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
2 | #include "ephemeral/Flutter-Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/macos/Flutter/Flutter-Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
2 | #include "ephemeral/Flutter-Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/macos/Flutter/GeneratedPluginRegistrant.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Generated file. Do not edit.
3 | //
4 |
5 | import FlutterMacOS
6 | import Foundation
7 |
8 | import file_selector_macos
9 | import package_info_plus
10 | import path_provider_foundation
11 | import share_plus
12 | import shared_preferences_foundation
13 | import sqflite_darwin
14 | import url_launcher_macos
15 |
16 | func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
17 | FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
18 | FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
19 | PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
20 | SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
21 | SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
22 | SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
23 | UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
24 | }
25 |
--------------------------------------------------------------------------------
/macos/Podfile:
--------------------------------------------------------------------------------
1 | platform :osx, '10.14'
2 |
3 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
4 | ENV['COCOAPODS_DISABLE_STATS'] = 'true'
5 |
6 | project 'Runner', {
7 | 'Debug' => :debug,
8 | 'Profile' => :release,
9 | 'Release' => :release,
10 | }
11 |
12 | def flutter_root
13 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__)
14 | unless File.exist?(generated_xcode_build_settings_path)
15 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first"
16 | end
17 |
18 | File.foreach(generated_xcode_build_settings_path) do |line|
19 | matches = line.match(/FLUTTER_ROOT\=(.*)/)
20 | return matches[1].strip if matches
21 | end
22 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\""
23 | end
24 |
25 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
26 |
27 | flutter_macos_podfile_setup
28 |
29 | target 'Runner' do
30 | use_frameworks!
31 | use_modular_headers!
32 |
33 | flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__))
34 | target 'RunnerTests' do
35 | inherit! :search_paths
36 | end
37 | end
38 |
39 | post_install do |installer|
40 | installer.pods_project.targets.each do |target|
41 | flutter_additional_macos_build_settings(target)
42 | end
43 | end
44 |
--------------------------------------------------------------------------------
/macos/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - file_selector_macos (0.0.1):
3 | - FlutterMacOS
4 | - FlutterMacOS (1.0.0)
5 | - package_info_plus (0.0.1):
6 | - FlutterMacOS
7 | - path_provider_foundation (0.0.1):
8 | - Flutter
9 | - FlutterMacOS
10 | - share_plus (0.0.1):
11 | - FlutterMacOS
12 | - shared_preferences_foundation (0.0.1):
13 | - Flutter
14 | - FlutterMacOS
15 | - sqflite_darwin (0.0.4):
16 | - Flutter
17 | - FlutterMacOS
18 | - url_launcher_macos (0.0.1):
19 | - FlutterMacOS
20 |
21 | DEPENDENCIES:
22 | - file_selector_macos (from `Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos`)
23 | - FlutterMacOS (from `Flutter/ephemeral`)
24 | - package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`)
25 | - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
26 | - share_plus (from `Flutter/ephemeral/.symlinks/plugins/share_plus/macos`)
27 | - shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`)
28 | - sqflite_darwin (from `Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin`)
29 | - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
30 |
31 | EXTERNAL SOURCES:
32 | file_selector_macos:
33 | :path: Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos
34 | FlutterMacOS:
35 | :path: Flutter/ephemeral
36 | package_info_plus:
37 | :path: Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos
38 | path_provider_foundation:
39 | :path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin
40 | share_plus:
41 | :path: Flutter/ephemeral/.symlinks/plugins/share_plus/macos
42 | shared_preferences_foundation:
43 | :path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin
44 | sqflite_darwin:
45 | :path: Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin
46 | url_launcher_macos:
47 | :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos
48 |
49 | SPEC CHECKSUMS:
50 | file_selector_macos: cc3858c981fe6889f364731200d6232dac1d812d
51 | FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
52 | package_info_plus: 12f1c5c2cfe8727ca46cbd0b26677728972d9a5b
53 | path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
54 | share_plus: 1fa619de8392a4398bfaf176d441853922614e89
55 | shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
56 | sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d
57 | url_launcher_macos: c82c93949963e55b228a30115bd219499a6fe404
58 |
59 | PODFILE CHECKSUM: 236401fc2c932af29a9fcf0e97baeeb2d750d367
60 |
61 | COCOAPODS: 1.16.2
62 |
--------------------------------------------------------------------------------
/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
37 |
38 |
39 |
40 |
43 |
49 |
50 |
51 |
52 |
53 |
63 |
65 |
71 |
72 |
73 |
74 |
80 |
82 |
88 |
89 |
90 |
91 |
93 |
94 |
97 |
98 |
99 |
--------------------------------------------------------------------------------
/macos/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/macos/Runner/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import Cocoa
2 | import FlutterMacOS
3 |
4 | @main
5 | class AppDelegate: FlutterAppDelegate {
6 | override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
7 | return true
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bipark/my_ollama_app/95a479cd7ab5513fd491247f95b51054d4a6a288/macos/Runner/Assets.xcassets/AppIcon.appiconset/1024.png
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bipark/my_ollama_app/95a479cd7ab5513fd491247f95b51054d4a6a288/macos/Runner/Assets.xcassets/AppIcon.appiconset/128.png
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bipark/my_ollama_app/95a479cd7ab5513fd491247f95b51054d4a6a288/macos/Runner/Assets.xcassets/AppIcon.appiconset/16.png
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bipark/my_ollama_app/95a479cd7ab5513fd491247f95b51054d4a6a288/macos/Runner/Assets.xcassets/AppIcon.appiconset/256.png
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bipark/my_ollama_app/95a479cd7ab5513fd491247f95b51054d4a6a288/macos/Runner/Assets.xcassets/AppIcon.appiconset/32.png
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bipark/my_ollama_app/95a479cd7ab5513fd491247f95b51054d4a6a288/macos/Runner/Assets.xcassets/AppIcon.appiconset/512.png
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bipark/my_ollama_app/95a479cd7ab5513fd491247f95b51054d4a6a288/macos/Runner/Assets.xcassets/AppIcon.appiconset/64.png
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {"images":[{"size":"128x128","expected-size":"128","filename":"128.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"1x"},{"size":"256x256","expected-size":"256","filename":"256.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"1x"},{"size":"128x128","expected-size":"256","filename":"256.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"2x"},{"size":"256x256","expected-size":"512","filename":"512.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"2x"},{"size":"32x32","expected-size":"32","filename":"32.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"1x"},{"size":"512x512","expected-size":"512","filename":"512.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"1x"},{"size":"16x16","expected-size":"16","filename":"16.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"1x"},{"size":"16x16","expected-size":"32","filename":"32.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"2x"},{"size":"32x32","expected-size":"64","filename":"64.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"2x"},{"size":"512x512","expected-size":"1024","filename":"1024.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"2x"}]}
--------------------------------------------------------------------------------
/macos/Runner/Base.lproj/MainMenu.xib:
--------------------------------------------------------------------------------
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 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
297 |
298 |
299 |
300 |
301 |
302 |
303 |
304 |
305 |
306 |
307 |
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 |
318 |
319 |
320 |
321 |
322 |
323 |
324 |
325 |
326 |
327 |
328 |
329 |
330 |
331 |
332 |
333 |
334 |
335 |
336 |
337 |
338 |
339 |
340 |
341 |
342 |
343 |
344 |
--------------------------------------------------------------------------------
/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 = my_ollama
9 |
10 | // The application's bundle identifier
11 | PRODUCT_BUNDLE_IDENTIFIER = com.rtlink.myOllama
12 |
13 | // The copyright displayed in application information
14 | PRODUCT_COPYRIGHT = Copyright © 2024 com.rtlink. All rights reserved.
15 |
--------------------------------------------------------------------------------
/macos/Runner/Configs/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include "../../Flutter/Flutter-Debug.xcconfig"
2 | #include "Warnings.xcconfig"
3 |
--------------------------------------------------------------------------------
/macos/Runner/Configs/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include "../../Flutter/Flutter-Release.xcconfig"
2 | #include "Warnings.xcconfig"
3 |
--------------------------------------------------------------------------------
/macos/Runner/Configs/Warnings.xcconfig:
--------------------------------------------------------------------------------
1 | WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings
2 | GCC_WARN_UNDECLARED_SELECTOR = YES
3 | CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES
4 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE
5 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES
6 | CLANG_WARN_PRAGMA_PACK = YES
7 | CLANG_WARN_STRICT_PROTOTYPES = YES
8 | CLANG_WARN_COMMA = YES
9 | GCC_WARN_STRICT_SELECTOR_MATCH = YES
10 | CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES
11 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES
12 | GCC_WARN_SHADOW = YES
13 | CLANG_WARN_UNREACHABLE_CODE = YES
14 |
--------------------------------------------------------------------------------
/macos/Runner/DebugProfile.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.cs.allow-jit
8 |
9 | com.apple.security.files.user-selected.read-only
10 |
11 | com.apple.security.network.client
12 |
13 | com.apple.security.network.server
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/macos/Runner/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIconFile
10 |
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | $(FLUTTER_BUILD_NAME)
21 | CFBundleVersion
22 | $(FLUTTER_BUILD_NUMBER)
23 | LSMinimumSystemVersion
24 | $(MACOSX_DEPLOYMENT_TARGET)
25 | NSAppTransportSecurity
26 |
27 | NSAllowsArbitraryLoads
28 |
29 |
30 | NSHumanReadableCopyright
31 | $(PRODUCT_COPYRIGHT)
32 | NSMainNibFile
33 | MainMenu
34 | NSPhotoLibraryUsageDescription
35 | The app needs permission to use the photo album to analyze photos.
36 | NSPrincipalClass
37 | NSApplication
38 |
39 |
40 |
--------------------------------------------------------------------------------
/macos/Runner/MainFlutterWindow.swift:
--------------------------------------------------------------------------------
1 | import Cocoa
2 | import FlutterMacOS
3 |
4 | class MainFlutterWindow: NSWindow {
5 | override func awakeFromNib() {
6 | let flutterViewController = FlutterViewController()
7 | let windowFrame = self.frame
8 | self.contentViewController = flutterViewController
9 | self.setFrame(windowFrame, display: true)
10 |
11 | RegisterGeneratedPlugins(registry: flutterViewController)
12 |
13 | super.awakeFromNib()
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/macos/Runner/Release.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.files.user-selected.read-only
8 |
9 | com.apple.security.network.client
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 |
--------------------------------------------------------------------------------
/my_ollama.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: my_ollama
2 | description: "MyOllama"
3 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev
4 |
5 | version: 1.0.8+41204
6 |
7 | environment:
8 | sdk: ^3.5.4
9 |
10 | dependencies:
11 | flutter:
12 | sdk: flutter
13 |
14 | cupertino_icons: ^1.0.8
15 | provider: ^6.1.2
16 | flutter_advanced_drawer: ^1.3.7
17 | sqflite: ^2.4.1
18 | intl: ^0.19.0
19 | event_bus: ^2.0.0
20 | flutter_chat_ui: ^1.6.15
21 | uuid: ^3.0.7
22 | ollama_dart: ^0.2.2
23 | image_picker: ^1.1.2
24 | shared_preferences: ^2.3.3
25 | package_info_plus: ^8.1.1
26 | dart_ping: ^9.0.1
27 | url_launcher: ^6.3.1
28 | easy_localization: ^3.0.7
29 | app_settings: ^5.1.1
30 | flutter_styled_toast: ^2.2.1
31 | share_plus: ^10.1.2
32 | flutter_markdown: ^0.7.4+3
33 | split_view: ^3.2.1
34 | sqflite_common_ffi: ^2.3.0
35 |
36 | dev_dependencies:
37 | flutter_test:
38 | sdk: flutter
39 |
40 | flutter_lints: ^4.0.0
41 |
42 | flutter:
43 | uses-material-design: true
44 | assets:
45 | - assets/images/
46 | - assets/translations/
47 |
48 |
--------------------------------------------------------------------------------
/test/widget_test.dart:
--------------------------------------------------------------------------------
1 | // This is a basic Flutter widget test.
2 | //
3 | // To perform an interaction with a widget in your test, use the WidgetTester
4 | // utility in the flutter_test package. For example, you can send tap and scroll
5 | // gestures. You can also use WidgetTester to find child widgets in the widget
6 | // tree, read text, and verify that the values of widget properties are correct.
7 |
8 | import 'package:flutter/material.dart';
9 | import 'package:flutter_test/flutter_test.dart';
10 |
11 | import 'package:my_ollama/main.dart';
12 |
13 | void main() {
14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async {
15 | // Build our app and trigger a frame.
16 | await tester.pumpWidget(const MyApp());
17 |
18 | // Verify that our counter starts at 0.
19 | expect(find.text('0'), findsOneWidget);
20 | expect(find.text('1'), findsNothing);
21 |
22 | // Tap the '+' icon and trigger a frame.
23 | await tester.tap(find.byIcon(Icons.add));
24 | await tester.pump();
25 |
26 | // Verify that our counter has incremented.
27 | expect(find.text('0'), findsNothing);
28 | expect(find.text('1'), findsOneWidget);
29 | });
30 | }
31 |
--------------------------------------------------------------------------------