├── .gitignore ├── .idx └── dev.nix ├── .metadata ├── .vscode └── settings.json ├── README.md ├── README ├── chat.png ├── history.png ├── home.png ├── mobile_chat.png ├── mobile_sidebar.png ├── setting.png └── setting_api.png ├── analysis_options.yaml ├── android ├── .gitignore ├── app │ ├── build.gradle.kts │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ │ └── dev │ │ │ │ └── hret │ │ │ │ └── ai_client │ │ │ │ └── MainActivity.java │ │ └── res │ │ │ ├── drawable-v21 │ │ │ └── launch_background.xml │ │ │ ├── drawable │ │ │ └── launch_background.xml │ │ │ ├── mipmap-hdpi │ │ │ └── launcher_icon.png │ │ │ ├── mipmap-mdpi │ │ │ └── launcher_icon.png │ │ │ ├── mipmap-xhdpi │ │ │ └── launcher_icon.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── launcher_icon.png │ │ │ ├── mipmap-xxxhdpi │ │ │ └── launcher_icon.png │ │ │ ├── values-night │ │ │ └── styles.xml │ │ │ └── values │ │ │ └── styles.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle.kts ├── build │ └── reports │ │ └── problems │ │ └── problems-report.html ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties └── settings.gradle.kts ├── assets ├── assistant │ ├── claude.svg │ ├── deepseek.svg │ ├── gemini.svg │ ├── grok.svg │ ├── ollama.svg │ ├── openai.svg │ └── qwen.svg ├── icon │ └── icon.png └── translations │ ├── en-US.json │ ├── ja-JP.json │ ├── ko-KR.json │ ├── zh-CN.json │ └── zh-TW.json ├── devtools_options.yaml ├── distribute_options.yaml ├── flutter_launcher_icons.yaml ├── ios ├── .gitignore ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ └── Release.xcconfig ├── Podfile ├── Podfile.lock ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings ├── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-50x50@1x.png │ │ │ ├── Icon-App-50x50@2x.png │ │ │ ├── Icon-App-57x57@1x.png │ │ │ ├── Icon-App-57x57@2x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-72x72@1x.png │ │ │ ├── Icon-App-72x72@2x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── Info.plist │ └── Runner-Bridging-Header.h └── RunnerTests │ └── RunnerTests.swift ├── ios_build.sh ├── lib ├── common │ ├── theme.dart │ ├── toast.dart │ └── utils │ │ ├── chat_http.dart │ │ ├── loading_indicator.dart │ │ └── message_markdown.dart ├── database │ ├── app_database.dart │ ├── app_database.g.dart │ ├── database_connection.dart │ ├── database_connection_native.dart │ └── database_connection_web.dart ├── generated │ ├── default_api_configs.dart │ └── locale_keys.dart ├── main.dart ├── models │ ├── ai_api.dart │ ├── chat_message.dart │ ├── chat_session.dart │ └── file_attachment.dart ├── pages │ ├── chat │ │ ├── chat_page.dart │ │ ├── chat_provider.dart │ │ ├── input.dart │ │ ├── message_action_buttons.dart │ │ └── message_list.dart │ ├── history │ │ └── history_list.dart │ ├── home_page.dart │ └── settings │ │ ├── apis │ │ ├── api_info.dart │ │ ├── api_list.dart │ │ └── api_settings.dart │ │ ├── language_settings.dart │ │ └── settings_page.dart ├── repositories │ ├── ai_api_repository.dart │ ├── chat_message_repository.dart │ ├── chat_session_repository.dart │ └── file_attachment_repository.dart └── services │ ├── ai_api_service.dart │ ├── chat_message_service.dart │ ├── chat_session_service.dart │ └── file_attachment_service.dart ├── linux ├── .gitignore ├── CMakeLists.txt ├── flutter │ ├── CMakeLists.txt │ ├── generated_plugin_registrant.cc │ ├── generated_plugin_registrant.h │ └── generated_plugins.cmake ├── packaging │ ├── appimage │ │ └── make_config.yaml │ ├── deb │ │ └── make_config.yaml │ └── rpm │ │ └── make_config.yaml └── runner │ ├── CMakeLists.txt │ ├── main.cc │ ├── my_application.cc │ └── my_application.h ├── macos ├── .gitignore ├── Flutter │ ├── Flutter-Debug.xcconfig │ ├── Flutter-Release.xcconfig │ └── GeneratedPluginRegistrant.swift ├── Podfile ├── Podfile.lock ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── app_icon_1024.png │ │ │ ├── app_icon_128.png │ │ │ ├── app_icon_16.png │ │ │ ├── app_icon_256.png │ │ │ ├── app_icon_32.png │ │ │ ├── app_icon_512.png │ │ │ └── app_icon_64.png │ ├── Base.lproj │ │ └── MainMenu.xib │ ├── Configs │ │ ├── AppInfo.xcconfig │ │ ├── Debug.xcconfig │ │ ├── Release.xcconfig │ │ └── Warnings.xcconfig │ ├── DebugProfile.entitlements │ ├── Info.plist │ ├── MainFlutterWindow.swift │ └── Release.entitlements ├── RunnerTests │ └── RunnerTests.swift └── packaging │ └── dmg │ └── make_config.yaml ├── pubspec.lock ├── pubspec.yaml ├── setup.dart ├── web ├── drift_worker.dart.js ├── favicon.png ├── icons │ ├── Icon-192.png │ ├── Icon-512.png │ ├── Icon-maskable-192.png │ └── Icon-maskable-512.png ├── index.html ├── manifest.json └── sqlite3.wasm └── windows ├── .gitignore ├── CMakeLists.txt ├── flutter ├── CMakeLists.txt ├── generated_plugin_registrant.cc ├── generated_plugin_registrant.h └── generated_plugins.cmake ├── packaging └── exe │ └── make_config.yaml └── runner ├── CMakeLists.txt ├── Runner.rc ├── flutter_window.cpp ├── flutter_window.h ├── main.cpp ├── resource.h ├── resources └── app_icon.ico ├── runner.exe.manifest ├── utils.cpp ├── utils.h ├── win32_window.cpp └── win32_window.h /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .build/ 9 | .buildlog/ 10 | .history 11 | .svn/ 12 | .swiftpm/ 13 | migrate_working_dir/ 14 | 15 | # IntelliJ related 16 | *.iml 17 | *.ipr 18 | *.iws 19 | .idea/ 20 | 21 | # The .vscode folder contains launch configuration and tasks you configure in 22 | # VS Code which you may wish to be included in version control, so this line 23 | # is commented out by default. 24 | #.vscode/ 25 | 26 | # Flutter/Dart/Pub related 27 | **/doc/api/ 28 | **/ios/Flutter/.last_build_id 29 | .dart_tool/ 30 | .flutter-plugins 31 | .flutter-plugins-dependencies 32 | .pub-cache/ 33 | .pub/ 34 | /build/ 35 | /dist/ 36 | 37 | # Symbolication related 38 | app.*.symbols 39 | 40 | # Obfuscation related 41 | app.*.map.json 42 | 43 | # Android Studio will place build artifacts here 44 | /android/app/debug 45 | /android/app/profile 46 | /android/app/release 47 | 48 | # Android 签名密钥文件 49 | android/app/*keystore.p12 -------------------------------------------------------------------------------- /.idx/dev.nix: -------------------------------------------------------------------------------- 1 | {pkgs}: { 2 | channel = "stable-24.05"; 3 | packages = [ 4 | pkgs.jdk17 5 | pkgs.unzip 6 | pkgs.dart 7 | ]; 8 | idx.extensions = [ 9 | 10 | ]; 11 | idx.previews = { 12 | previews = { 13 | web = { 14 | command = [ 15 | "flutter" 16 | "run" 17 | "--machine" 18 | "-d" 19 | "web-server" 20 | "--web-hostname" 21 | "0.0.0.0" 22 | "--web-port" 23 | "$PORT" 24 | ]; 25 | manager = "flutter"; 26 | }; 27 | android = { 28 | command = [ 29 | "flutter" 30 | "run" 31 | "--machine" 32 | "-d" 33 | "android" 34 | "-d" 35 | "localhost:5555" 36 | ]; 37 | manager = "flutter"; 38 | }; 39 | }; 40 | }; 41 | } -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: "35c388afb57ef061d06a39b537336c87e0e3d1b1" 8 | channel: "stable" 9 | 10 | project_type: app 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 17 | base_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 18 | - platform: android 19 | create_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 20 | base_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 21 | - platform: ios 22 | create_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 23 | base_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 24 | - platform: linux 25 | create_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 26 | base_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 27 | - platform: macos 28 | create_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 29 | base_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 30 | - platform: web 31 | create_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 32 | base_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 33 | - platform: windows 34 | create_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 35 | base_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 36 | 37 | # User provided section 38 | 39 | # List of Local paths (relative to this file) that should be 40 | # ignored by the migrate tool. 41 | # 42 | # Files that are not part of the templates will be ignored by default. 43 | unmanaged_files: 44 | - 'lib/main.dart' 45 | - 'ios/Runner.xcodeproj/project.pbxproj' 46 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "IDX.aI.enableInlineCompletion": true, 3 | "IDX.aI.enableCodebaseIndexing": true 4 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AiClient 2 | 3 | AiClient 是一个跨平台的 AI 聊天客户端应用程序,支持多种 AI API 服务商和多语言界面。 4 | 5 | ## 功能预览 6 | ### 移动端 7 | chat 8 | chat 9 | 10 | ### 桌面端 11 | chat 12 | chat 13 | chat 14 | chat 15 | chat 16 | 17 | 18 | ## 技术栈 19 | 20 | - 使用 Flutter/Dart 开发 21 | - 支持 Android、iOS、Windows、macOS、Linux、Web 平台 22 | 23 | ## TODO 24 | 25 | - 消息回复中时中断回复 26 | - 消息重发保存上一条消息 27 | - 聊天附件功能 28 | 29 | ## 本地开发指南 30 | 31 | ### 环境要求 32 | ```yaml 33 | Flutter: ">=3.0.0 <4.0.0" 34 | Dart: ">=3.0.0 <4.0.0" 35 | MacOS: macOS 10.15+(用于macOS开发) 36 | iOS: Xcode 13.0+(用于iOS开发) 37 | Android: Android SDK(用于Android开发) 38 | ``` 39 | 40 | ### 1. 项目获取配置 41 | 42 | #### 1.1 克隆项目 43 | ```bash 44 | # 克隆项目 45 | git clone https://github.com/HRET-Dev/AiClient.git 46 | 47 | # 进入项目 48 | cd AiClient 49 | ``` 50 | #### 1.2 安装依赖 51 | ```bash 52 | flutter pub get 53 | ``` 54 | 55 | #### 1.3 代码生成 56 | ```bash 57 | # 生成drift相关代码 58 | dart run build_runner build 59 | ``` 60 | 61 | ### 2. ios、Android 平台特定配置 62 |
63 | Android 配置 64 |
65 | 66 | #### 2.1 环境要求 67 | ```yaml 68 | Jdk: 17+ 69 | ``` 70 | 71 | #### 2.2 生成签名文件 跟随提示输入即可 72 | **PS:请牢记输入的密钥库口令和密钥口令** 73 | ```bash 74 | keytool -genkeypair -alias aiclient-key -keyalg RSA -keysize 2048 -validity 36500 -keystore android/app/aiclient-keystore.p12 -storetype PKCS12 75 | ``` 76 | 77 | #### 2.3 配置签名文件 78 | ```bash 79 | cat < android/key.properties 80 | storePassword=密钥库口令 81 | keyPassword=密钥口令 82 | keyAlias=aiclient-key 83 | storeFile=../app/aiclient-keystore.p12 84 | EOF 85 | ``` 86 | 87 |
88 | 89 | ### 🎉 运行项目 90 | ```bash 91 | # 运行项目 92 | flutter run 93 | ``` -------------------------------------------------------------------------------- /README/chat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HRET-Dev/AiClient/0dbc366dc49a2cdb4087975b18af9b04a6a90af5/README/chat.png -------------------------------------------------------------------------------- /README/history.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HRET-Dev/AiClient/0dbc366dc49a2cdb4087975b18af9b04a6a90af5/README/history.png -------------------------------------------------------------------------------- /README/home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HRET-Dev/AiClient/0dbc366dc49a2cdb4087975b18af9b04a6a90af5/README/home.png -------------------------------------------------------------------------------- /README/mobile_chat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HRET-Dev/AiClient/0dbc366dc49a2cdb4087975b18af9b04a6a90af5/README/mobile_chat.png -------------------------------------------------------------------------------- /README/mobile_sidebar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HRET-Dev/AiClient/0dbc366dc49a2cdb4087975b18af9b04a6a90af5/README/mobile_sidebar.png -------------------------------------------------------------------------------- /README/setting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HRET-Dev/AiClient/0dbc366dc49a2cdb4087975b18af9b04a6a90af5/README/setting.png -------------------------------------------------------------------------------- /README/setting_api.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HRET-Dev/AiClient/0dbc366dc49a2cdb4087975b18af9b04a6a90af5/README/setting_api.png -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:flutter_lints/flutter.yaml 2 | 3 | linter: 4 | rules: 5 | prefer_const_constructors: false 6 | prefer_final_fields: false 7 | use_key_in_widget_constructors: false 8 | prefer_const_literals_to_create_immutables: false 9 | prefer_const_constructors_in_immutables: false 10 | avoid_print: false -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | .cxx/ 9 | 10 | # Remember to never publicly share your keystore. 11 | # See https://flutter.dev/to/reference-keystore 12 | key.properties 13 | **/*.keystore 14 | **/*.jks 15 | -------------------------------------------------------------------------------- /android/app/build.gradle.kts: -------------------------------------------------------------------------------- 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 | import java.util.Properties 9 | import java.io.FileInputStream 10 | 11 | // 加载密钥属性文件 12 | val keystorePropertiesFile = rootProject.file("key.properties") 13 | val keystoreProperties = Properties() 14 | if (keystorePropertiesFile.exists()) { 15 | keystoreProperties.load(FileInputStream(keystorePropertiesFile)) 16 | } 17 | 18 | android { 19 | namespace = "dev.hret.AiClient" 20 | compileSdk = flutter.compileSdkVersion 21 | ndkVersion = "27.0.12077973" 22 | 23 | compileOptions { 24 | sourceCompatibility = JavaVersion.VERSION_17 25 | targetCompatibility = JavaVersion.VERSION_17 26 | } 27 | 28 | kotlinOptions { 29 | jvmTarget = "17" 30 | } 31 | 32 | // 配置签名 33 | signingConfigs { 34 | create("release") { 35 | keyAlias = keystoreProperties["keyAlias"].toString() 36 | keyPassword = keystoreProperties["keyPassword"].toString() 37 | storeFile = file(keystoreProperties["storeFile"].toString()) 38 | storePassword = keystoreProperties["storePassword"].toString() 39 | storeType = "PKCS12" 40 | } 41 | } 42 | 43 | defaultConfig { 44 | applicationId = "dev.hret.AiClient" 45 | minSdk = flutter.minSdkVersion 46 | targetSdk = flutter.targetSdkVersion 47 | versionCode = flutter.versionCode 48 | versionName = flutter.versionName 49 | } 50 | 51 | buildTypes { 52 | release { 53 | // 使用发布签名配置 54 | signingConfig = signingConfigs.getByName("release") 55 | } 56 | } 57 | } 58 | 59 | flutter { 60 | source = "../.." 61 | } 62 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 18 | 22 | 23 | 24 | 25 | 26 | 30 | 31 | 33 | 36 | 37 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /android/app/src/main/java/dev/hret/ai_client/MainActivity.java: -------------------------------------------------------------------------------- 1 | package dev.hret.AiClient; 2 | 3 | import io.flutter.embedding.android.FlutterActivity; 4 | 5 | public class MainActivity extends 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/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HRET-Dev/AiClient/0dbc366dc49a2cdb4087975b18af9b04a6a90af5/android/app/src/main/res/mipmap-hdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HRET-Dev/AiClient/0dbc366dc49a2cdb4087975b18af9b04a6a90af5/android/app/src/main/res/mipmap-mdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HRET-Dev/AiClient/0dbc366dc49a2cdb4087975b18af9b04a6a90af5/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HRET-Dev/AiClient/0dbc366dc49a2cdb4087975b18af9b04a6a90af5/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HRET-Dev/AiClient/0dbc366dc49a2cdb4087975b18af9b04a6a90af5/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.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.kts: -------------------------------------------------------------------------------- 1 | allprojects { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | } 6 | } 7 | 8 | val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get() 9 | rootProject.layout.buildDirectory.value(newBuildDir) 10 | 11 | subprojects { 12 | val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) 13 | project.layout.buildDirectory.value(newSubprojectBuildDir) 14 | } 15 | subprojects { 16 | project.evaluationDependsOn(":app") 17 | } 18 | 19 | tasks.register("clean") { 20 | delete(rootProject.layout.buildDirectory) 21 | } 22 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -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.13-all.zip 6 | -------------------------------------------------------------------------------- /android/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | val flutterSdkPath = run { 3 | val properties = java.util.Properties() 4 | file("local.properties").inputStream().use { properties.load(it) } 5 | val flutterSdkPath = properties.getProperty("flutter.sdk") 6 | require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } 7 | 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.0" apply false 22 | id("org.jetbrains.kotlin.android") version "1.8.22" apply false 23 | } 24 | 25 | include(":app") 26 | -------------------------------------------------------------------------------- /assets/assistant/claude.svg: -------------------------------------------------------------------------------- 1 | Claude -------------------------------------------------------------------------------- /assets/assistant/deepseek.svg: -------------------------------------------------------------------------------- 1 | DeepSeek -------------------------------------------------------------------------------- /assets/assistant/gemini.svg: -------------------------------------------------------------------------------- 1 | Gemini -------------------------------------------------------------------------------- /assets/assistant/grok.svg: -------------------------------------------------------------------------------- 1 | Grok -------------------------------------------------------------------------------- /assets/assistant/ollama.svg: -------------------------------------------------------------------------------- 1 | Ollama -------------------------------------------------------------------------------- /assets/assistant/openai.svg: -------------------------------------------------------------------------------- 1 | OpenAI -------------------------------------------------------------------------------- /assets/assistant/qwen.svg: -------------------------------------------------------------------------------- 1 | Qwen -------------------------------------------------------------------------------- /assets/icon/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HRET-Dev/AiClient/0dbc366dc49a2cdb4087975b18af9b04a6a90af5/assets/icon/icon.png -------------------------------------------------------------------------------- /assets/translations/en-US.json: -------------------------------------------------------------------------------- 1 | { 2 | "Chat": "Chat", 3 | "History": "History", 4 | "Settings": "Settings", 5 | "save": "Save", 6 | "cancel": "Cancel", 7 | "clear": "Clear", 8 | "delete": "Delete", 9 | "confirm": "Confirm", 10 | "confirm_delete": "Confirm Delete", 11 | "confirm_delete_message": "Are you sure you want to delete this? This action cannot be undone.", 12 | "share": "Share", 13 | "shared": "Shared", 14 | "import": "Import", 15 | "clipboard_empty": "Clipboard is empty", 16 | "this_feature_is_under_development": "This feature is under development", 17 | "please_select": "Please select", 18 | "copy": "Copy", 19 | "copy_success": "Copied successfully", 20 | "sidebar": { 21 | "show": "Show Sidebar", 22 | "hide": "Hide Sidebar", 23 | "app_name": "AI Client", 24 | "all_chats": "All Chats" 25 | }, 26 | "chat_page": { 27 | "new_chat": "New Chat", 28 | "thinking": "Thinking...", 29 | "input_hintText": "Type a message here..." 30 | }, 31 | "settings_page": { 32 | "api_setting": { 33 | "api_manager": "API Management", 34 | "add_api": "Add API", 35 | "edit_api": "Edit API", 36 | "delete_api": "Delete API", 37 | "add_success": "API added successfully", 38 | "add_failed": "Failed to add API", 39 | "api_config_copied_to_clipboard": "Configuration copied to clipboard", 40 | "invalid_api_config_string": "Invalid configuration format", 41 | "failed_to_decrypt_api_config": "Failed to parse configuration", 42 | "api_config_imported": "Configuration imported successfully" 43 | }, 44 | "language_setting": { 45 | "language_button_text": "Language", 46 | "select_language": "Select Language", 47 | "language_list": { 48 | "zh_CN": "Simplified Chinese", 49 | "zh_TW": "Traditional Chinese", 50 | "en_US": "English (US)", 51 | "ja_JP": "Japanese", 52 | "ko_KR": "Korean" 53 | } 54 | } 55 | }, 56 | "ai_api_model": { 57 | "config": "API Configuration", 58 | "api_name": "API Name", 59 | "api_name_validate_failed": "API name cannot be empty", 60 | "api_provider": "API Provider", 61 | "api_type": "API Service Type", 62 | "api_base_url": "API URL", 63 | "api_base_url_validate_failed": "Please enter a valid API URL", 64 | "api_key": "API Key", 65 | "models": "Model List", 66 | "models_validate_failed": "Model list cannot be empty" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /assets/translations/ja-JP.json: -------------------------------------------------------------------------------- 1 | { 2 | "Chat": "チャット", 3 | "History": "履歴", 4 | "Settings": "設定", 5 | "save": "保存", 6 | "cancel": "キャンセル", 7 | "clear": "クリア", 8 | "delete": "削除", 9 | "confirm": "確認", 10 | "confirm_delete": "削除の確認", 11 | "confirm_delete_message": "削除してもよろしいですか?この操作は元に戻せません。", 12 | "share": "共有", 13 | "shared": "共有済み", 14 | "import": "インポート", 15 | "clipboard_empty": "クリップボードが空です", 16 | "this_feature_is_under_development": "この機能は開発中です", 17 | "please_select": "選択してください", 18 | "copy": "コピー", 19 | "copy_success": "コピー成功", 20 | "sidebar": { 21 | "show": "サイドバーを表示", 22 | "hide": "サイドバーを隠す", 23 | "app_name": "AI クライアント", 24 | "all_chats": "すべてのチャット" 25 | }, 26 | "chat_page": { 27 | "new_chat": "新しいチャット", 28 | "thinking": "考え中...", 29 | "input_hintText": "ここにメッセージを入力..." 30 | }, 31 | "settings_page": { 32 | "api_setting": { 33 | "api_manager": "API 管理", 34 | "add_api": "API 追加", 35 | "edit_api": "API 編集", 36 | "delete_api": "API 削除", 37 | "add_success": "API 追加成功", 38 | "add_failed": "API 追加失敗", 39 | "api_config_copied_to_clipboard": "設定情報がクリップボードにコピーされました", 40 | "invalid_api_config_string": "無効な設定情報形式", 41 | "failed_to_decrypt_api_config": "設定情報の解析に失敗しました", 42 | "api_config_imported": "設定情報のインポートに成功しました" 43 | }, 44 | "language_setting": { 45 | "language_button_text": "言語", 46 | "select_language": "言語を選択", 47 | "language_list": { 48 | "zh_CN": "簡体字中国語", 49 | "zh_TW": "繁体字中国語", 50 | "en_US": "英語(米国)", 51 | "ja_JP": "日本語", 52 | "ko_KR": "韓国語" 53 | } 54 | } 55 | }, 56 | "ai_api_model": { 57 | "config": "API 設定", 58 | "api_name": "API 名", 59 | "api_name_validate_failed": "API名は空にできません", 60 | "api_provider": "APIプロバイダー", 61 | "api_type": "APIサービスタイプ", 62 | "api_base_url": "API アドレス", 63 | "api_base_url_validate_failed": "有効なAPIアドレスを入力してください", 64 | "api_key": "APIキー", 65 | "models": "モデルリスト", 66 | "models_validate_failed": "モデルリストは空にできません" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /assets/translations/ko-KR.json: -------------------------------------------------------------------------------- 1 | { 2 | "Chat": "채팅", 3 | "History": "기록", 4 | "Settings": "설정", 5 | "save": "저장", 6 | "cancel": "취소", 7 | "clear": "지우기", 8 | "delete": "삭제", 9 | "confirm": "확인", 10 | "confirm_delete": "삭제 확인", 11 | "confirm_delete_message": "삭제하시겠습니까? 이 작업은 취소할 수 없습니다.", 12 | "share": "공유", 13 | "shared": "공유됨", 14 | "import": "가져오기", 15 | "clipboard_empty": "클립보드가 비어 있습니다", 16 | "this_feature_is_under_development": "이 기능은 개발 중입니다", 17 | "please_select": "선택해 주세요", 18 | "copy": "복사", 19 | "copy_success": "복사 성공", 20 | "sidebar": { 21 | "show": "사이드바 표시", 22 | "hide": "사이드바 숨기기", 23 | "app_name": "AI 클라이언트", 24 | "all_chats": "모든 채팅" 25 | }, 26 | "chat_page": { 27 | "new_chat": "새 채팅", 28 | "thinking": "생각 중...", 29 | "input_hintText": "여기에 메시지를 입력하세요..." 30 | }, 31 | "settings_page": { 32 | "api_setting": { 33 | "api_manager": "API 관리", 34 | "add_api": "API 추가", 35 | "edit_api": "API 편집", 36 | "delete_api": "API 삭제", 37 | "add_success": "API 추가 성공", 38 | "add_failed": "API 추가 실패", 39 | "api_config_copied_to_clipboard": "구성 정보가 클립보드에 복사되었습니다", 40 | "invalid_api_config_string": "잘못된 구성 정보 형식", 41 | "failed_to_decrypt_api_config": "구성 정보 분석 실패", 42 | "api_config_imported": "구성 정보 가져오기 성공" 43 | }, 44 | "language_setting": { 45 | "language_button_text": "언어", 46 | "select_language": "언어 선택", 47 | "language_list": { 48 | "zh_CN": "중국어 간체", 49 | "zh_TW": "중국어 번체", 50 | "en_US": "영어(미국)", 51 | "ja_JP": "일본어", 52 | "ko_KR": "한국어" 53 | } 54 | } 55 | }, 56 | "ai_api_model": { 57 | "config": "API 구성", 58 | "api_name": "API 이름", 59 | "api_name_validate_failed": "API 이름은 비워둘 수 없습니다", 60 | "api_provider": "API 제공업체", 61 | "api_type": "API 서비스 유형", 62 | "api_base_url": "API 주소", 63 | "api_base_url_validate_failed": "유효한 API 주소를 입력하세요", 64 | "api_key": "API 키", 65 | "models": "모델 목록", 66 | "models_validate_failed": "모델 목록은 비워둘 수 없습니다" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /assets/translations/zh-CN.json: -------------------------------------------------------------------------------- 1 | { 2 | "Chat": "聊天", 3 | "History": "历史", 4 | "Settings": "设置", 5 | "save": "保存", 6 | "cancel": "取消", 7 | "clear": "清除", 8 | "delete": "删除", 9 | "confirm": "确认", 10 | "confirm_delete": "确认删除", 11 | "confirm_delete_message": "确定要删除吗?此操作无法撤销。", 12 | "share": "分享", 13 | "shared": "已分享", 14 | "import": "导入", 15 | "clipboard_empty": "剪贴板为空", 16 | "this_feature_is_under_development": "该功能正在开发中", 17 | "please_select": "请选择", 18 | "copy": "复制", 19 | "copy_success": "复制成功", 20 | "sidebar": { 21 | "show": "显示侧边栏", 22 | "hide": "隐藏侧边栏", 23 | "app_name": "AI 客户端", 24 | "all_chats": "所有聊天" 25 | }, 26 | "chat_page": { 27 | "new_chat": "新聊天", 28 | "thinking": "正在思考中...", 29 | "input_hintText": "在这里输入消息..." 30 | }, 31 | "settings_page": { 32 | "api_setting": { 33 | "api_manager": "API 管理", 34 | "add_api": "添加 API", 35 | "edit_api": "编辑 API", 36 | "delete_api": "删除 API", 37 | "add_success": "API 添加成功", 38 | "add_failed": "API 添加失败", 39 | "api_config_copied_to_clipboard": "配置信息已复制到剪贴板", 40 | "invalid_api_config_string": "无效的配置信息格式", 41 | "failed_to_decrypt_api_config": "配置信息解析失败", 42 | "api_config_imported": "配置信息导入成功" 43 | }, 44 | "language_setting": { 45 | "language_button_text": "语言", 46 | "select_language": "选择语言", 47 | "language_list": { 48 | "zh_CN": "简体中文", 49 | "zh_TW": "繁体中文", 50 | "en_US": "英语(美国)", 51 | "ja_JP": "日语", 52 | "ko_KR": "韩语" 53 | } 54 | } 55 | }, 56 | "ai_api_model": { 57 | "config": "API 配置", 58 | "api_name": "API 名称", 59 | "api_name_validate_failed": "API 名称不能为空", 60 | "api_provider": "API 服务商", 61 | "api_type": "API 服务类型", 62 | "api_base_url": "API 地址", 63 | "api_base_url_validate_failed": "请输入有效的 API 地址", 64 | "api_key": "API 密钥", 65 | "models": "模型列表", 66 | "models_validate_failed": "模型列表不能为空" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /assets/translations/zh-TW.json: -------------------------------------------------------------------------------- 1 | { 2 | "Chat": "聊天", 3 | "History": "歷史", 4 | "Settings": "設置", 5 | "save": "保存", 6 | "cancel": "取消", 7 | "clear": "清除", 8 | "delete": "刪除", 9 | "confirm": "確認", 10 | "confirm_delete": "確認刪除", 11 | "confirm_delete_message": "確定要刪除嗎?此操作無法撤銷。", 12 | "share": "分享", 13 | "shared": "已分享", 14 | "import": "導入", 15 | "clipboard_empty": "剪貼板為空", 16 | "this_feature_is_under_development": "該功能正在開發中", 17 | "please_select": "請選擇", 18 | "copy": "複製", 19 | "copy_success": "複製成功", 20 | "sidebar": { 21 | "show": "顯示側邊欄", 22 | "hide": "隱藏側邊欄", 23 | "app_name": "AI 客戶端", 24 | "all_chats": "所有聊天" 25 | }, 26 | "chat_page": { 27 | "new_chat": "新聊天", 28 | "thinking": "正在思考中...", 29 | "input_hintText": "在這裡輸入消息..." 30 | }, 31 | "settings_page": { 32 | "api_setting": { 33 | "api_manager": "API 管理", 34 | "add_api": "添加 API", 35 | "edit_api": "編輯 API", 36 | "delete_api": "刪除 API", 37 | "add_success": "API 添加成功", 38 | "add_failed": "API 添加失敗", 39 | "api_config_copied_to_clipboard": "配置信息已複製到剪貼板", 40 | "invalid_api_config_string": "無效的配置信息格式", 41 | "failed_to_decrypt_api_config": "配置信息解析失敗", 42 | "api_config_imported": "配置信息導入成功" 43 | }, 44 | "language_setting": { 45 | "language_button_text": "語言", 46 | "select_language": "選擇語言", 47 | "language_list": { 48 | "zh_CN": "簡體中文", 49 | "zh_TW": "繁體中文", 50 | "en_US": "英語(美國)", 51 | "ja_JP": "日語", 52 | "ko_KR": "韓語" 53 | } 54 | } 55 | }, 56 | "ai_api_model": { 57 | "config": "API 配置", 58 | "api_name": "API 名稱", 59 | "api_name_validate_failed": "API 名稱不能為空", 60 | "api_provider": "API 服務商", 61 | "api_type": "API 服務類型", 62 | "api_base_url": "API 地址", 63 | "api_base_url_validate_failed": "請輸入有效的 API 地址", 64 | "api_key": "API 密鑰", 65 | "models": "模型列表", 66 | "models_validate_failed": "模型列表不能為空" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /distribute_options.yaml: -------------------------------------------------------------------------------- 1 | app_name: 'AiClient' 2 | output: 'dist/' -------------------------------------------------------------------------------- /flutter_launcher_icons.yaml: -------------------------------------------------------------------------------- 1 | # flutter pub run flutter_launcher_icons 2 | flutter_launcher_icons: 3 | image_path: "assets/icon/icon.png" 4 | 5 | android: "launcher_icon" 6 | # image_path_android: "assets/icon/icon.png" 7 | min_sdk_android: 21 # android min sdk min:16, default 21 8 | # adaptive_icon_background: "assets/icon/background.png" 9 | # adaptive_icon_foreground: "assets/icon/foreground.png" 10 | # adaptive_icon_monochrome: "assets/icon/monochrome.png" 11 | 12 | ios: true 13 | # image_path_ios: "assets/icon/icon.png" 14 | remove_alpha_channel_ios: true 15 | # image_path_ios_dark_transparent: "assets/icon/icon_dark.png" 16 | # image_path_ios_tinted_grayscale: "assets/icon/icon_tinted.png" 17 | # desaturate_tinted_to_grayscale_ios: true 18 | 19 | web: 20 | generate: true 21 | image_path: "assets/icon/icon.png" 22 | background_color: "#hexcode" 23 | theme_color: "#hexcode" 24 | 25 | windows: 26 | generate: true 27 | image_path: "assets/icon/icon.png" 28 | icon_size: 48 # min:48, max:256, default: 48 29 | 30 | macos: 31 | generate: true 32 | image_path: "assets/icon/icon.png" 33 | -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | **/dgph 2 | *.mode1v3 3 | *.mode2v3 4 | *.moved-aside 5 | *.pbxuser 6 | *.perspectivev3 7 | **/*sync/ 8 | .sconsign.dblite 9 | .tags* 10 | **/.vagrant/ 11 | **/DerivedData/ 12 | Icon? 13 | **/Pods/ 14 | **/.symlinks/ 15 | profile 16 | xcuserdata 17 | **/.generated/ 18 | Flutter/App.framework 19 | Flutter/Flutter.framework 20 | Flutter/Flutter.podspec 21 | Flutter/Generated.xcconfig 22 | Flutter/ephemeral/ 23 | Flutter/app.flx 24 | Flutter/app.zip 25 | Flutter/flutter_assets/ 26 | Flutter/flutter_export_environment.sh 27 | ServiceDefinitions.json 28 | Runner/GeneratedPluginRegistrant.* 29 | 30 | # Exceptions to above rules. 31 | !default.mode1v3 32 | !default.mode2v3 33 | !default.pbxuser 34 | !default.perspectivev3 35 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 12.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '12.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | use_frameworks! 32 | 33 | flutter_install_all_ios_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_ios_build_settings(target) 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - connectivity_plus (0.0.1): 3 | - Flutter 4 | - Flutter (1.0.0) 5 | - path_provider_foundation (0.0.1): 6 | - Flutter 7 | - FlutterMacOS 8 | - shared_preferences_foundation (0.0.1): 9 | - Flutter 10 | - FlutterMacOS 11 | - sqlite3 (3.49.1): 12 | - sqlite3/common (= 3.49.1) 13 | - sqlite3/common (3.49.1) 14 | - sqlite3/dbstatvtab (3.49.1): 15 | - sqlite3/common 16 | - sqlite3/fts5 (3.49.1): 17 | - sqlite3/common 18 | - sqlite3/math (3.49.1): 19 | - sqlite3/common 20 | - sqlite3/perf-threadsafe (3.49.1): 21 | - sqlite3/common 22 | - sqlite3/rtree (3.49.1): 23 | - sqlite3/common 24 | - sqlite3_flutter_libs (0.0.1): 25 | - Flutter 26 | - FlutterMacOS 27 | - sqlite3 (~> 3.49.1) 28 | - sqlite3/dbstatvtab 29 | - sqlite3/fts5 30 | - sqlite3/math 31 | - sqlite3/perf-threadsafe 32 | - sqlite3/rtree 33 | 34 | DEPENDENCIES: 35 | - connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`) 36 | - Flutter (from `Flutter`) 37 | - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) 38 | - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) 39 | - sqlite3_flutter_libs (from `.symlinks/plugins/sqlite3_flutter_libs/darwin`) 40 | 41 | SPEC REPOS: 42 | trunk: 43 | - sqlite3 44 | 45 | EXTERNAL SOURCES: 46 | connectivity_plus: 47 | :path: ".symlinks/plugins/connectivity_plus/ios" 48 | Flutter: 49 | :path: Flutter 50 | path_provider_foundation: 51 | :path: ".symlinks/plugins/path_provider_foundation/darwin" 52 | shared_preferences_foundation: 53 | :path: ".symlinks/plugins/shared_preferences_foundation/darwin" 54 | sqlite3_flutter_libs: 55 | :path: ".symlinks/plugins/sqlite3_flutter_libs/darwin" 56 | 57 | SPEC CHECKSUMS: 58 | connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd 59 | Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 60 | path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 61 | shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 62 | sqlite3: fc1400008a9b3525f5914ed715a5d1af0b8f4983 63 | sqlite3_flutter_libs: f6acaa2172e6bb3e2e70c771661905080e8ebcf2 64 | 65 | PODFILE CHECKSUM: 4305caec6b40dde0ae97be1573c53de1882a07e5 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 | 64 | 66 | 72 | 73 | 74 | 75 | 81 | 83 | 89 | 90 | 91 | 92 | 94 | 95 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /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/Contents.json: -------------------------------------------------------------------------------- 1 | {"images":[{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@3x.png","scale":"3x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@3x.png","scale":"3x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@3x.png","scale":"3x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@1x.png","scale":"1x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@3x.png","scale":"3x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@1x.png","scale":"1x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@1x.png","scale":"1x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@1x.png","scale":"1x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@2x.png","scale":"2x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@1x.png","scale":"1x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@2x.png","scale":"2x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@1x.png","scale":"1x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@2x.png","scale":"2x"},{"size":"83.5x83.5","idiom":"ipad","filename":"Icon-App-83.5x83.5@2x.png","scale":"2x"},{"size":"1024x1024","idiom":"ios-marketing","filename":"Icon-App-1024x1024@1x.png","scale":"1x"}],"info":{"version":1,"author":"xcode"}} -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HRET-Dev/AiClient/0dbc366dc49a2cdb4087975b18af9b04a6a90af5/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HRET-Dev/AiClient/0dbc366dc49a2cdb4087975b18af9b04a6a90af5/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HRET-Dev/AiClient/0dbc366dc49a2cdb4087975b18af9b04a6a90af5/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HRET-Dev/AiClient/0dbc366dc49a2cdb4087975b18af9b04a6a90af5/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HRET-Dev/AiClient/0dbc366dc49a2cdb4087975b18af9b04a6a90af5/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HRET-Dev/AiClient/0dbc366dc49a2cdb4087975b18af9b04a6a90af5/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HRET-Dev/AiClient/0dbc366dc49a2cdb4087975b18af9b04a6a90af5/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HRET-Dev/AiClient/0dbc366dc49a2cdb4087975b18af9b04a6a90af5/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HRET-Dev/AiClient/0dbc366dc49a2cdb4087975b18af9b04a6a90af5/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HRET-Dev/AiClient/0dbc366dc49a2cdb4087975b18af9b04a6a90af5/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HRET-Dev/AiClient/0dbc366dc49a2cdb4087975b18af9b04a6a90af5/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HRET-Dev/AiClient/0dbc366dc49a2cdb4087975b18af9b04a6a90af5/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HRET-Dev/AiClient/0dbc366dc49a2cdb4087975b18af9b04a6a90af5/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HRET-Dev/AiClient/0dbc366dc49a2cdb4087975b18af9b04a6a90af5/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HRET-Dev/AiClient/0dbc366dc49a2cdb4087975b18af9b04a6a90af5/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HRET-Dev/AiClient/0dbc366dc49a2cdb4087975b18af9b04a6a90af5/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HRET-Dev/AiClient/0dbc366dc49a2cdb4087975b18af9b04a6a90af5/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HRET-Dev/AiClient/0dbc366dc49a2cdb4087975b18af9b04a6a90af5/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HRET-Dev/AiClient/0dbc366dc49a2cdb4087975b18af9b04a6a90af5/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HRET-Dev/AiClient/0dbc366dc49a2cdb4087975b18af9b04a6a90af5/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HRET-Dev/AiClient/0dbc366dc49a2cdb4087975b18af9b04a6a90af5/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HRET-Dev/AiClient/0dbc366dc49a2cdb4087975b18af9b04a6a90af5/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HRET-Dev/AiClient/0dbc366dc49a2cdb4087975b18af9b04a6a90af5/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HRET-Dev/AiClient/0dbc366dc49a2cdb4087975b18af9b04a6a90af5/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 | Ai Client 11 | CFBundleExecutable 12 | $(EXECUTABLE_NAME) 13 | CFBundleIdentifier 14 | $(PRODUCT_BUNDLE_IDENTIFIER) 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | CFBundleName 18 | AiClient 19 | CFBundlePackageType 20 | APPL 21 | CFBundleShortVersionString 22 | $(FLUTTER_BUILD_NAME) 23 | CFBundleSignature 24 | ???? 25 | CFBundleVersion 26 | $(FLUTTER_BUILD_NUMBER) 27 | LSRequiresIPhoneOS 28 | 29 | NSAppTransportSecurity 30 | 31 | NSAllowsArbitraryLoads 32 | 33 | 34 | UIApplicationSupportsIndirectInputEvents 35 | 36 | UILaunchStoryboardName 37 | LaunchScreen 38 | UIMainStoryboardFile 39 | Main 40 | UISupportedInterfaceOrientations 41 | 42 | UIInterfaceOrientationPortrait 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | UISupportedInterfaceOrientations~ipad 47 | 48 | UIInterfaceOrientationPortrait 49 | UIInterfaceOrientationPortraitUpsideDown 50 | UIInterfaceOrientationLandscapeLeft 51 | UIInterfaceOrientationLandscapeRight 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /ios/RunnerTests/RunnerTests.swift: -------------------------------------------------------------------------------- 1 | import Flutter 2 | import UIKit 3 | import XCTest 4 | 5 | class RunnerTests: XCTestCase { 6 | 7 | func testExample() { 8 | // If you add code to the Runner application, consider adding tests here. 9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest. 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /ios_build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | NAME=$1 3 | if [ "${NAME}" == "" ] 4 | then 5 | NAME="AiClient-ios-unsigned" 6 | fi 7 | 8 | # 获取版本号 9 | VERSION=$(grep 'version:' pubspec.yaml | sed 's/version: //') 10 | # 移除版本号中的+号及之后的内容(如果有) 11 | VERSION=$(echo $VERSION | sed 's/+.*//') 12 | 13 | echo "版本号: $VERSION" 14 | echo '正在使用 Flutter 进行构建' 15 | 16 | ######### Sign ######### 17 | # flutter build ios 18 | 19 | ######### No sign ######### 20 | flutter build ios --no-codesign 21 | 22 | ######### build ######### 23 | mkdir -p Payload 24 | mkdir -p dist 25 | mv ./build/ios/iphoneos/Runner.app Payload 26 | zip -r -y Payload.zip Payload/Runner.app 27 | mv Payload.zip ./dist/${VERSION}/${NAME}-v${VERSION}.ipa 28 | rm -Rf Payload -------------------------------------------------------------------------------- /lib/common/theme.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class AppThemes { 4 | // 获取亮主题 5 | static ThemeData get lightTheme { 6 | return ThemeData( 7 | useMaterial3: true, 8 | colorScheme: ColorScheme.fromSeed( 9 | seedColor: const Color.fromARGB(255, 171, 194, 235))); 10 | } 11 | 12 | // 获取暗主题 13 | static ThemeData get darkTheme { 14 | return ThemeData( 15 | useMaterial3: true, 16 | colorScheme: ColorScheme.fromSeed( 17 | seedColor: const Color.fromARGB(255, 17, 95, 230), 18 | brightness: Brightness.dark)); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/common/toast.dart: -------------------------------------------------------------------------------- 1 | import 'package:bot_toast/bot_toast.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | /// Toast工具类 5 | class Toast { 6 | // 工具类无需实例化 设置构造私有化 7 | Toast._(); 8 | 9 | /// 显示一个标准的自定义 Toast 提示. 10 | /// 11 | /// 该方法允许通过参数自定义 Toast 的内容、样式和行为. 12 | /// 13 | /// 返回一个 [CancelFunc] 函数,调用此函数可以手动提前关闭该 Toast. 14 | /// 15 | /// 参数说明: 16 | /// 17 | /// - `message`:需要在 Toast 中显示的文本内容。 18 | /// - `crossPage`:指示 Toast 是否可以跨越多个页面(路由)显示,默认为 `false`. 19 | /// - `align`:Toast 在屏幕上的对齐方式,默认为 `Alignment(0.0, -0.8)`,即屏幕顶部中央. 20 | /// - `duration`:Toast 显示的持续时间,默认为 `Duration(seconds: 3)`. 21 | /// - `onClose`:当 Toast 关闭时执行的回调函数. 22 | /// - `icon`:在消息文本前显示的图标(可选). 23 | /// - `textColor`:消息文本的颜色(可选). 24 | /// - `defaultIconColor`:图标的默认颜色(可选,仅当提供了 `icon` 时生效). 25 | /// - `iconBackgroundColor`:图标背景的颜色(可选,仅当提供了 `icon` 时生效). 26 | static CancelFunc showToast( 27 | String message, { 28 | bool? crossPage, 29 | Alignment? align, 30 | Duration? duration, 31 | VoidCallback? onClose, 32 | Icon? icon, 33 | Color? textColor, 34 | Color? defaultIconColor, 35 | Color? iconBackgroundColor, 36 | }) { 37 | return BotToast.showCustomText( 38 | // 是否跨页面显示 39 | crossPage: crossPage ?? false, 40 | // 是否只显示一个 41 | onlyOne: true, 42 | // 显示位置 43 | align: align ?? const Alignment(0.0, -0.8), 44 | // 显示时长 45 | duration: duration ?? const Duration(seconds: 3), 46 | // 关闭回调 47 | onClose: onClose, 48 | // Toast样式内容 49 | toastBuilder: (cancel) { 50 | final List children = []; 51 | 52 | // 添加图标 53 | if (icon != null) { 54 | children.add(Container( 55 | padding: const EdgeInsets.all(2), 56 | decoration: BoxDecoration( 57 | color: iconBackgroundColor ?? Colors.black12, 58 | borderRadius: BorderRadius.circular(20), 59 | ), 60 | child: IconTheme( 61 | data: IconThemeData(size: 16, color: defaultIconColor), 62 | child: icon), 63 | )); 64 | } 65 | 66 | // 添加消息内容 67 | children.add(Flexible( 68 | child: Padding( 69 | padding: EdgeInsets.symmetric( 70 | horizontal: 10, 71 | vertical: icon == null ? 5 : 0, 72 | ), 73 | child: Text( 74 | message, 75 | style: TextStyle( 76 | fontSize: 14, 77 | color: textColor, 78 | ), 79 | strutStyle: const StrutStyle(leading: 0, forceStrutHeight: true), 80 | ), 81 | ), 82 | )); 83 | 84 | return Container( 85 | padding: const EdgeInsets.all(5), 86 | margin: const EdgeInsets.only(left: 10, right: 10), 87 | decoration: BoxDecoration( 88 | color: Colors.white, 89 | borderRadius: BorderRadius.circular(20), 90 | boxShadow: [ 91 | BoxShadow( 92 | color: Colors.black12, 93 | spreadRadius: 1, 94 | blurRadius: 3, 95 | offset: const Offset(0, 3), 96 | ), 97 | ], 98 | ), 99 | child: Row( 100 | mainAxisSize: MainAxisSize.min, // 宽度刚好包裹内容 101 | mainAxisAlignment: MainAxisAlignment.center, // 横向居中(视觉对称) 102 | crossAxisAlignment: CrossAxisAlignment.center, // 垂直方向对齐 103 | children: children, 104 | ), 105 | ); 106 | }, 107 | ); 108 | } 109 | 110 | /// 成功类消息 111 | /// 112 | /// 参数: 113 | /// - `message`: 需要在 Toast 中显示的文本内容. 114 | /// - `crossPage`: 指示 Toast 是否可以跨越多个页面(路由)显示. 115 | /// - `align`: Toast 在屏幕上的对齐方式. 116 | /// - `duration`: Toast 显示的持续时间. 117 | /// - `onClose`: 当 Toast 关闭时执行的回调函数. 118 | static CancelFunc showSuccess( 119 | String message, { 120 | bool? crossPage, 121 | Alignment? align, 122 | Duration? duration, 123 | VoidCallback? onClose, 124 | }) { 125 | return showToast( 126 | message, 127 | crossPage: crossPage, 128 | align: align, 129 | duration: duration, 130 | onClose: onClose, 131 | icon: const Icon(Icons.check_circle_outline), 132 | defaultIconColor: const Color(0xFF4CAF50), // Green 133 | iconBackgroundColor: const Color(0xFFE8F5E9), // Light Green 134 | textColor: Colors.black87, 135 | ); 136 | } 137 | 138 | /// 提示类消息 139 | /// 140 | /// 参数与 [showSuccess] 类似. 141 | static CancelFunc showInfo( 142 | String message, { 143 | bool? crossPage, 144 | Alignment? align, 145 | Duration? duration, 146 | VoidCallback? onClose, 147 | }) { 148 | return showToast( 149 | message, 150 | crossPage: crossPage, 151 | align: align, 152 | duration: duration, 153 | onClose: onClose, 154 | icon: const Icon(Icons.info_outline), 155 | defaultIconColor: const Color(0xFF2196F3), // Blue 156 | iconBackgroundColor: const Color(0xFFE3F2FD), // Light Blue 157 | textColor: Colors.black87, 158 | ); 159 | } 160 | 161 | /// 警告类消息 162 | /// 163 | /// 参数与 [showSuccess] 类似. 164 | static CancelFunc showWarning( 165 | String message, { 166 | bool? crossPage, 167 | Alignment? align, 168 | Duration? duration, 169 | VoidCallback? onClose, 170 | }) { 171 | return showToast( 172 | message, 173 | crossPage: crossPage, 174 | align: align, 175 | duration: duration, 176 | onClose: onClose, 177 | icon: const Icon(Icons.warning_amber_outlined), 178 | defaultIconColor: const Color(0xFFFF9800), // Orange 179 | iconBackgroundColor: const Color(0xFFFFF3E0), // Light Orange 180 | textColor: Colors.black87, 181 | ); 182 | } 183 | 184 | /// 错误类消息 185 | /// 186 | /// 参数与 [showSuccess] 类似. 187 | static CancelFunc showError( 188 | String message, { 189 | bool? crossPage, 190 | Alignment? align, 191 | Duration? duration, 192 | VoidCallback? onClose, 193 | }) { 194 | return showToast( 195 | message, 196 | crossPage: crossPage, 197 | align: align, 198 | duration: duration, 199 | onClose: onClose, 200 | icon: const Icon(Icons.error_outline), 201 | defaultIconColor: const Color(0xFFF44336), // Red 202 | iconBackgroundColor: const Color(0xFFFFEBEE), // Light Red 203 | textColor: Colors.black87, 204 | ); 205 | } 206 | 207 | /// 加载中消息 208 | static CancelFunc showLoading() { 209 | return BotToast.showCustomLoading( 210 | toastBuilder: (cancelFunc) { 211 | return Container( 212 | child: Text("data"), 213 | ); 214 | }, 215 | ); 216 | } 217 | 218 | /// 横幅消息 219 | static void showNotification() {} 220 | } 221 | -------------------------------------------------------------------------------- /lib/common/utils/loading_indicator.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | /// 加载指示器工具类 4 | class LoadingIndicator { 5 | /// 构建加载指示器 6 | static Widget buildLoadingIndicator(BuildContext context) { 7 | return Center( 8 | child: SizedBox( 9 | // 设置容器高度为可用高度(屏幕高度减去 AppBar 高度) 10 | height: 11 | MediaQuery.of(context).size.height - AppBar().preferredSize.height, 12 | child: Column( 13 | mainAxisAlignment: MainAxisAlignment.center, // 垂直居中 14 | children: [ 15 | Column( 16 | mainAxisSize: MainAxisSize.min, 17 | children: [ 18 | CircularProgressIndicator( 19 | color: Theme.of(context).colorScheme.primary, 20 | ), 21 | const SizedBox(height: 16), 22 | Text( 23 | '加载中…', 24 | style: TextStyle( 25 | color: Theme.of(context).colorScheme.onSurface, 26 | fontSize: 14, 27 | ), 28 | ), 29 | ], 30 | ), 31 | ], 32 | ), 33 | ), 34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/database/app_database.dart: -------------------------------------------------------------------------------- 1 | // 导入AI API实体类 2 | import 'package:ai_client/database/database_connection.dart'; 3 | import 'package:ai_client/models/ai_api.dart'; 4 | import 'package:ai_client/models/chat_message.dart'; 5 | import 'package:ai_client/models/chat_session.dart'; 6 | import 'package:ai_client/models/file_attachment.dart'; 7 | // 导入drift数据库依赖 8 | import 'package:drift/drift.dart'; 9 | 10 | // 引入生成的数据库代码 11 | part 'app_database.g.dart'; 12 | 13 | /// 数据库名称 14 | const _dbName = 'ai_client.db'; 15 | 16 | // 使用DriftDatabase注解定义数据库类 17 | @DriftDatabase(tables: [AiApi, ChatSessions, ChatMessages, FileAttachments]) 18 | class AppDatabase extends _$AppDatabase { 19 | // 构造函数,使用平台特定的连接方法 20 | AppDatabase() : super(openConnection(_dbName)); 21 | 22 | // 重写获取数据库版本号方法 23 | @override 24 | int get schemaVersion => 1; 25 | 26 | //数据库迁移方法 27 | @override 28 | MigrationStrategy get migration { 29 | return MigrationStrategy( 30 | beforeOpen: (details) async {}, 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /lib/database/database_connection.dart: -------------------------------------------------------------------------------- 1 | import 'package:drift/drift.dart'; 2 | 3 | // 使用条件导入,根据平台自动选择正确的实现 4 | import 'database_connection_web.dart' 5 | if (dart.library.io) 'database_connection_native.dart'; 6 | 7 | // 根据平台自动调用对应的实现 8 | QueryExecutor openConnection(String dbName) { 9 | return createDriftDatabase(dbName); 10 | } 11 | -------------------------------------------------------------------------------- /lib/database/database_connection_native.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'package:drift/drift.dart'; 3 | import 'package:drift/native.dart'; 4 | import 'package:path_provider/path_provider.dart'; 5 | import 'package:path/path.dart' as p; 6 | import 'package:logger/logger.dart'; 7 | 8 | // 原生平台的数据库连接实现 9 | QueryExecutor createDriftDatabase(String dbName) { 10 | return LazyDatabase(() async { 11 | // 根据不同平台获取适合存放数据库的目录路径 12 | final Directory path; 13 | if (Platform.isAndroid) { 14 | path = await getApplicationDocumentsDirectory(); 15 | } else if (Platform.isIOS || Platform.isMacOS) { 16 | path = await getApplicationDocumentsDirectory(); 17 | } else if (Platform.isWindows || Platform.isLinux) { 18 | path = await getApplicationSupportDirectory(); 19 | } else { 20 | // 其他平台的默认处理 21 | path = await getApplicationSupportDirectory(); 22 | } 23 | 24 | // 数据库文件 25 | final dbFile = p.join(path.path, 'databases', dbName); 26 | 27 | Logger().d(dbFile); 28 | return NativeDatabase(File(dbFile)); 29 | }); 30 | } -------------------------------------------------------------------------------- /lib/database/database_connection_web.dart: -------------------------------------------------------------------------------- 1 | import 'package:drift/drift.dart'; 2 | import 'package:drift_flutter/drift_flutter.dart'; 3 | 4 | // Web 平台的数据库连接实现 5 | QueryExecutor createDriftDatabase(String dbName) { 6 | /// web端 使用 wasm 方式打开数据库 7 | return driftDatabase( 8 | name: dbName, 9 | web: DriftWebOptions( 10 | sqlite3Wasm: Uri.parse('sqlite3.wasm'), 11 | driftWorker: Uri.parse('drift_worker.dart.js'), 12 | ), 13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /lib/generated/locale_keys.dart: -------------------------------------------------------------------------------- 1 | /// 本地化多语言静态常量 2 | abstract class LocaleKeys { 3 | //=================================================== 4 | // 通用键 (Common Keys) 5 | //=================================================== 6 | 7 | /// 聊天 8 | static const chat = 'Chat'; 9 | 10 | /// 历史 11 | static const history = 'History'; 12 | 13 | /// 设置 14 | static const settings = 'Settings'; 15 | 16 | /// 保存 17 | static const save = 'save'; 18 | 19 | /// 取消 20 | static const cancel = 'cancel'; 21 | 22 | /// 清除 23 | static const clear = 'clear'; 24 | 25 | /// 删除 26 | static const delete = 'delete'; 27 | 28 | /// 确认 29 | static const confirm = 'confirm'; 30 | 31 | /// 确认删除 32 | static const confirmDelete = 'confirm_delete'; 33 | 34 | /// 确认删除提示信息 35 | static const confirmDeleteMessage = 'confirm_delete_message'; 36 | 37 | /// 分享 38 | static const share = 'share'; 39 | 40 | /// 已分享 41 | static const shared = 'shared'; 42 | 43 | /// 导入 44 | static const import = 'import'; 45 | 46 | /// 剪贴板为空 47 | static const clipboardEmpty = 'clipboard_empty'; 48 | 49 | /// 该功能正在开发中 50 | static const thisFeatureIsUnderDevelopment = 51 | 'this_feature_is_under_development'; 52 | 53 | /// 请选择 54 | static const pleaseSelect = 'please_select'; 55 | 56 | /// 复制 57 | static const copy = 'copy'; 58 | 59 | /// 复制完成 60 | static const copySuccess = 'copy_success'; 61 | 62 | //=================================================== 63 | // 侧边栏 (Sidebar) 64 | //=================================================== 65 | 66 | /// 侧边栏 - 显示侧边栏 67 | static const sidebarShow = 'sidebar.show'; 68 | 69 | /// 侧边栏 - 隐藏侧边栏 70 | static const sidebarHide = 'sidebar.hide'; 71 | 72 | /// 侧边栏 - 应用名称 73 | static const sidebarAppName = 'sidebar.app_name'; 74 | 75 | /// 侧边栏 - 所有对话列表 76 | static const sidebarAllChats ='sidebar.all_chats'; 77 | 78 | //=================================================== 79 | // 聊天页面 (Chat Page) 80 | //=================================================== 81 | 82 | /// 聊天页 - 新对话按钮提示值 83 | static const chatPageNewChat = 'chat_page.new_chat'; 84 | 85 | /// 聊天页 - 聊天回复等待提示值 86 | static const chatPageThinking = 'chat_page.thinking'; 87 | 88 | /// 聊天页 - 底部输入框提示值 89 | static const chatPageInputHintText = 'chat_page.input_hintText'; 90 | 91 | //=================================================== 92 | // 设置页面 (Settings Page) 93 | //=================================================== 94 | 95 | //--------------------------------------------------- 96 | // API 设置 (API Settings) 97 | //--------------------------------------------------- 98 | 99 | /// 设置页 - API管理按钮提示值 100 | static const settingPageApiSettingApiManger = 101 | 'settings_page.api_setting.api_manager'; 102 | 103 | /// 设置页 - 添加API按钮提示值 104 | static const addApi = 'settings_page.api_setting.add_api'; 105 | 106 | /// 设置页 - 编辑API按钮提示值 107 | static const editApi = 'settings_page.api_setting.edit_api'; 108 | 109 | /// 设置页 - 删除API按钮提示值 110 | static const settingPageApiSettingDeleteApi = 111 | 'settings_page.api_setting.delete_api'; 112 | 113 | /// 设置页 - 添加API成功提示信息 114 | static const settingPageApiSettingAddSuccess = 115 | 'settings_page.api_setting.add_success'; 116 | 117 | /// 设置页 - 添加API失败提示信息 118 | static const settingPageApiSettingAddFailed = 119 | 'settings_page.api_setting.add_failed'; 120 | 121 | /// API配置已复制到剪贴板 122 | static const apiConfigCopiedToClipboard = 123 | 'settings_page.api_setting.api_config_copied_to_clipboard'; 124 | 125 | /// 无效的API配置字符串 126 | static const invalidApiConfigString = 127 | 'settings_page.api_setting.invalid_api_config_string'; 128 | 129 | /// 解密API配置失败 130 | static const failedToDecryptApiConfig = 131 | 'settings_page.api_setting.failed_to_decrypt_api_config'; 132 | 133 | /// API配置导入成功 134 | static const apiConfigImported = 135 | 'settings_page.api_setting.api_config_imported'; 136 | 137 | //--------------------------------------------------- 138 | // 语言设置 (Language Settings) 139 | //--------------------------------------------------- 140 | 141 | /// 设置页 - 语言按钮提示值 142 | static const settingPageLanguageSettingLanguageButtonText = 143 | 'settings_page.language_setting.language_button_text'; 144 | 145 | /// 设置页 - 选择语言按钮提示值 146 | static const settingPageLanguageSettingSelectLanguage = 147 | 'settings_page.language_setting.select_language'; 148 | 149 | /// 设置页 - 语言信息列表 150 | static const settingPageLanguageSettingLanguageList = 151 | 'settings_page.language_setting.language_list'; 152 | 153 | //=================================================== 154 | // API 模型 (AI API Model) 155 | //=================================================== 156 | 157 | /// AI API 模型 158 | static const aiApiModel = 'ai_api_model'; 159 | 160 | /// API 配置 161 | static const aiApiModelConfig = '$aiApiModel.config'; 162 | 163 | /// API 名称 164 | static const aiApiModelApiName = '$aiApiModel.api_name'; 165 | 166 | /// API 名称校验失败 提示信息 167 | static const aiApiModelApiNameValidateFailed = 168 | '$aiApiModel.api_name_validate_failed'; 169 | 170 | /// API 服务商 171 | static const aiApiModelApiProvider = '$aiApiModel.api_provider'; 172 | 173 | /// API 服务类型 174 | static const aiApiModelApiType = '$aiApiModel.api_type'; 175 | 176 | /// API 地址 177 | static const aiApiModelApiBaseUrl = '$aiApiModel.api_base_url'; 178 | 179 | /// API 地址校验失败 提示信息 180 | static const aiApiModelApiBaseUrlValidateFailed = 181 | '$aiApiModel.api_base_url_validate_failed'; 182 | 183 | /// API 密钥 184 | static const aiApiModelApiKey = '$aiApiModel.api_key'; 185 | 186 | /// 模型列表 187 | static const aiApiModelModels = '$aiApiModel.models'; 188 | 189 | /// 模型列表校验失败 提示信息 190 | static const aiApiModelModelsValidateFailed = 191 | '$aiApiModel.models_validate_failed'; 192 | } 193 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:ai_client/common/theme.dart'; 4 | import 'package:ai_client/pages/chat/chat_provider.dart'; 5 | import 'package:ai_client/pages/home_page.dart'; 6 | import 'package:bot_toast/bot_toast.dart'; 7 | import 'package:easy_localization/easy_localization.dart'; 8 | import 'package:flutter/foundation.dart'; 9 | import 'package:flutter/material.dart'; 10 | import 'package:provider/provider.dart'; 11 | import 'package:window_manager/window_manager.dart'; 12 | 13 | void main() async { 14 | // 初始化Easy_Localization库 15 | WidgetsFlutterBinding.ensureInitialized(); 16 | await EasyLocalization.ensureInitialized(); 17 | 18 | // 判断是否为 Web 平台 19 | if (!kIsWeb) { 20 | // 仅在桌面平台上初始化 window_manager 21 | if (Platform.isWindows || Platform.isLinux || Platform.isMacOS) { 22 | await windowManager.ensureInitialized(); 23 | 24 | // 窗口设置 25 | WindowOptions windowOptions = const WindowOptions( 26 | // 初始窗口大小 27 | size: Size(800, 630), 28 | // 最小窗口大小 29 | minimumSize: Size(330, 400), 30 | // 窗口居中显示 31 | center: true, 32 | backgroundColor: Colors.transparent, 33 | skipTaskbar: false, 34 | titleBarStyle: TitleBarStyle.normal, 35 | ); 36 | 37 | // 应用窗口设置 38 | await windowManager.waitUntilReadyToShow(windowOptions, () async { 39 | await windowManager.show(); 40 | await windowManager.focus(); 41 | }); 42 | } 43 | } 44 | 45 | runApp( 46 | EasyLocalization( 47 | supportedLocales: [ 48 | Locale('zh', 'CN'), 49 | Locale('zh', 'TW'), 50 | Locale('en', 'US'), 51 | Locale('ja', 'JP'), 52 | Locale('ko', 'KR'), 53 | ], 54 | path: 'assets/translations', 55 | fallbackLocale: Locale('zh', 'CN'), 56 | child: App()), 57 | ); 58 | } 59 | 60 | class App extends StatelessWidget { 61 | const App({super.key}); 62 | 63 | @override 64 | Widget build(BuildContext context) { 65 | return MultiProvider( 66 | providers: [ 67 | ChangeNotifierProvider(create: (_) => HomePageProvider()), 68 | ChangeNotifierProvider(create: (_) => ChatProvider()), 69 | ], 70 | child: MaterialApp( 71 | // 初始化 BotToast, 用于全局提示 72 | builder: BotToastInit(), 73 | // 路由监听器 74 | navigatorObservers: [ 75 | // BotToast路由观察者 76 | BotToastNavigatorObserver(), 77 | ], 78 | 79 | // 多语言 配置Easy_Localization 80 | localizationsDelegates: context.localizationDelegates, 81 | supportedLocales: context.supportedLocales, 82 | locale: context.locale, 83 | 84 | // 主题 85 | // 明亮主题 86 | theme: AppThemes.lightTheme, 87 | // 黑暗主题 88 | darkTheme: AppThemes.darkTheme, 89 | // 主题模式 90 | themeMode: ThemeMode.system, 91 | home: HomePage(), 92 | ), 93 | ); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /lib/models/ai_api.dart: -------------------------------------------------------------------------------- 1 | import 'package:drift/drift.dart'; 2 | 3 | /// AiApi 配置表 4 | class AiApi extends Table { 5 | /// 主键ID 6 | late final id = integer().autoIncrement()(); 7 | 8 | /// 服务名称 9 | late final serviceName = text()(); 10 | 11 | /// 服务提供商 12 | late final provider = text()(); 13 | 14 | /// 基础URL地址 15 | late final baseUrl = text()(); 16 | 17 | /// API密钥 18 | late final apiKey = text()(); 19 | 20 | /// 模型列表 (多个模型以逗号分隔) 21 | late final models = text()(); 22 | 23 | /// 是否激活 (默认激活) 24 | late final isActive = boolean().withDefault(const Constant(true))(); 25 | 26 | /// 创建时间 27 | late final createdTime = dateTime().withDefault(currentDateAndTime)(); 28 | 29 | /// 更新时间 30 | late final updatedTime = dateTime().withDefault(currentDateAndTime)(); 31 | } 32 | -------------------------------------------------------------------------------- /lib/models/chat_message.dart: -------------------------------------------------------------------------------- 1 | import 'package:ai_client/models/chat_session.dart'; 2 | import 'package:drift/drift.dart'; 3 | 4 | /// 消息类型枚举 5 | enum MessageType { 6 | /// 文本 7 | text, 8 | 9 | /// 文件 10 | file, 11 | 12 | /// 默认 13 | system 14 | } 15 | 16 | /// 消息角色枚举 17 | enum MessageRole { 18 | /// 用户 19 | user, 20 | 21 | /// 助手 22 | assistant, 23 | 24 | /// 系统 25 | system 26 | } 27 | 28 | /// 消息状态枚举 29 | enum MessageStatus { 30 | /// 发送中 31 | send, 32 | 33 | /// 已发送 34 | sent, 35 | 36 | /// 发送失败 37 | error 38 | } 39 | 40 | /// 消息状态转换 41 | MessageStatus convertStatus(String status) { 42 | switch (status) { 43 | case 'send': 44 | return MessageStatus.send; 45 | case 'sent': 46 | return MessageStatus.sent; 47 | default: 48 | return MessageStatus.error; 49 | } 50 | } 51 | 52 | class ChatMessages extends Table { 53 | // 主键ID 54 | IntColumn get id => integer().autoIncrement()(); 55 | 56 | // 关联的会话ID 57 | IntColumn get sessionId => integer().references(ChatSessions, #id)(); 58 | 59 | // 父级消息ID 60 | IntColumn get parentId => integer().nullable()(); 61 | 62 | // 消息内容 63 | TextColumn get content => text()(); 64 | 65 | // 会话使用的API配置ID 66 | IntColumn get apiConfigId => integer().nullable()(); 67 | 68 | // 会话使用的模型 69 | TextColumn get model => text().nullable()(); 70 | 71 | // 消息类型 72 | TextColumn get type => textEnum()(); 73 | 74 | // 消息角色 75 | TextColumn get role => textEnum()(); 76 | 77 | // 消息创建时间 78 | DateTimeColumn get createdTime => 79 | dateTime().withDefault(Constant(DateTime.now()))(); 80 | 81 | // 如果是文件类型,存储文件路径 82 | TextColumn get filePath => text().nullable()(); 83 | 84 | // 消息状态(发送中、已发送、发送失败等) 85 | TextColumn get status => textEnum()(); 86 | } 87 | -------------------------------------------------------------------------------- /lib/models/chat_session.dart: -------------------------------------------------------------------------------- 1 | import 'package:drift/drift.dart'; 2 | 3 | class ChatSessions extends Table { 4 | // 主键ID 5 | IntColumn get id => integer().autoIncrement()(); 6 | 7 | // 会话标题 8 | TextColumn get title => text()(); 9 | 10 | // 会话使用的API配置ID 11 | IntColumn get apiConfigId => integer().nullable()(); 12 | 13 | // 会话使用的模型 14 | TextColumn get model => text().nullable()(); 15 | 16 | // 会话创建时间 17 | DateTimeColumn get createdTime => dateTime().withDefault(Constant(DateTime.now()))(); 18 | 19 | // 会话最后更新时间 20 | DateTimeColumn get updatedTime => dateTime().withDefault(Constant(DateTime.now()))(); 21 | 22 | // 是否已收藏 23 | BoolColumn get isFavorite => boolean().withDefault(const Constant(false))(); 24 | } -------------------------------------------------------------------------------- /lib/models/file_attachment.dart: -------------------------------------------------------------------------------- 1 | import 'package:ai_client/models/chat_message.dart'; 2 | import 'package:drift/drift.dart'; 3 | 4 | class FileAttachments extends Table { 5 | // 主键ID 6 | IntColumn get id => integer().autoIncrement()(); 7 | 8 | // 关联的消息ID 9 | IntColumn get messageId => integer().references(ChatMessages, #id)(); 10 | 11 | // 文件名 12 | TextColumn get fileName => text()(); 13 | 14 | // 文件路径 15 | TextColumn get filePath => text()(); 16 | 17 | // 文件大小(字节) 18 | IntColumn get fileSize => integer()(); 19 | 20 | // 文件类型 21 | TextColumn get fileType => text()(); 22 | 23 | // 上传时间 24 | DateTimeColumn get uploadedAt => dateTime().withDefault(Constant(DateTime.now()))(); 25 | } -------------------------------------------------------------------------------- /lib/pages/chat/input.dart: -------------------------------------------------------------------------------- 1 | import 'package:ai_client/generated/locale_keys.dart'; 2 | import 'package:easy_localization/easy_localization.dart'; 3 | import 'package:flutter/material.dart'; 4 | 5 | class InputWidget extends StatefulWidget { 6 | final TextEditingController messageController; 7 | final bool isWaitingResponse; 8 | final Function() onSendMessage; 9 | 10 | const InputWidget({ 11 | super.key, 12 | required this.messageController, 13 | required this.isWaitingResponse, 14 | required this.onSendMessage, 15 | }); 16 | 17 | @override 18 | State createState() => InputState(); 19 | } 20 | 21 | class InputState extends State { 22 | 23 | /// 构建输入框 24 | Widget _buildInput() { 25 | // 获取主题信息 26 | final theme = Theme.of(context); 27 | 28 | return Padding( 29 | padding: const EdgeInsets.all(8.0), 30 | child: Row( 31 | crossAxisAlignment: CrossAxisAlignment.end, 32 | children: [ 33 | // 输入框 34 | Expanded( 35 | child: Container( 36 | decoration: BoxDecoration( 37 | color: theme.scaffoldBackgroundColor, 38 | borderRadius: BorderRadius.circular(15), 39 | border: Border.all( 40 | color: theme.focusColor, 41 | width: 1, 42 | ), 43 | boxShadow: [ 44 | BoxShadow( 45 | color: Colors.black.withValues(alpha: 0.05), 46 | blurRadius: 4, 47 | offset: Offset(0, 2), 48 | ), 49 | ], 50 | ), 51 | child: TextField( 52 | controller: widget.messageController, 53 | maxLines: null, 54 | minLines: 1, 55 | onChanged: (value) => setState(() {}), 56 | onSubmitted: (value) => widget.onSendMessage(), 57 | decoration: InputDecoration( 58 | border: OutlineInputBorder( 59 | borderRadius: BorderRadius.circular(15), 60 | borderSide: BorderSide.none, 61 | ), 62 | filled: true, 63 | fillColor: theme.hoverColor, 64 | hintText: tr(LocaleKeys.chatPageInputHintText), 65 | ), 66 | ), 67 | ), 68 | ), 69 | // 发送按钮 70 | Container( 71 | margin: EdgeInsets.only(left: 6, bottom: 4), 72 | width: 40, 73 | height: 40, 74 | decoration: BoxDecoration( 75 | color: widget.messageController.text.isEmpty || 76 | widget.isWaitingResponse 77 | ? theme.disabledColor 78 | : theme.buttonTheme.colorScheme?.onPrimaryFixedVariant, 79 | shape: BoxShape.circle, 80 | boxShadow: [ 81 | BoxShadow( 82 | color: Colors.black.withValues(alpha: 0.1), 83 | blurRadius: 4, 84 | offset: Offset(0, 2), 85 | ), 86 | ], 87 | ), 88 | child: IconButton( 89 | onPressed: widget.messageController.text.isEmpty || 90 | widget.isWaitingResponse 91 | ? null 92 | : () => widget.onSendMessage(), 93 | icon: Icon( 94 | Icons.send_rounded, 95 | size: 20, 96 | color: theme.colorScheme.onPrimary, 97 | ), 98 | padding: EdgeInsets.zero, 99 | constraints: BoxConstraints(), 100 | ), 101 | ), 102 | ], 103 | ), 104 | ); 105 | } 106 | 107 | @override 108 | Widget build(BuildContext context) { 109 | return _buildInput(); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /lib/pages/chat/message_action_buttons.dart: -------------------------------------------------------------------------------- 1 | import 'package:ai_client/models/chat_message.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:easy_localization/easy_localization.dart'; 4 | import 'package:ai_client/generated/locale_keys.dart'; 5 | 6 | /// 消息操作按钮组件 7 | /// 用于在消息内容下方显示操作按钮 8 | class MessageActionButtons extends StatelessWidget { 9 | /// 消息类型 MessageRole 10 | final MessageRole messageRole; 11 | 12 | /// 删除消息回调函数 13 | final VoidCallback onDelete; 14 | 15 | /// 重新回答回调函数 16 | final VoidCallback onRegenerate; 17 | 18 | /// 按钮大小 19 | final double iconSize; 20 | 21 | /// 按钮间距 22 | final double spacing; 23 | 24 | /// 构造函数 25 | const MessageActionButtons({ 26 | super.key, 27 | required this.messageRole, 28 | required this.onDelete, 29 | required this.onRegenerate, 30 | this.iconSize = 20.0, 31 | this.spacing = 8.0, 32 | }); 33 | 34 | @override 35 | Widget build(BuildContext context) { 36 | // 获取主题 37 | final theme = Theme.of(context); 38 | 39 | return Padding( 40 | padding: const EdgeInsets.only(top: 8.0), 41 | child: Row( 42 | mainAxisSize: MainAxisSize.min, 43 | children: [ 44 | // 删除按钮 45 | _buildActionButton( 46 | context: context, 47 | icon: Icons.delete_sweep, 48 | tooltip: LocaleKeys.delete.tr(), 49 | onPressed: onDelete, 50 | color: theme.colorScheme.error, 51 | ), 52 | SizedBox(width: spacing), 53 | if (messageRole != MessageRole.user) 54 | // 重新回答按钮 55 | _buildActionButton( 56 | context: context, 57 | icon: Icons.refresh, 58 | tooltip: '重新回答', 59 | onPressed: onRegenerate, 60 | color: theme.colorScheme.primary, 61 | ), 62 | ], 63 | ), 64 | ); 65 | } 66 | 67 | /// 构建操作按钮 68 | Widget _buildActionButton({ 69 | required BuildContext context, 70 | required IconData icon, 71 | required String tooltip, 72 | required VoidCallback onPressed, 73 | required Color color, 74 | }) { 75 | return Tooltip( 76 | message: tooltip, 77 | child: InkWell( 78 | onTap: onPressed, 79 | borderRadius: BorderRadius.circular(20), 80 | child: Container( 81 | padding: const EdgeInsets.all(6.0), 82 | decoration: BoxDecoration( 83 | borderRadius: BorderRadius.circular(20), 84 | border: Border.all( 85 | color: color.withAlpha(1), 86 | width: 1.0, 87 | ), 88 | ), 89 | child: Icon( 90 | icon, 91 | size: iconSize, 92 | color: color, 93 | ), 94 | ), 95 | ), 96 | ); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /lib/pages/settings/apis/api_list.dart: -------------------------------------------------------------------------------- 1 | import 'dart:core'; 2 | 3 | import 'package:ai_client/common/utils/loading_indicator.dart'; 4 | import 'package:ai_client/database/app_database.dart'; 5 | import 'package:ai_client/pages/settings/apis/api_info.dart'; 6 | import 'package:ai_client/repositories/ai_api_repository.dart'; 7 | import 'package:ai_client/services/ai_api_service.dart'; 8 | import 'package:easy_localization/easy_localization.dart'; 9 | import 'package:flutter/material.dart'; 10 | 11 | /// API 列表 12 | class ApiList extends StatefulWidget { 13 | // 数据库实例 14 | final AppDatabase appDatabase; 15 | 16 | const ApiList({ 17 | super.key, 18 | required this.appDatabase, 19 | }); 20 | 21 | @override 22 | State createState() => _ApiListState(); 23 | } 24 | 25 | /// API 列表的状态管理 26 | class _ApiListState extends State { 27 | /// AiApi 服务类 28 | late AiApiService _aiApiService; 29 | 30 | /// API信息列表 31 | List _dataList = []; 32 | 33 | /// 是否显示加载中状态 34 | bool _isLoading = true; 35 | 36 | @override 37 | void initState() { 38 | super.initState(); 39 | 40 | // 初始化 AiApi 服务类 41 | _aiApiService = AiApiService( 42 | AiApiRepository( 43 | widget.appDatabase, 44 | ), 45 | ); 46 | // 初始化数据库和数据 47 | _initDatabaseAndData(); 48 | } 49 | 50 | @override 51 | void dispose() { 52 | super.dispose(); 53 | } 54 | 55 | /// 初始化数据库和数据 56 | Future _initDatabaseAndData() async { 57 | try { 58 | // 设置加载状态为 true 59 | setState(() => _isLoading = true); 60 | 61 | // 查询数据 62 | var data = await _aiApiService.initDefaultAiApiConfig(); 63 | 64 | if (mounted) { 65 | setState(() { 66 | _dataList = List.from(data); // 更新数据列表 67 | _isLoading = false; // 设置加载状态为 false 68 | }); 69 | } 70 | } catch (e) { 71 | if (mounted) { 72 | setState(() => _isLoading = false); // 发生错误时设置加载状态为 false 73 | } 74 | } 75 | } 76 | 77 | /// 渲染数据列表 78 | Widget _buildDataList() { 79 | // 主题配置 80 | final theme = Theme.of(context); 81 | 82 | return Padding( 83 | padding: const EdgeInsets.all(8.0), 84 | // 数据列表 85 | child: SizedBox( 86 | child: ListView.builder( 87 | shrinkWrap: true, 88 | physics: const AlwaysScrollableScrollPhysics(), 89 | itemCount: _dataList.length, 90 | itemBuilder: (context, index) { 91 | final item = _dataList[index]; 92 | return Dismissible( 93 | key: ValueKey(item.id), 94 | direction: DismissDirection.endToStart, 95 | onDismissed: (direction) { 96 | // 在安全的时机执行删除操作 97 | WidgetsBinding.instance.addPostFrameCallback((_) { 98 | // 获取数据ID 99 | var dataId = item.id; 100 | // 删除对应的数据 101 | _aiApiService.deleteAiApiById(dataId); 102 | // 使用 setState 安全地更新 UI 103 | setState(() { 104 | _dataList.removeAt(index); 105 | }); 106 | }); 107 | }, 108 | child: Card( 109 | color: theme.colorScheme.onSecondary, 110 | shadowColor: theme.colorScheme.secondary, 111 | elevation: 2.0, 112 | margin: const EdgeInsets.symmetric(vertical: 4.0), 113 | child: ListTile( 114 | title: Text(item.serviceName), 115 | subtitle: Text(DateFormat('yyyy-MM-dd HH:mm:ss') 116 | .format(item.updatedTime)), 117 | onTap: () async { 118 | // 通过弹窗打开 api 信息页面 119 | final result = await showDialog( 120 | context: context, 121 | builder: (context) => ApiInfo( 122 | aiApi: item, 123 | appDatabase: widget.appDatabase, 124 | ), 125 | ); 126 | 127 | // 如果编辑成功,刷新列表 128 | if (result == true) { 129 | // 重新查询数据 130 | var data = await _aiApiService.getAllAiApis(); 131 | setState(() { 132 | // 更新数据列表 133 | _dataList = List.from(data); 134 | }); 135 | } 136 | }, 137 | ), 138 | ), 139 | ); 140 | }, 141 | ), 142 | ), 143 | ); 144 | } 145 | 146 | @override 147 | Widget build(BuildContext context) { 148 | // 判断是否显示加载中 149 | if (_isLoading) { 150 | return LoadingIndicator.buildLoadingIndicator(context); // 显示加载指示器 151 | } 152 | 153 | // 渲染数据列表 154 | return _buildDataList(); 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /lib/pages/settings/apis/api_settings.dart: -------------------------------------------------------------------------------- 1 | import 'package:ai_client/database/app_database.dart'; 2 | import 'package:ai_client/generated/default_api_configs.dart'; 3 | import 'package:ai_client/generated/locale_keys.dart'; 4 | import 'package:ai_client/pages/settings/apis/api_info.dart'; 5 | import 'package:ai_client/pages/settings/apis/api_list.dart'; 6 | import 'package:easy_localization/easy_localization.dart'; 7 | import 'package:flutter/material.dart'; 8 | 9 | /// API设置 10 | class ApiSettings extends StatefulWidget { 11 | // API设置信息 12 | static Widget buildApiSettings(BuildContext context) { 13 | return ListTile( 14 | leading: const Icon(Icons.cloud), 15 | title: Text(tr(LocaleKeys.settingPageApiSettingApiManger)), 16 | onTap: () { 17 | // 点击事件,跳转到 API 列表页面 18 | Navigator.push( 19 | context, 20 | MaterialPageRoute(builder: (context) => ApiSettings()), 21 | ); 22 | }, 23 | ); 24 | } 25 | 26 | @override 27 | State createState() => _ApiSettings(); 28 | } 29 | 30 | class _ApiSettings extends State { 31 | // 获取 AppDatabase 实例 32 | final AppDatabase appDatabase = AppDatabase(); 33 | 34 | /// 添加按钮 35 | Widget _addButton() { 36 | return IconButton( 37 | icon: const Icon(Icons.add), 38 | tooltip: tr(LocaleKeys.addApi), 39 | onPressed: () async { 40 | // 从 DefaultApiConfigs 获取第一个支持的 API 提供商作为默认值 41 | final defaultProvider = 42 | DefaultApiConfigs.supportedApiProviders.isNotEmpty 43 | ? DefaultApiConfigs.supportedApiProviders.first 44 | : 'OpenAI'; // 提供一个后备默认值 45 | // 打开 AiApi 信息弹窗,创建一个新的空 AiApiData 对象 46 | final result = await showDialog( 47 | context: context, 48 | builder: (context) => ApiInfo( 49 | aiApi: AiApiData( 50 | id: 0, 51 | provider: defaultProvider, 52 | serviceName: '', 53 | baseUrl: '', 54 | apiKey: '', 55 | models: '', 56 | createdTime: DateTime.now(), 57 | isActive: true, 58 | updatedTime: DateTime.now(), 59 | ), 60 | appDatabase: appDatabase, 61 | ), 62 | ); 63 | 64 | // 如果保存成功,刷新列表 65 | if (result == true) { 66 | // 通过 key 刷新 ApiList 67 | setState(() {}); 68 | } 69 | }, 70 | ); 71 | } 72 | 73 | @override 74 | Widget build(BuildContext context) { 75 | return Scaffold( 76 | appBar: AppBar( 77 | backgroundColor: Theme.of(context).colorScheme.onInverseSurface, 78 | toolbarHeight: 48, 79 | title: Text( 80 | LocaleKeys.settingPageApiSettingApiManger, 81 | style: const TextStyle(fontSize: 18), 82 | ).tr(), 83 | actions: [ 84 | // 添加按钮 85 | _addButton(), 86 | ], 87 | ), 88 | body: SafeArea( 89 | child: Column( 90 | children: [ 91 | Expanded( 92 | child: ApiList(key: UniqueKey(), appDatabase: appDatabase), 93 | ), 94 | ], 95 | ), 96 | ), 97 | ); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /lib/pages/settings/language_settings.dart: -------------------------------------------------------------------------------- 1 | import 'package:ai_client/generated/locale_keys.dart'; 2 | import 'package:easy_localization/easy_localization.dart'; 3 | import 'package:flutter/material.dart'; 4 | 5 | /// 语言设置 6 | class LanguageSettings { 7 | // 语言设置信息 8 | static Widget buildLanguageSettings(BuildContext context) { 9 | return ListTile( 10 | leading: const Icon(Icons.language), 11 | title: Text(tr(LocaleKeys.settingPageLanguageSettingLanguageButtonText)), 12 | onTap: () { 13 | showDialog( 14 | context: context, 15 | builder: (context) { 16 | final currentIndex = 17 | context.supportedLocales.indexOf(context.locale); 18 | 19 | return AlertDialog( 20 | title: Text( 21 | tr(LocaleKeys.settingPageLanguageSettingLanguageButtonText)), 22 | contentPadding: 23 | const EdgeInsets.symmetric(horizontal: 0, vertical: 8), 24 | insetPadding: 25 | const EdgeInsets.symmetric(horizontal: 16, vertical: 24), 26 | content: StatefulBuilder( 27 | builder: (context, setState) { 28 | return Container( 29 | width: MediaQuery.of(context).size.width * 0.9, 30 | constraints: BoxConstraints( 31 | maxHeight: MediaQuery.of(context).size.height * 0.7, 32 | ), 33 | child: ListView.builder( 34 | shrinkWrap: true, 35 | physics: const ClampingScrollPhysics(), 36 | itemCount: context.supportedLocales.length, 37 | itemBuilder: (context, index) { 38 | var language = 39 | context.supportedLocales[index].toString(); 40 | var title = tr( 41 | '${LocaleKeys.settingPageLanguageSettingLanguageList}.$language'); 42 | 43 | return RadioListTile( 44 | title: Text(title), 45 | value: index, 46 | groupValue: currentIndex, 47 | onChanged: (value) { 48 | if (value != null) { 49 | // 关闭对话框 50 | Navigator.pop(context); 51 | // 设置语言 52 | context 53 | .setLocale(context.supportedLocales[value]); 54 | } 55 | }, 56 | ); 57 | }, 58 | ), 59 | ); 60 | }, 61 | ), 62 | ); 63 | }, 64 | ); 65 | }, 66 | ); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /lib/pages/settings/settings_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:ai_client/generated/locale_keys.dart'; 2 | import 'package:ai_client/pages/settings/apis/api_settings.dart'; 3 | import 'package:ai_client/pages/settings/language_settings.dart'; 4 | import 'package:easy_localization/easy_localization.dart'; 5 | import 'package:flutter/material.dart'; 6 | 7 | class SettingsPage extends StatefulWidget { 8 | /// 是否是从页面导航过来的 9 | final bool isFromNavigation; 10 | 11 | const SettingsPage({this.isFromNavigation = false}); 12 | 13 | @override 14 | State createState() => _SettingsPageState(); 15 | } 16 | 17 | class _SettingsPageState extends State { 18 | /// 设置菜单 19 | Widget _buildSettingsList(BuildContext context) { 20 | return ListView( 21 | children: [ 22 | // API 管理 23 | ApiSettings.buildApiSettings(context), 24 | // 分割线 25 | const Divider( 26 | height: 1, 27 | thickness: 1, 28 | ), 29 | // 语言设置 30 | LanguageSettings.buildLanguageSettings(context), 31 | ], 32 | ); 33 | } 34 | 35 | @override 36 | Widget build(BuildContext context) { 37 | return Scaffold( 38 | body: SafeArea( 39 | child: Column( 40 | children: [ 41 | if (!widget.isFromNavigation) 42 | Padding( 43 | padding: const EdgeInsets.all(10.0), 44 | child: Text(LocaleKeys.settings).tr(), 45 | ), 46 | Expanded(child: _buildSettingsList(context)), 47 | ], 48 | ), 49 | ), 50 | ); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /lib/repositories/ai_api_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:ai_client/database/app_database.dart'; 2 | import 'package:drift/drift.dart'; 3 | 4 | /// AiApi 数据访问对象 5 | class AiApiRepository { 6 | /// 数据库实例 7 | final AppDatabase database; 8 | 9 | // 构造函数 10 | AiApiRepository(this.database); 11 | 12 | /// 获取所有AI API记录 13 | /// 返回AI API列表 14 | Future> getAllAiApis() { 15 | return database.select(database.aiApi).get(); 16 | } 17 | 18 | /// 获取所有启用的AI API记录 19 | /// 1:激活 0:未激活 20 | /// 返回启用的AI API列表 21 | Future> getAllActiveAiApis() { 22 | return (database.select(database.aiApi) 23 | ..where((t) => t.isActive.equals(true))) 24 | .get(); 25 | } 26 | 27 | /// 根据ID获取单个AI API 28 | /// 29 | /// [id] AI API ID 30 | /// 返回单个AI API对象 31 | Future getAiApiById(int id) { 32 | return (database.select(database.aiApi)..where((t) => t.id.equals(id))) 33 | .getSingleOrNull(); 34 | } 35 | 36 | /// 插入AI API记录 37 | /// 38 | /// [aiApi] AI API对象 39 | /// 返回插入的记录ID 40 | Future insertAiApi(Insertable aiApi) { 41 | return database.into(database.aiApi).insert(aiApi); 42 | } 43 | 44 | /// 批量插入AI API记录 45 | /// 46 | /// [aiApis] AI API列表 47 | Future insertAiApis(List> aiApis) { 48 | return database.batch((batch) { 49 | batch.insertAll(database.aiApi, aiApis); 50 | }); 51 | } 52 | 53 | /// 更新AI API记录 54 | /// 55 | /// [aiApi] AI API对象 56 | /// 返回更新是否成功 57 | Future updateAiApi(Insertable aiApi) { 58 | return database.update(database.aiApi).replace(aiApi); 59 | } 60 | 61 | /// 删除AI API记录 62 | /// 63 | /// [id] AI API ID 64 | /// 返回删除的记录数量 65 | Future deleteAiApi(int id) { 66 | return (database.delete(database.aiApi)..where((t) => t.id.equals(id))) 67 | .go(); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /lib/repositories/chat_message_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:ai_client/database/app_database.dart'; 2 | import 'package:drift/drift.dart'; 3 | 4 | class ChatMessageRepository { 5 | final AppDatabase _database; 6 | 7 | ChatMessageRepository(this._database); 8 | 9 | // 获取会话的所有消息 10 | Future> getMessagesBySessionId(int sessionId) async { 11 | return await (_database.select(_database.chatMessages) 12 | ..where((t) => t.sessionId.equals(sessionId)) 13 | ..orderBy([(t) => OrderingTerm(expression: t.createdTime)])) 14 | .get(); 15 | } 16 | 17 | // 获取单条消息 18 | Future getMessageById(int id) async { 19 | return await (_database.select(_database.chatMessages) 20 | ..where((t) => t.id.equals(id))) 21 | .getSingleOrNull(); 22 | } 23 | 24 | // 创建新消息 25 | Future createMessage(ChatMessagesCompanion message) async { 26 | return await _database.into(_database.chatMessages).insert(message); 27 | } 28 | 29 | // 更新消息 30 | Future updateMessage(ChatMessage message) async { 31 | return await _database.update(_database.chatMessages).replace(message); 32 | } 33 | 34 | // 删除消息 35 | Future deleteMessage(int id) async { 36 | return await (_database.delete(_database.chatMessages) 37 | ..where((t) => t.id.equals(id))) 38 | .go(); 39 | } 40 | 41 | // 删除会话的所有消息 42 | Future deleteMessagesBySessionId(int sessionId) async { 43 | return await (_database.delete(_database.chatMessages) 44 | ..where((t) => t.sessionId.equals(sessionId))) 45 | .go(); 46 | } 47 | 48 | // 搜索消息 49 | Future> searchMessages(String query) async { 50 | return await (_database.select(_database.chatMessages) 51 | ..where((t) => t.content.contains(query))) 52 | .get(); 53 | } 54 | } -------------------------------------------------------------------------------- /lib/repositories/chat_session_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:ai_client/database/app_database.dart'; 2 | import 'package:drift/drift.dart'; 3 | 4 | class ChatSessionRepository { 5 | final AppDatabase _database; 6 | 7 | ChatSessionRepository(this._database); 8 | 9 | // 获取所有会话 10 | Future> getAllSessions() async { 11 | return await _database.select(_database.chatSessions).get(); 12 | } 13 | 14 | // 获取收藏的会话 15 | Future> getFavoriteSessions() async { 16 | return await (_database.select(_database.chatSessions) 17 | ..where((t) => t.isFavorite.equals(true))) 18 | .get(); 19 | } 20 | 21 | // 根据ID获取会话 22 | Future getSessionById(int id) async { 23 | return await (_database.select(_database.chatSessions) 24 | ..where((t) => t.id.equals(id))) 25 | .getSingleOrNull(); 26 | } 27 | 28 | // 创建新会话 29 | Future createSession(ChatSessionsCompanion session) async { 30 | return await _database.into(_database.chatSessions).insert(session); 31 | } 32 | 33 | // 更新会话 34 | Future updateSession(ChatSession session) async { 35 | return await _database.update(_database.chatSessions).replace(session); 36 | } 37 | 38 | // 删除会话 39 | Future deleteSession(int id) async { 40 | return await (_database.delete(_database.chatSessions) 41 | ..where((t) => t.id.equals(id))) 42 | .go(); 43 | } 44 | 45 | // 搜索会话 46 | Future> searchSessions(String query) async { 47 | return await (_database.select(_database.chatSessions) 48 | ..where((t) => t.title.contains(query))) 49 | .get(); 50 | } 51 | 52 | // 切换收藏状态 53 | Future toggleFavorite(int id) async { 54 | final session = await getSessionById(id); 55 | if (session != null) { 56 | final updatedSession = session.copyWith(isFavorite: !session.isFavorite); 57 | return await updateSession(updatedSession); 58 | } 59 | return false; 60 | } 61 | } -------------------------------------------------------------------------------- /lib/repositories/file_attachment_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:ai_client/database/app_database.dart'; 2 | 3 | class FileAttachmentRepository { 4 | final AppDatabase _database; 5 | 6 | FileAttachmentRepository(this._database); 7 | 8 | // 获取消息的所有附件 9 | Future> getAttachmentsByMessageId(int messageId) async { 10 | return await (_database.select(_database.fileAttachments) 11 | ..where((t) => t.messageId.equals(messageId))) 12 | .get(); 13 | } 14 | 15 | // 获取单个附件 16 | Future getAttachmentById(int id) async { 17 | return await (_database.select(_database.fileAttachments) 18 | ..where((t) => t.id.equals(id))) 19 | .getSingleOrNull(); 20 | } 21 | 22 | // 创建新附件 23 | Future createAttachment(FileAttachmentsCompanion attachment) async { 24 | return await _database.into(_database.fileAttachments).insert(attachment); 25 | } 26 | 27 | // 更新附件 28 | Future updateAttachment(FileAttachment attachment) async { 29 | return await _database.update(_database.fileAttachments).replace(attachment); 30 | } 31 | 32 | // 删除附件 33 | Future deleteAttachment(int id) async { 34 | return await (_database.delete(_database.fileAttachments) 35 | ..where((t) => t.id.equals(id))) 36 | .go(); 37 | } 38 | 39 | // 删除消息的所有附件 40 | Future deleteAttachmentsByMessageId(int messageId) async { 41 | return await (_database.delete(_database.fileAttachments) 42 | ..where((t) => t.messageId.equals(messageId))) 43 | .go(); 44 | } 45 | 46 | // 根据文件类型获取附件 47 | Future> getAttachmentsByFileType(String fileType) async { 48 | return await (_database.select(_database.fileAttachments) 49 | ..where((t) => t.fileType.equals(fileType))) 50 | .get(); 51 | } 52 | } -------------------------------------------------------------------------------- /lib/services/ai_api_service.dart: -------------------------------------------------------------------------------- 1 | import 'package:ai_client/database/app_database.dart'; 2 | import 'package:ai_client/generated/default_api_configs.dart'; 3 | import 'package:ai_client/repositories/ai_api_repository.dart'; 4 | import 'package:drift/drift.dart'; 5 | import 'dart:developer' as developer; 6 | 7 | /// AiApi服务类 8 | class AiApiService { 9 | // AiApiRepository实例 10 | final AiApiRepository aiApiRepository; 11 | 12 | // 构造函数 13 | AiApiService(this.aiApiRepository); 14 | 15 | /// 初始化AiApi默认配置 16 | /// 返回AI API列表 17 | Future> initDefaultAiApiConfig() async { 18 | // 查询数据 19 | var data = await getAllAiApis(); 20 | // 判断是否有数据 21 | if (data.isEmpty) { 22 | // 获取默认配置信息 23 | final defaultApiConfig = DefaultApiConfigs.defaultApiConfig(); 24 | // 添加默认数据到数据库 25 | insertAiApis(defaultApiConfig); 26 | // 重新查询数据 27 | data = await getAllAiApis(); 28 | } 29 | // 返回数据 30 | return data; 31 | } 32 | 33 | /// 获取所有AI API记录 34 | /// 返回AI API列表 35 | Future> getAllAiApis() async { 36 | try { 37 | // 调用数据层方法获取所有API记录 38 | return await aiApiRepository.getAllAiApis(); 39 | } catch (e, stackTrace) { 40 | // 发生异常时记录错误日志并返回空列表 41 | developer.log('获取所有AI API失败', error: e, stackTrace: stackTrace); 42 | return []; 43 | } 44 | } 45 | 46 | /// 获取所有启用的AI API记录 47 | /// 1:激活 0:未激活 48 | Future> getAllActiveAiApis() async { 49 | try { 50 | // 调用数据层方法获取所有激活状态的API记录 51 | return await aiApiRepository.getAllActiveAiApis(); 52 | } catch (e, stackTrace) { 53 | // 发生异常时记录错误日志并返回空列表 54 | developer.log('获取所有启用的AI API失败', error: e, stackTrace: stackTrace); 55 | return []; 56 | } 57 | } 58 | 59 | /// 根据ID获取AI API记录 60 | /// [id] AI API记录ID 61 | /// 返回AI API对象 62 | Future getAiApiById(int id) async { 63 | try { 64 | // 调用数据层方法根据ID查询API记录 65 | return await aiApiRepository.getAiApiById(id); 66 | } catch (e, stackTrace) { 67 | // 发生异常时记录错误日志并返回null 68 | developer.log('根据ID获取AI API失败: $id', error: e, stackTrace: stackTrace); 69 | return null; 70 | } 71 | } 72 | 73 | /// 插入AI API记录 74 | /// [aiApi] AI API对象 75 | Future insertAiApi(AiApiCompanion aiApi) async { 76 | try { 77 | // 获取当前时间戳 78 | final now = DateTime.now(); 79 | // 设置创建时间和更新时间,并将id设置为null 80 | aiApi = aiApi.copyWith( 81 | id: const Value.absent(), 82 | createdTime: Value(now), 83 | updatedTime: Value(now)); 84 | // 调用数据层方法插入API记录 85 | await aiApiRepository.insertAiApi(aiApi); 86 | // 插入成功返回true 87 | return true; 88 | } catch (e, stackTrace) { 89 | // 发生异常时记录错误日志并返回false 90 | developer.log('插入AI API失败', error: e, stackTrace: stackTrace); 91 | return false; 92 | } 93 | } 94 | 95 | /// 批量插入AI API记录 96 | /// [aiApis] AI API列表 97 | Future insertAiApis(List aiApis) async { 98 | try { 99 | // 获取当前时间戳 100 | final now = DateTime.now(); 101 | // 为每条记录设置创建时间和更新时间 102 | aiApis = aiApis.map((aiApi) { 103 | return aiApi.copyWith(createdTime: Value(now), updatedTime: Value(now)); 104 | }).toList(); 105 | // 调用数据层方法批量插入API记录 106 | await aiApiRepository.insertAiApis(aiApis); 107 | // 插入成功返回true 108 | return true; 109 | } catch (e, stackTrace) { 110 | // 发生异常时记录错误日志并返回false 111 | developer.log('批量插入AI API失败', error: e, stackTrace: stackTrace); 112 | return false; 113 | } 114 | } 115 | 116 | /// 更新AI API记录 117 | /// [aiApi] AI API对象 118 | Future updateAiApi(AiApiCompanion aiApi) async { 119 | try { 120 | // 获取当前时间戳 121 | final now = DateTime.now(); 122 | // 更新记录的更新时间 123 | aiApi = aiApi.copyWith(updatedTime: Value(now)); 124 | // 调用数据层方法更新API记录 125 | return await aiApiRepository.updateAiApi(aiApi); 126 | } catch (e, stackTrace) { 127 | // 发生异常时记录错误日志并返回false 128 | developer.log('更新AI API失败', error: e, stackTrace: stackTrace); 129 | return false; 130 | } 131 | } 132 | 133 | /// 根据ID删除AI API记录 134 | /// [id] AI API记录ID 135 | /// 返回删除的记录数,失败返回0 136 | Future deleteAiApiById(int id) async { 137 | try { 138 | // 调用数据层方法删除API记录 139 | return await aiApiRepository.deleteAiApi(id); 140 | } catch (e, stackTrace) { 141 | // 发生异常时记录错误日志并返回0 142 | developer.log('删除AI API失败: $id', error: e, stackTrace: stackTrace); 143 | return 0; 144 | } 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /lib/services/chat_message_service.dart: -------------------------------------------------------------------------------- 1 | import 'package:ai_client/database/app_database.dart'; 2 | import 'package:drift/drift.dart'; 3 | import '../models/chat_message.dart'; 4 | import '../repositories/chat_message_repository.dart'; 5 | import '../repositories/chat_session_repository.dart'; 6 | 7 | class ChatMessageService { 8 | final ChatMessageRepository _messageRepository; 9 | final ChatSessionRepository _sessionRepository; 10 | 11 | ChatMessageService(this._messageRepository, this._sessionRepository); 12 | 13 | // 获取会话的所有消息 14 | Future> getMessagesBySessionId(int sessionId) async { 15 | return await _messageRepository.getMessagesBySessionId(sessionId); 16 | } 17 | 18 | // 获取单条消息 19 | Future getMessageById(int id) async { 20 | return await _messageRepository.getMessageById(id); 21 | } 22 | 23 | // 创建文本消息 24 | Future createTextMessage({ 25 | required int sessionId, 26 | required String content, 27 | required int apiConfigId, 28 | required String model, 29 | required MessageRole role, 30 | required MessageStatus status, 31 | }) async { 32 | // 创建消息 33 | final message = ChatMessagesCompanion.insert( 34 | sessionId: sessionId, 35 | content: content, 36 | apiConfigId: Value(apiConfigId), 37 | model: Value(model), 38 | type: MessageType.text, 39 | role: role, 40 | status: status, 41 | ); 42 | 43 | final messageId = await _messageRepository.createMessage(message); 44 | 45 | // 更新会话的最后更新时间 46 | final session = await _sessionRepository.getSessionById(sessionId); 47 | if (session != null) { 48 | await _sessionRepository 49 | .updateSession(session.copyWith(updatedTime: DateTime.now())); 50 | } 51 | 52 | return messageId; 53 | } 54 | 55 | // 创建文件消息 56 | Future createFileMessage({ 57 | required int sessionId, 58 | required String content, 59 | required int apiConfigId, 60 | required String model, 61 | required MessageRole role, 62 | required String filePath, 63 | required MessageStatus status, 64 | }) async { 65 | final message = ChatMessagesCompanion.insert( 66 | sessionId: sessionId, 67 | content: content, 68 | apiConfigId: Value(apiConfigId), 69 | model: Value(model), 70 | type: MessageType.file, 71 | role: role, 72 | filePath: Value(filePath), 73 | status: status); 74 | 75 | final messageId = await _messageRepository.createMessage(message); 76 | 77 | // 更新会话的最后更新时间 78 | final session = await _sessionRepository.getSessionById(sessionId); 79 | if (session != null) { 80 | await _sessionRepository 81 | .updateSession(session.copyWith(updatedTime: DateTime.now())); 82 | } 83 | 84 | return messageId; 85 | } 86 | 87 | // 更新消息内容 88 | Future updateMessageContent(int id, String content) async { 89 | final message = await _messageRepository.getMessageById(id); 90 | if (message != null) { 91 | final updatedMessage = message.copyWith(content: content); 92 | return await _messageRepository.updateMessage(updatedMessage); 93 | } 94 | return false; 95 | } 96 | 97 | // 更新消息状态 98 | Future updateMessageStatus(int id, MessageStatus status) async { 99 | final message = await _messageRepository.getMessageById(id); 100 | if (message != null) { 101 | final updatedMessage = message.copyWith(status: status); 102 | return await _messageRepository.updateMessage(updatedMessage); 103 | } 104 | return false; 105 | } 106 | 107 | // 删除消息 108 | Future deleteMessage(int id) async { 109 | final result = await _messageRepository.deleteMessage(id); 110 | return result > 0; 111 | } 112 | 113 | // 删除会话的所有消息 114 | Future deleteMessagesBySessionId(int sessionId) async { 115 | final result = 116 | await _messageRepository.deleteMessagesBySessionId(sessionId); 117 | return result > 0; 118 | } 119 | 120 | // 搜索消息 121 | Future> searchMessages(String query) async { 122 | if (query.isEmpty) { 123 | return []; 124 | } 125 | return await _messageRepository.searchMessages(query); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /lib/services/file_attachment_service.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'package:ai_client/database/app_database.dart'; 3 | import 'package:path/path.dart' as path; 4 | import 'package:path_provider/path_provider.dart'; 5 | import '../repositories/file_attachment_repository.dart'; 6 | 7 | class FileAttachmentService { 8 | final FileAttachmentRepository _attachmentRepository; 9 | 10 | FileAttachmentService(this._attachmentRepository); 11 | 12 | // 获取消息的所有附件 13 | Future> getAttachmentsByMessageId(int messageId) async { 14 | return await _attachmentRepository.getAttachmentsByMessageId(messageId); 15 | } 16 | 17 | // 获取单个附件 18 | Future getAttachmentById(int id) async { 19 | return await _attachmentRepository.getAttachmentById(id); 20 | } 21 | 22 | // 上传文件并创建附件 23 | Future uploadFile({ 24 | required int messageId, 25 | required File file, 26 | }) async { 27 | try { 28 | // 获取应用文档目录 29 | final appDir = await getApplicationDocumentsDirectory(); 30 | final attachmentsDir = Directory('${appDir.path}/attachments'); 31 | if (!await attachmentsDir.exists()) { 32 | await attachmentsDir.create(recursive: true); 33 | } 34 | 35 | // 生成唯一文件名 36 | final fileName = path.basename(file.path); 37 | final uniqueFileName = '${DateTime.now().millisecondsSinceEpoch}_$fileName'; 38 | final targetPath = '${attachmentsDir.path}/$uniqueFileName'; 39 | 40 | // 复制文件到应用目录 41 | final newFile = await file.copy(targetPath); 42 | final fileSize = await newFile.length(); 43 | final fileType = path.extension(file.path).replaceAll('.', ''); 44 | 45 | // 创建附件记录 46 | final attachment = FileAttachmentsCompanion.insert( 47 | messageId: messageId, 48 | fileName: fileName, 49 | filePath: targetPath, 50 | fileSize: fileSize, 51 | fileType: fileType, 52 | ); 53 | 54 | return await _attachmentRepository.createAttachment(attachment); 55 | } catch (e) { 56 | print('上传文件失败: $e'); 57 | rethrow; 58 | } 59 | } 60 | 61 | // 删除附件及其文件 62 | Future deleteAttachment(int id) async { 63 | try { 64 | final attachment = await _attachmentRepository.getAttachmentById(id); 65 | if (attachment != null) { 66 | // 删除文件 67 | final file = File(attachment.filePath); 68 | if (await file.exists()) { 69 | await file.delete(); 70 | } 71 | 72 | // 删除数据库记录 73 | final result = await _attachmentRepository.deleteAttachment(id); 74 | return result > 0; 75 | } 76 | return false; 77 | } catch (e) { 78 | print('删除附件失败: $e'); 79 | return false; 80 | } 81 | } 82 | 83 | // 删除消息的所有附件 84 | Future deleteAttachmentsByMessageId(int messageId) async { 85 | try { 86 | final attachments = await _attachmentRepository.getAttachmentsByMessageId(messageId); 87 | 88 | // 删除所有文件 89 | for (var attachment in attachments) { 90 | final file = File(attachment.filePath); 91 | if (await file.exists()) { 92 | await file.delete(); 93 | } 94 | } 95 | 96 | // 删除数据库记录 97 | final result = await _attachmentRepository.deleteAttachmentsByMessageId(messageId); 98 | return result > 0; 99 | } catch (e) { 100 | print('删除消息附件失败: $e'); 101 | return false; 102 | } 103 | } 104 | 105 | // 根据文件类型获取附件 106 | Future> getAttachmentsByFileType(String fileType) async { 107 | return await _attachmentRepository.getAttachmentsByFileType(fileType); 108 | } 109 | 110 | // 获取文件大小的可读字符串 111 | String getReadableFileSize(int bytes) { 112 | const suffixes = ['B', 'KB', 'MB', 'GB', 'TB']; 113 | var i = 0; 114 | double size = bytes.toDouble(); 115 | 116 | while (size >= 1024 && i < suffixes.length - 1) { 117 | size /= 1024; 118 | i++; 119 | } 120 | 121 | return '${size.toStringAsFixed(2)} ${suffixes[i]}'; 122 | } 123 | } -------------------------------------------------------------------------------- /linux/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral 2 | -------------------------------------------------------------------------------- /linux/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Project-level configuration. 2 | cmake_minimum_required(VERSION 3.13) 3 | project(runner LANGUAGES CXX) 4 | 5 | # The name of the executable created for the application. Change this to change 6 | # the on-disk name of your application. 7 | set(BINARY_NAME "AiClient") 8 | # The unique GTK application identifier for this application. See: 9 | # https://wiki.gnome.org/HowDoI/ChooseApplicationID 10 | set(APPLICATION_ID "dev.hret.AiClient") 11 | 12 | # Explicitly opt in to modern CMake behaviors to avoid warnings with recent 13 | # versions of CMake. 14 | cmake_policy(SET CMP0063 NEW) 15 | 16 | # Load bundled libraries from the lib/ directory relative to the binary. 17 | set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") 18 | 19 | # Root filesystem for cross-building. 20 | if(FLUTTER_TARGET_PLATFORM_SYSROOT) 21 | set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) 22 | set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) 23 | set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) 24 | set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) 25 | set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) 26 | set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) 27 | endif() 28 | 29 | # Define build configuration options. 30 | if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) 31 | set(CMAKE_BUILD_TYPE "Debug" CACHE 32 | STRING "Flutter build mode" FORCE) 33 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS 34 | "Debug" "Profile" "Release") 35 | endif() 36 | 37 | # Compilation settings that should be applied to most targets. 38 | # 39 | # Be cautious about adding new options here, as plugins use this function by 40 | # default. In most cases, you should add new options to specific targets instead 41 | # of modifying this function. 42 | function(APPLY_STANDARD_SETTINGS TARGET) 43 | target_compile_features(${TARGET} PUBLIC cxx_std_14) 44 | target_compile_options(${TARGET} PRIVATE -Wall -Werror) 45 | target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") 46 | target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") 47 | endfunction() 48 | 49 | # Flutter library and tool build rules. 50 | set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") 51 | add_subdirectory(${FLUTTER_MANAGED_DIR}) 52 | 53 | # System-level dependencies. 54 | find_package(PkgConfig REQUIRED) 55 | pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) 56 | 57 | # Application build; see runner/CMakeLists.txt. 58 | add_subdirectory("runner") 59 | 60 | # Run the Flutter tool portions of the build. This must not be removed. 61 | add_dependencies(${BINARY_NAME} flutter_assemble) 62 | 63 | # Only the install-generated bundle's copy of the executable will launch 64 | # correctly, since the resources must in the right relative locations. To avoid 65 | # people trying to run the unbundled copy, put it in a subdirectory instead of 66 | # the default top-level location. 67 | set_target_properties(${BINARY_NAME} 68 | PROPERTIES 69 | RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" 70 | ) 71 | 72 | 73 | # Generated plugin build rules, which manage building the plugins and adding 74 | # them to the application. 75 | include(flutter/generated_plugins.cmake) 76 | 77 | 78 | # === Installation === 79 | # By default, "installing" just makes a relocatable bundle in the build 80 | # directory. 81 | set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") 82 | if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) 83 | set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) 84 | endif() 85 | 86 | # Start with a clean build bundle directory every time. 87 | install(CODE " 88 | file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") 89 | " COMPONENT Runtime) 90 | 91 | set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") 92 | set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") 93 | 94 | install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" 95 | COMPONENT Runtime) 96 | 97 | install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" 98 | COMPONENT Runtime) 99 | 100 | install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 101 | COMPONENT Runtime) 102 | 103 | foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) 104 | install(FILES "${bundled_library}" 105 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 106 | COMPONENT Runtime) 107 | endforeach(bundled_library) 108 | 109 | # Copy the native assets provided by the build.dart from all packages. 110 | set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/") 111 | install(DIRECTORY "${NATIVE_ASSETS_DIR}" 112 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 113 | COMPONENT Runtime) 114 | 115 | # Fully re-copy the assets directory on each build to avoid having stale files 116 | # from a previous install. 117 | set(FLUTTER_ASSET_DIR_NAME "flutter_assets") 118 | install(CODE " 119 | file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") 120 | " COMPONENT Runtime) 121 | install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" 122 | DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) 123 | 124 | # Install the AOT library on non-Debug builds only. 125 | if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") 126 | install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 127 | COMPONENT Runtime) 128 | endif() 129 | -------------------------------------------------------------------------------- /linux/flutter/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file controls Flutter-level build steps. It should not be edited. 2 | cmake_minimum_required(VERSION 3.10) 3 | 4 | set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") 5 | 6 | # Configuration provided via flutter tool. 7 | include(${EPHEMERAL_DIR}/generated_config.cmake) 8 | 9 | # TODO: Move the rest of this into files in ephemeral. See 10 | # https://github.com/flutter/flutter/issues/57146. 11 | 12 | # Serves the same purpose as list(TRANSFORM ... PREPEND ...), 13 | # which isn't available in 3.10. 14 | function(list_prepend LIST_NAME PREFIX) 15 | set(NEW_LIST "") 16 | foreach(element ${${LIST_NAME}}) 17 | list(APPEND NEW_LIST "${PREFIX}${element}") 18 | endforeach(element) 19 | set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) 20 | endfunction() 21 | 22 | # === Flutter Library === 23 | # System-level dependencies. 24 | find_package(PkgConfig REQUIRED) 25 | pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) 26 | pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) 27 | pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) 28 | 29 | set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") 30 | 31 | # Published to parent scope for install step. 32 | set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) 33 | set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) 34 | set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) 35 | set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) 36 | 37 | list(APPEND FLUTTER_LIBRARY_HEADERS 38 | "fl_basic_message_channel.h" 39 | "fl_binary_codec.h" 40 | "fl_binary_messenger.h" 41 | "fl_dart_project.h" 42 | "fl_engine.h" 43 | "fl_json_message_codec.h" 44 | "fl_json_method_codec.h" 45 | "fl_message_codec.h" 46 | "fl_method_call.h" 47 | "fl_method_channel.h" 48 | "fl_method_codec.h" 49 | "fl_method_response.h" 50 | "fl_plugin_registrar.h" 51 | "fl_plugin_registry.h" 52 | "fl_standard_message_codec.h" 53 | "fl_standard_method_codec.h" 54 | "fl_string_codec.h" 55 | "fl_value.h" 56 | "fl_view.h" 57 | "flutter_linux.h" 58 | ) 59 | list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") 60 | add_library(flutter INTERFACE) 61 | target_include_directories(flutter INTERFACE 62 | "${EPHEMERAL_DIR}" 63 | ) 64 | target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") 65 | target_link_libraries(flutter INTERFACE 66 | PkgConfig::GTK 67 | PkgConfig::GLIB 68 | PkgConfig::GIO 69 | ) 70 | add_dependencies(flutter flutter_assemble) 71 | 72 | # === Flutter tool backend === 73 | # _phony_ is a non-existent file to force this command to run every time, 74 | # since currently there's no way to get a full input/output list from the 75 | # flutter tool. 76 | add_custom_command( 77 | OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} 78 | ${CMAKE_CURRENT_BINARY_DIR}/_phony_ 79 | COMMAND ${CMAKE_COMMAND} -E env 80 | ${FLUTTER_TOOL_ENVIRONMENT} 81 | "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" 82 | ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} 83 | VERBATIM 84 | ) 85 | add_custom_target(flutter_assemble DEPENDS 86 | "${FLUTTER_LIBRARY}" 87 | ${FLUTTER_LIBRARY_HEADERS} 88 | ) 89 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | void fl_register_plugins(FlPluginRegistry* registry) { 14 | g_autoptr(FlPluginRegistrar) screen_retriever_linux_registrar = 15 | fl_plugin_registry_get_registrar_for_plugin(registry, "ScreenRetrieverLinuxPlugin"); 16 | screen_retriever_linux_plugin_register_with_registrar(screen_retriever_linux_registrar); 17 | g_autoptr(FlPluginRegistrar) sqlite3_flutter_libs_registrar = 18 | fl_plugin_registry_get_registrar_for_plugin(registry, "Sqlite3FlutterLibsPlugin"); 19 | sqlite3_flutter_libs_plugin_register_with_registrar(sqlite3_flutter_libs_registrar); 20 | g_autoptr(FlPluginRegistrar) window_manager_registrar = 21 | fl_plugin_registry_get_registrar_for_plugin(registry, "WindowManagerPlugin"); 22 | window_manager_plugin_register_with_registrar(window_manager_registrar); 23 | } 24 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include 11 | 12 | // Registers Flutter plugins. 13 | void fl_register_plugins(FlPluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | screen_retriever_linux 7 | sqlite3_flutter_libs 8 | window_manager 9 | ) 10 | 11 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 12 | ) 13 | 14 | set(PLUGIN_BUNDLED_LIBRARIES) 15 | 16 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 17 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) 18 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 19 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 20 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 21 | endforeach(plugin) 22 | 23 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 24 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) 25 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 26 | endforeach(ffi_plugin) 27 | -------------------------------------------------------------------------------- /linux/packaging/appimage/make_config.yaml: -------------------------------------------------------------------------------- 1 | display_name: AiClient 2 | 3 | icon: ./assets/icon/icon.png 4 | 5 | keywords: 6 | - AiClient 7 | - AI 8 | - Chat 9 | - Client 10 | 11 | generic_name: AiClient 12 | 13 | 14 | categories: 15 | - Network 16 | 17 | startup_notify: true 18 | 19 | include: [] -------------------------------------------------------------------------------- /linux/packaging/deb/make_config.yaml: -------------------------------------------------------------------------------- 1 | display_name: AiClient 2 | package_name: AiClient 3 | maintainer: 4 | name: HRET 5 | email: hret.dev@gmail.com 6 | 7 | priority: optional 8 | section: x11 9 | installed_size: 6604 10 | essential: false 11 | icon: ./assets/icon/icon.png 12 | 13 | keywords: 14 | - AiClient 15 | - AI 16 | - Chat 17 | - Client 18 | 19 | generic_name: AiClient 20 | 21 | categories: 22 | - Network 23 | 24 | startup_notify: true -------------------------------------------------------------------------------- /linux/packaging/rpm/make_config.yaml: -------------------------------------------------------------------------------- 1 | display_name: AiClient 2 | 3 | packager: HRET 4 | packagerEmail: hret.dev@gmail.com 5 | license: Other 6 | 7 | priority: optional 8 | section: x11 9 | installed_size: 6604 10 | essential: false 11 | icon: ./assets/icon/icon.png 12 | 13 | keywords: 14 | - AiClient 15 | - AI 16 | - Chat 17 | - Client 18 | 19 | generic_name: AiClient 20 | 21 | group: Applications/Internet 22 | 23 | startup_notify: true -------------------------------------------------------------------------------- /linux/runner/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13) 2 | project(runner LANGUAGES CXX) 3 | 4 | # Define the application target. To change its name, change BINARY_NAME in the 5 | # top-level CMakeLists.txt, not the value here, or `flutter run` will no longer 6 | # work. 7 | # 8 | # Any new source files that you add to the application should be added here. 9 | add_executable(${BINARY_NAME} 10 | "main.cc" 11 | "my_application.cc" 12 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" 13 | ) 14 | 15 | # Apply the standard set of build settings. This can be removed for applications 16 | # that need different build settings. 17 | apply_standard_settings(${BINARY_NAME}) 18 | 19 | # Add preprocessor definitions for the application ID. 20 | add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") 21 | 22 | # Add dependency libraries. Add any application-specific dependencies here. 23 | target_link_libraries(${BINARY_NAME} PRIVATE flutter) 24 | target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) 25 | 26 | target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") 27 | -------------------------------------------------------------------------------- /linux/runner/main.cc: -------------------------------------------------------------------------------- 1 | #include "my_application.h" 2 | 3 | int main(int argc, char** argv) { 4 | g_autoptr(MyApplication) app = my_application_new(); 5 | return g_application_run(G_APPLICATION(app), argc, argv); 6 | } 7 | -------------------------------------------------------------------------------- /linux/runner/my_application.cc: -------------------------------------------------------------------------------- 1 | #include "my_application.h" 2 | 3 | #include 4 | #ifdef GDK_WINDOWING_X11 5 | #include 6 | #endif 7 | 8 | #include "flutter/generated_plugin_registrant.h" 9 | 10 | struct _MyApplication { 11 | GtkApplication parent_instance; 12 | char** dart_entrypoint_arguments; 13 | }; 14 | 15 | G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) 16 | 17 | // Implements GApplication::activate. 18 | static void my_application_activate(GApplication* application) { 19 | MyApplication* self = MY_APPLICATION(application); 20 | GtkWindow* window = 21 | GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); 22 | 23 | // Use a header bar when running in GNOME as this is the common style used 24 | // by applications and is the setup most users will be using (e.g. Ubuntu 25 | // desktop). 26 | // If running on X and not using GNOME then just use a traditional title bar 27 | // in case the window manager does more exotic layout, e.g. tiling. 28 | // If running on Wayland assume the header bar will work (may need changing 29 | // if future cases occur). 30 | gboolean use_header_bar = TRUE; 31 | #ifdef GDK_WINDOWING_X11 32 | GdkScreen* screen = gtk_window_get_screen(window); 33 | if (GDK_IS_X11_SCREEN(screen)) { 34 | const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); 35 | if (g_strcmp0(wm_name, "GNOME Shell") != 0) { 36 | use_header_bar = FALSE; 37 | } 38 | } 39 | #endif 40 | if (use_header_bar) { 41 | GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); 42 | gtk_widget_show(GTK_WIDGET(header_bar)); 43 | gtk_header_bar_set_title(header_bar, "AiClient"); 44 | gtk_header_bar_set_show_close_button(header_bar, TRUE); 45 | gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); 46 | } else { 47 | gtk_window_set_title(window, "AiClient"); 48 | } 49 | 50 | gtk_window_set_default_size(window, 1280, 720); 51 | gtk_widget_show(GTK_WIDGET(window)); 52 | 53 | g_autoptr(FlDartProject) project = fl_dart_project_new(); 54 | fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); 55 | 56 | FlView* view = fl_view_new(project); 57 | gtk_widget_show(GTK_WIDGET(view)); 58 | gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); 59 | 60 | fl_register_plugins(FL_PLUGIN_REGISTRY(view)); 61 | 62 | gtk_widget_grab_focus(GTK_WIDGET(view)); 63 | } 64 | 65 | // Implements GApplication::local_command_line. 66 | static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) { 67 | MyApplication* self = MY_APPLICATION(application); 68 | // Strip out the first argument as it is the binary name. 69 | self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); 70 | 71 | g_autoptr(GError) error = nullptr; 72 | if (!g_application_register(application, nullptr, &error)) { 73 | g_warning("Failed to register: %s", error->message); 74 | *exit_status = 1; 75 | return TRUE; 76 | } 77 | 78 | g_application_activate(application); 79 | *exit_status = 0; 80 | 81 | return TRUE; 82 | } 83 | 84 | // Implements GApplication::startup. 85 | static void my_application_startup(GApplication* application) { 86 | //MyApplication* self = MY_APPLICATION(object); 87 | 88 | // Perform any actions required at application startup. 89 | 90 | G_APPLICATION_CLASS(my_application_parent_class)->startup(application); 91 | } 92 | 93 | // Implements GApplication::shutdown. 94 | static void my_application_shutdown(GApplication* application) { 95 | //MyApplication* self = MY_APPLICATION(object); 96 | 97 | // Perform any actions required at application shutdown. 98 | 99 | G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application); 100 | } 101 | 102 | // Implements GObject::dispose. 103 | static void my_application_dispose(GObject* object) { 104 | MyApplication* self = MY_APPLICATION(object); 105 | g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); 106 | G_OBJECT_CLASS(my_application_parent_class)->dispose(object); 107 | } 108 | 109 | static void my_application_class_init(MyApplicationClass* klass) { 110 | G_APPLICATION_CLASS(klass)->activate = my_application_activate; 111 | G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; 112 | G_APPLICATION_CLASS(klass)->startup = my_application_startup; 113 | G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown; 114 | G_OBJECT_CLASS(klass)->dispose = my_application_dispose; 115 | } 116 | 117 | static void my_application_init(MyApplication* self) {} 118 | 119 | MyApplication* my_application_new() { 120 | // Set the program name to the application ID, which helps various systems 121 | // like GTK and desktop environments map this running application to its 122 | // corresponding .desktop file. This ensures better integration by allowing 123 | // the application to be recognized beyond its binary name. 124 | g_set_prgname(APPLICATION_ID); 125 | 126 | return MY_APPLICATION(g_object_new(my_application_get_type(), 127 | "application-id", APPLICATION_ID, 128 | "flags", G_APPLICATION_NON_UNIQUE, 129 | nullptr)); 130 | } 131 | -------------------------------------------------------------------------------- /linux/runner/my_application.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUTTER_MY_APPLICATION_H_ 2 | #define FLUTTER_MY_APPLICATION_H_ 3 | 4 | #include 5 | 6 | G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, 7 | GtkApplication) 8 | 9 | /** 10 | * my_application_new: 11 | * 12 | * Creates a new Flutter-based application. 13 | * 14 | * Returns: a new #MyApplication. 15 | */ 16 | MyApplication* my_application_new(); 17 | 18 | #endif // FLUTTER_MY_APPLICATION_H_ 19 | -------------------------------------------------------------------------------- /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 connectivity_plus 9 | import path_provider_foundation 10 | import screen_retriever_macos 11 | import shared_preferences_foundation 12 | import sqlite3_flutter_libs 13 | import window_manager 14 | 15 | func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { 16 | ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin")) 17 | PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) 18 | ScreenRetrieverMacosPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverMacosPlugin")) 19 | SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) 20 | Sqlite3FlutterLibsPlugin.register(with: registry.registrar(forPlugin: "Sqlite3FlutterLibsPlugin")) 21 | WindowManagerPlugin.register(with: registry.registrar(forPlugin: "WindowManagerPlugin")) 22 | } 23 | -------------------------------------------------------------------------------- /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 | 32 | flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) 33 | target 'RunnerTests' do 34 | inherit! :search_paths 35 | end 36 | end 37 | 38 | post_install do |installer| 39 | installer.pods_project.targets.each do |target| 40 | flutter_additional_macos_build_settings(target) 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /macos/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - connectivity_plus (0.0.1): 3 | - FlutterMacOS 4 | - FlutterMacOS (1.0.0) 5 | - path_provider_foundation (0.0.1): 6 | - Flutter 7 | - FlutterMacOS 8 | - screen_retriever_macos (0.0.1): 9 | - FlutterMacOS 10 | - shared_preferences_foundation (0.0.1): 11 | - Flutter 12 | - FlutterMacOS 13 | - sqlite3 (3.49.1): 14 | - sqlite3/common (= 3.49.1) 15 | - sqlite3/common (3.49.1) 16 | - sqlite3/dbstatvtab (3.49.1): 17 | - sqlite3/common 18 | - sqlite3/fts5 (3.49.1): 19 | - sqlite3/common 20 | - sqlite3/math (3.49.1): 21 | - sqlite3/common 22 | - sqlite3/perf-threadsafe (3.49.1): 23 | - sqlite3/common 24 | - sqlite3/rtree (3.49.1): 25 | - sqlite3/common 26 | - sqlite3_flutter_libs (0.0.1): 27 | - Flutter 28 | - FlutterMacOS 29 | - sqlite3 (~> 3.49.1) 30 | - sqlite3/dbstatvtab 31 | - sqlite3/fts5 32 | - sqlite3/math 33 | - sqlite3/perf-threadsafe 34 | - sqlite3/rtree 35 | - window_manager (0.2.0): 36 | - FlutterMacOS 37 | 38 | DEPENDENCIES: 39 | - connectivity_plus (from `Flutter/ephemeral/.symlinks/plugins/connectivity_plus/macos`) 40 | - FlutterMacOS (from `Flutter/ephemeral`) 41 | - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`) 42 | - screen_retriever_macos (from `Flutter/ephemeral/.symlinks/plugins/screen_retriever_macos/macos`) 43 | - shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`) 44 | - sqlite3_flutter_libs (from `Flutter/ephemeral/.symlinks/plugins/sqlite3_flutter_libs/darwin`) 45 | - window_manager (from `Flutter/ephemeral/.symlinks/plugins/window_manager/macos`) 46 | 47 | SPEC REPOS: 48 | trunk: 49 | - sqlite3 50 | 51 | EXTERNAL SOURCES: 52 | connectivity_plus: 53 | :path: Flutter/ephemeral/.symlinks/plugins/connectivity_plus/macos 54 | FlutterMacOS: 55 | :path: Flutter/ephemeral 56 | path_provider_foundation: 57 | :path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin 58 | screen_retriever_macos: 59 | :path: Flutter/ephemeral/.symlinks/plugins/screen_retriever_macos/macos 60 | shared_preferences_foundation: 61 | :path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin 62 | sqlite3_flutter_libs: 63 | :path: Flutter/ephemeral/.symlinks/plugins/sqlite3_flutter_libs/darwin 64 | window_manager: 65 | :path: Flutter/ephemeral/.symlinks/plugins/window_manager/macos 66 | 67 | SPEC CHECKSUMS: 68 | connectivity_plus: 4adf20a405e25b42b9c9f87feff8f4b6fde18a4e 69 | FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 70 | path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 71 | screen_retriever_macos: 452e51764a9e1cdb74b3c541238795849f21557f 72 | shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 73 | sqlite3: fc1400008a9b3525f5914ed715a5d1af0b8f4983 74 | sqlite3_flutter_libs: f6acaa2172e6bb3e2e70c771661905080e8ebcf2 75 | window_manager: 1d01fa7ac65a6e6f83b965471b1a7fdd3f06166c 76 | 77 | PODFILE CHECKSUM: 7eb978b976557c8c1cd717d8185ec483fd090a82 78 | 79 | COCOAPODS: 1.16.2 80 | -------------------------------------------------------------------------------- /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 | 64 | 66 | 72 | 73 | 74 | 75 | 81 | 83 | 89 | 90 | 91 | 92 | 94 | 95 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /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 | override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { 11 | return true 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "version": 1, 4 | "author": "xcode" 5 | }, 6 | "images": [ 7 | { 8 | "size": "16x16", 9 | "idiom": "mac", 10 | "filename": "app_icon_16.png", 11 | "scale": "1x" 12 | }, 13 | { 14 | "size": "16x16", 15 | "idiom": "mac", 16 | "filename": "app_icon_32.png", 17 | "scale": "2x" 18 | }, 19 | { 20 | "size": "32x32", 21 | "idiom": "mac", 22 | "filename": "app_icon_32.png", 23 | "scale": "1x" 24 | }, 25 | { 26 | "size": "32x32", 27 | "idiom": "mac", 28 | "filename": "app_icon_64.png", 29 | "scale": "2x" 30 | }, 31 | { 32 | "size": "128x128", 33 | "idiom": "mac", 34 | "filename": "app_icon_128.png", 35 | "scale": "1x" 36 | }, 37 | { 38 | "size": "128x128", 39 | "idiom": "mac", 40 | "filename": "app_icon_256.png", 41 | "scale": "2x" 42 | }, 43 | { 44 | "size": "256x256", 45 | "idiom": "mac", 46 | "filename": "app_icon_256.png", 47 | "scale": "1x" 48 | }, 49 | { 50 | "size": "256x256", 51 | "idiom": "mac", 52 | "filename": "app_icon_512.png", 53 | "scale": "2x" 54 | }, 55 | { 56 | "size": "512x512", 57 | "idiom": "mac", 58 | "filename": "app_icon_512.png", 59 | "scale": "1x" 60 | }, 61 | { 62 | "size": "512x512", 63 | "idiom": "mac", 64 | "filename": "app_icon_1024.png", 65 | "scale": "2x" 66 | } 67 | ] 68 | } -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HRET-Dev/AiClient/0dbc366dc49a2cdb4087975b18af9b04a6a90af5/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HRET-Dev/AiClient/0dbc366dc49a2cdb4087975b18af9b04a6a90af5/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HRET-Dev/AiClient/0dbc366dc49a2cdb4087975b18af9b04a6a90af5/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HRET-Dev/AiClient/0dbc366dc49a2cdb4087975b18af9b04a6a90af5/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HRET-Dev/AiClient/0dbc366dc49a2cdb4087975b18af9b04a6a90af5/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HRET-Dev/AiClient/0dbc366dc49a2cdb4087975b18af9b04a6a90af5/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HRET-Dev/AiClient/0dbc366dc49a2cdb4087975b18af9b04a6a90af5/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png -------------------------------------------------------------------------------- /macos/Runner/Configs/AppInfo.xcconfig: -------------------------------------------------------------------------------- 1 | // Application-level settings for the Runner target. 2 | // 3 | // This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the 4 | // future. If not, the values below would default to using the project name when this becomes a 5 | // 'flutter create' template. 6 | 7 | // The application's name. By default this is also the title of the Flutter window. 8 | PRODUCT_NAME = AiClient 9 | 10 | // The application's bundle identifier 11 | PRODUCT_BUNDLE_IDENTIFIER = dev.hret.aiClient 12 | 13 | // The copyright displayed in application information 14 | PRODUCT_COPYRIGHT = Copyright © 2025 dev.hret. All rights reserved. 15 | -------------------------------------------------------------------------------- /macos/Runner/Configs/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Debug.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/Runner/Configs/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Release.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/Runner/Configs/Warnings.xcconfig: -------------------------------------------------------------------------------- 1 | WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings 2 | GCC_WARN_UNDECLARED_SELECTOR = YES 3 | CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES 4 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE 5 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES 6 | CLANG_WARN_PRAGMA_PACK = YES 7 | CLANG_WARN_STRICT_PROTOTYPES = YES 8 | CLANG_WARN_COMMA = YES 9 | GCC_WARN_STRICT_SELECTOR_MATCH = YES 10 | CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES 11 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES 12 | GCC_WARN_SHADOW = YES 13 | CLANG_WARN_UNREACHABLE_CODE = YES 14 | -------------------------------------------------------------------------------- /macos/Runner/DebugProfile.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.cs.allow-jit 8 | 9 | com.apple.security.network.server 10 | 11 | com.apple.security.network.client 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /macos/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSHumanReadableCopyright 26 | $(PRODUCT_COPYRIGHT) 27 | NSMainNibFile 28 | MainMenu 29 | 30 | NSAppTransportSecurity 31 | 32 | NSAllowsArbitraryLoads 33 | 34 | 35 | NSPrincipalClass 36 | NSApplication 37 | 38 | 39 | -------------------------------------------------------------------------------- /macos/Runner/MainFlutterWindow.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | class MainFlutterWindow: NSWindow { 5 | override func awakeFromNib() { 6 | let flutterViewController = FlutterViewController() 7 | let windowFrame = self.frame 8 | self.contentViewController = flutterViewController 9 | self.setFrame(windowFrame, display: true) 10 | 11 | RegisterGeneratedPlugins(registry: flutterViewController) 12 | 13 | super.awakeFromNib() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /macos/Runner/Release.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.network.server 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 | -------------------------------------------------------------------------------- /macos/packaging/dmg/make_config.yaml: -------------------------------------------------------------------------------- 1 | title: AiClient 2 | contents: 3 | - x: 448 4 | y: 344 5 | type: link 6 | path: "/Applications" 7 | - x: 192 8 | y: 344 9 | type: file 10 | path: AiClient.app -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: ai_client 2 | description: "AI客户端" 3 | 4 | version: 0.0.4 5 | publish_to: none 6 | 7 | environment: 8 | sdk: ">=3.6.0 <4.0.0" 9 | 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | flutter_localizations: 14 | sdk: flutter 15 | 16 | crypto: ^3.0.6 17 | cupertino_icons: ^1.0.8 18 | dio: ^5.8.0+1 19 | drift: ^2.25.1 20 | drift_flutter: ^0.2.4 21 | easy_localization: ^3.0.7+1 22 | easy_localization_loader: ^2.0.2 23 | flutter_highlighter: ^0.1.1 24 | flutter_launcher_icons: ^0.14.3 25 | flutter_markdown: ^0.7.6+2 26 | flutter_svg: ^2.0.17 27 | gpt_markdown: ^1.0.16 28 | http: ^1.3.0 29 | logger: ^2.5.0 30 | path: ^1.9.1 31 | path_provider: ^2.1.5 32 | provider: ^6.0.0 33 | shared_preferences: ^2.5.3 34 | window_manager: ^0.4.3 35 | bot_toast: ^4.1.3 36 | font_awesome_flutter: ^10.8.0 37 | 38 | dev_dependencies: 39 | flutter_test: 40 | sdk: flutter 41 | 42 | build_runner: ^2.4.15 43 | drift_dev: ^2.25.2 44 | flutter_lints: ^5.0.0 45 | args: ^2.7.0 46 | 47 | flutter: 48 | uses-material-design: true 49 | assets: 50 | - assets/translations/ # 添加 多语言文件目录 到资源声明 51 | - assets/assistant/ # 添加 Ai Icon 到资源声明 52 | -------------------------------------------------------------------------------- /web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HRET-Dev/AiClient/0dbc366dc49a2cdb4087975b18af9b04a6a90af5/web/favicon.png -------------------------------------------------------------------------------- /web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HRET-Dev/AiClient/0dbc366dc49a2cdb4087975b18af9b04a6a90af5/web/icons/Icon-192.png -------------------------------------------------------------------------------- /web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HRET-Dev/AiClient/0dbc366dc49a2cdb4087975b18af9b04a6a90af5/web/icons/Icon-512.png -------------------------------------------------------------------------------- /web/icons/Icon-maskable-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HRET-Dev/AiClient/0dbc366dc49a2cdb4087975b18af9b04a6a90af5/web/icons/Icon-maskable-192.png -------------------------------------------------------------------------------- /web/icons/Icon-maskable-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HRET-Dev/AiClient/0dbc366dc49a2cdb4087975b18af9b04a6a90af5/web/icons/Icon-maskable-512.png -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | AiClient 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "AiClient", 3 | "short_name": "AiClient", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#hexcode", 7 | "theme_color": "#hexcode", 8 | "description": "跨平台的AI客户端", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | }, 22 | { 23 | "src": "icons/Icon-maskable-192.png", 24 | "sizes": "192x192", 25 | "type": "image/png", 26 | "purpose": "maskable" 27 | }, 28 | { 29 | "src": "icons/Icon-maskable-512.png", 30 | "sizes": "512x512", 31 | "type": "image/png", 32 | "purpose": "maskable" 33 | } 34 | ] 35 | } -------------------------------------------------------------------------------- /web/sqlite3.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HRET-Dev/AiClient/0dbc366dc49a2cdb4087975b18af9b04a6a90af5/web/sqlite3.wasm -------------------------------------------------------------------------------- /windows/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral/ 2 | 3 | # Visual Studio user-specific files. 4 | *.suo 5 | *.user 6 | *.userosscache 7 | *.sln.docstates 8 | 9 | # Visual Studio build-related files. 10 | x64/ 11 | x86/ 12 | 13 | # Visual Studio cache files 14 | # files ending in .cache can be ignored 15 | *.[Cc]ache 16 | # but keep track of directories ending in .cache 17 | !*.[Cc]ache/ 18 | -------------------------------------------------------------------------------- /windows/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Project-level configuration. 2 | cmake_minimum_required(VERSION 3.14) 3 | project(AiClient LANGUAGES CXX) 4 | 5 | # The name of the executable created for the application. Change this to change 6 | # the on-disk name of your application. 7 | set(BINARY_NAME "AiClient") 8 | 9 | # Explicitly opt in to modern CMake behaviors to avoid warnings with recent 10 | # versions of CMake. 11 | cmake_policy(VERSION 3.14...3.25) 12 | 13 | # Define build configuration option. 14 | get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) 15 | if(IS_MULTICONFIG) 16 | set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" 17 | CACHE STRING "" FORCE) 18 | else() 19 | if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) 20 | set(CMAKE_BUILD_TYPE "Debug" CACHE 21 | STRING "Flutter build mode" FORCE) 22 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS 23 | "Debug" "Profile" "Release") 24 | endif() 25 | endif() 26 | # Define settings for the Profile build mode. 27 | set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") 28 | set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") 29 | set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") 30 | set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") 31 | 32 | # Use Unicode for all projects. 33 | add_definitions(-DUNICODE -D_UNICODE) 34 | 35 | # Compilation settings that should be applied to most targets. 36 | # 37 | # Be cautious about adding new options here, as plugins use this function by 38 | # default. In most cases, you should add new options to specific targets instead 39 | # of modifying this function. 40 | function(APPLY_STANDARD_SETTINGS TARGET) 41 | target_compile_features(${TARGET} PUBLIC cxx_std_17) 42 | target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") 43 | target_compile_options(${TARGET} PRIVATE /EHsc) 44 | target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") 45 | target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") 46 | endfunction() 47 | 48 | # Flutter library and tool build rules. 49 | set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") 50 | add_subdirectory(${FLUTTER_MANAGED_DIR}) 51 | 52 | # Application build; see runner/CMakeLists.txt. 53 | add_subdirectory("runner") 54 | 55 | 56 | # Generated plugin build rules, which manage building the plugins and adding 57 | # them to the application. 58 | include(flutter/generated_plugins.cmake) 59 | 60 | 61 | # === Installation === 62 | # Support files are copied into place next to the executable, so that it can 63 | # run in place. This is done instead of making a separate bundle (as on Linux) 64 | # so that building and running from within Visual Studio will work. 65 | set(BUILD_BUNDLE_DIR "$") 66 | # Make the "install" step default, as it's required to run. 67 | set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) 68 | if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) 69 | set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) 70 | endif() 71 | 72 | set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") 73 | set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") 74 | 75 | install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" 76 | COMPONENT Runtime) 77 | 78 | install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" 79 | COMPONENT Runtime) 80 | 81 | install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 82 | COMPONENT Runtime) 83 | 84 | if(PLUGIN_BUNDLED_LIBRARIES) 85 | install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" 86 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 87 | COMPONENT Runtime) 88 | endif() 89 | 90 | # Copy the native assets provided by the build.dart from all packages. 91 | set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/") 92 | install(DIRECTORY "${NATIVE_ASSETS_DIR}" 93 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 94 | COMPONENT Runtime) 95 | 96 | # Fully re-copy the assets directory on each build to avoid having stale files 97 | # from a previous install. 98 | set(FLUTTER_ASSET_DIR_NAME "flutter_assets") 99 | install(CODE " 100 | file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") 101 | " COMPONENT Runtime) 102 | install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" 103 | DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) 104 | 105 | # Install the AOT library on non-Debug builds only. 106 | install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" 107 | CONFIGURATIONS Profile;Release 108 | COMPONENT Runtime) 109 | -------------------------------------------------------------------------------- /windows/flutter/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file controls Flutter-level build steps. It should not be edited. 2 | cmake_minimum_required(VERSION 3.14) 3 | 4 | set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") 5 | 6 | # Configuration provided via flutter tool. 7 | include(${EPHEMERAL_DIR}/generated_config.cmake) 8 | 9 | # TODO: Move the rest of this into files in ephemeral. See 10 | # https://github.com/flutter/flutter/issues/57146. 11 | set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") 12 | 13 | # Set fallback configurations for older versions of the flutter tool. 14 | if (NOT DEFINED FLUTTER_TARGET_PLATFORM) 15 | set(FLUTTER_TARGET_PLATFORM "windows-x64") 16 | endif() 17 | 18 | # === Flutter Library === 19 | set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") 20 | 21 | # Published to parent scope for install step. 22 | set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) 23 | set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) 24 | set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) 25 | set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) 26 | 27 | list(APPEND FLUTTER_LIBRARY_HEADERS 28 | "flutter_export.h" 29 | "flutter_windows.h" 30 | "flutter_messenger.h" 31 | "flutter_plugin_registrar.h" 32 | "flutter_texture_registrar.h" 33 | ) 34 | list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") 35 | add_library(flutter INTERFACE) 36 | target_include_directories(flutter INTERFACE 37 | "${EPHEMERAL_DIR}" 38 | ) 39 | target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") 40 | add_dependencies(flutter flutter_assemble) 41 | 42 | # === Wrapper === 43 | list(APPEND CPP_WRAPPER_SOURCES_CORE 44 | "core_implementations.cc" 45 | "standard_codec.cc" 46 | ) 47 | list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") 48 | list(APPEND CPP_WRAPPER_SOURCES_PLUGIN 49 | "plugin_registrar.cc" 50 | ) 51 | list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") 52 | list(APPEND CPP_WRAPPER_SOURCES_APP 53 | "flutter_engine.cc" 54 | "flutter_view_controller.cc" 55 | ) 56 | list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") 57 | 58 | # Wrapper sources needed for a plugin. 59 | add_library(flutter_wrapper_plugin STATIC 60 | ${CPP_WRAPPER_SOURCES_CORE} 61 | ${CPP_WRAPPER_SOURCES_PLUGIN} 62 | ) 63 | apply_standard_settings(flutter_wrapper_plugin) 64 | set_target_properties(flutter_wrapper_plugin PROPERTIES 65 | POSITION_INDEPENDENT_CODE ON) 66 | set_target_properties(flutter_wrapper_plugin PROPERTIES 67 | CXX_VISIBILITY_PRESET hidden) 68 | target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) 69 | target_include_directories(flutter_wrapper_plugin PUBLIC 70 | "${WRAPPER_ROOT}/include" 71 | ) 72 | add_dependencies(flutter_wrapper_plugin flutter_assemble) 73 | 74 | # Wrapper sources needed for the runner. 75 | add_library(flutter_wrapper_app STATIC 76 | ${CPP_WRAPPER_SOURCES_CORE} 77 | ${CPP_WRAPPER_SOURCES_APP} 78 | ) 79 | apply_standard_settings(flutter_wrapper_app) 80 | target_link_libraries(flutter_wrapper_app PUBLIC flutter) 81 | target_include_directories(flutter_wrapper_app PUBLIC 82 | "${WRAPPER_ROOT}/include" 83 | ) 84 | add_dependencies(flutter_wrapper_app flutter_assemble) 85 | 86 | # === Flutter tool backend === 87 | # _phony_ is a non-existent file to force this command to run every time, 88 | # since currently there's no way to get a full input/output list from the 89 | # flutter tool. 90 | set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") 91 | set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) 92 | add_custom_command( 93 | OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} 94 | ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} 95 | ${CPP_WRAPPER_SOURCES_APP} 96 | ${PHONY_OUTPUT} 97 | COMMAND ${CMAKE_COMMAND} -E env 98 | ${FLUTTER_TOOL_ENVIRONMENT} 99 | "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" 100 | ${FLUTTER_TARGET_PLATFORM} $ 101 | VERBATIM 102 | ) 103 | add_custom_target(flutter_assemble DEPENDS 104 | "${FLUTTER_LIBRARY}" 105 | ${FLUTTER_LIBRARY_HEADERS} 106 | ${CPP_WRAPPER_SOURCES_CORE} 107 | ${CPP_WRAPPER_SOURCES_PLUGIN} 108 | ${CPP_WRAPPER_SOURCES_APP} 109 | ) 110 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | void RegisterPlugins(flutter::PluginRegistry* registry) { 15 | ConnectivityPlusWindowsPluginRegisterWithRegistrar( 16 | registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin")); 17 | ScreenRetrieverWindowsPluginCApiRegisterWithRegistrar( 18 | registry->GetRegistrarForPlugin("ScreenRetrieverWindowsPluginCApi")); 19 | Sqlite3FlutterLibsPluginRegisterWithRegistrar( 20 | registry->GetRegistrarForPlugin("Sqlite3FlutterLibsPlugin")); 21 | WindowManagerPluginRegisterWithRegistrar( 22 | registry->GetRegistrarForPlugin("WindowManagerPlugin")); 23 | } 24 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include 11 | 12 | // Registers Flutter plugins. 13 | void RegisterPlugins(flutter::PluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | connectivity_plus 7 | screen_retriever_windows 8 | sqlite3_flutter_libs 9 | window_manager 10 | ) 11 | 12 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 13 | ) 14 | 15 | set(PLUGIN_BUNDLED_LIBRARIES) 16 | 17 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 18 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) 19 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 20 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 21 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 22 | endforeach(plugin) 23 | 24 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 25 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) 26 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 27 | endforeach(ffi_plugin) 28 | -------------------------------------------------------------------------------- /windows/packaging/exe/make_config.yaml: -------------------------------------------------------------------------------- 1 | # AppId 的值唯一标识此应用。 2 | # 不要在其他应用的安装程序中使用相同的 AppId 值。 3 | app_id: E3A5D45C-F3F7-4305-B3F4-322EC4E9A264 4 | publisher: AiClient 5 | publisher_url: https://github.com/HRET-Dev/AiClient 6 | display_name: AiClient 7 | create_desktop_icon: true -------------------------------------------------------------------------------- /windows/runner/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | project(runner LANGUAGES CXX) 3 | 4 | # Define the application target. To change its name, change BINARY_NAME in the 5 | # top-level CMakeLists.txt, not the value here, or `flutter run` will no longer 6 | # work. 7 | # 8 | # Any new source files that you add to the application should be added here. 9 | add_executable(${BINARY_NAME} WIN32 10 | "flutter_window.cpp" 11 | "main.cpp" 12 | "utils.cpp" 13 | "win32_window.cpp" 14 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" 15 | "Runner.rc" 16 | "runner.exe.manifest" 17 | ) 18 | 19 | # Apply the standard set of build settings. This can be removed for applications 20 | # that need different build settings. 21 | apply_standard_settings(${BINARY_NAME}) 22 | 23 | # Add preprocessor definitions for the build version. 24 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") 25 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") 26 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") 27 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") 28 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") 29 | 30 | # Disable Windows macros that collide with C++ standard library functions. 31 | target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") 32 | 33 | # Add dependency libraries and include directories. Add any application-specific 34 | # dependencies here. 35 | target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) 36 | target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") 37 | target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") 38 | 39 | # Run the Flutter tool portions of the build. This must not be removed. 40 | add_dependencies(${BINARY_NAME} flutter_assemble) 41 | -------------------------------------------------------------------------------- /windows/runner/Runner.rc: -------------------------------------------------------------------------------- 1 | // Microsoft Visual C++ generated resource script. 2 | // 3 | #pragma code_page(65001) 4 | #include "resource.h" 5 | 6 | #define APSTUDIO_READONLY_SYMBOLS 7 | ///////////////////////////////////////////////////////////////////////////// 8 | // 9 | // Generated from the TEXTINCLUDE 2 resource. 10 | // 11 | #include "winres.h" 12 | 13 | ///////////////////////////////////////////////////////////////////////////// 14 | #undef APSTUDIO_READONLY_SYMBOLS 15 | 16 | ///////////////////////////////////////////////////////////////////////////// 17 | // English (United States) resources 18 | 19 | #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) 20 | LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US 21 | 22 | #ifdef APSTUDIO_INVOKED 23 | ///////////////////////////////////////////////////////////////////////////// 24 | // 25 | // TEXTINCLUDE 26 | // 27 | 28 | 1 TEXTINCLUDE 29 | BEGIN 30 | "resource.h\0" 31 | END 32 | 33 | 2 TEXTINCLUDE 34 | BEGIN 35 | "#include ""winres.h""\r\n" 36 | "\0" 37 | END 38 | 39 | 3 TEXTINCLUDE 40 | BEGIN 41 | "\r\n" 42 | "\0" 43 | END 44 | 45 | #endif // APSTUDIO_INVOKED 46 | 47 | 48 | ///////////////////////////////////////////////////////////////////////////// 49 | // 50 | // Icon 51 | // 52 | 53 | // Icon with lowest ID value placed first to ensure application icon 54 | // remains consistent on all systems. 55 | IDI_APP_ICON ICON "resources\\app_icon.ico" 56 | 57 | 58 | ///////////////////////////////////////////////////////////////////////////// 59 | // 60 | // Version 61 | // 62 | 63 | #if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) 64 | #define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD 65 | #else 66 | #define VERSION_AS_NUMBER 1,0,0,0 67 | #endif 68 | 69 | #if defined(FLUTTER_VERSION) 70 | #define VERSION_AS_STRING FLUTTER_VERSION 71 | #else 72 | #define VERSION_AS_STRING "1.0.0" 73 | #endif 74 | 75 | VS_VERSION_INFO VERSIONINFO 76 | FILEVERSION VERSION_AS_NUMBER 77 | PRODUCTVERSION VERSION_AS_NUMBER 78 | FILEFLAGSMASK VS_FFI_FILEFLAGSMASK 79 | #ifdef _DEBUG 80 | FILEFLAGS VS_FF_DEBUG 81 | #else 82 | FILEFLAGS 0x0L 83 | #endif 84 | FILEOS VOS__WINDOWS32 85 | FILETYPE VFT_APP 86 | FILESUBTYPE 0x0L 87 | BEGIN 88 | BLOCK "StringFileInfo" 89 | BEGIN 90 | BLOCK "040904e4" 91 | BEGIN 92 | VALUE "CompanyName", "dev.hret" "\0" 93 | VALUE "FileDescription", "AiClient" "\0" 94 | VALUE "FileVersion", VERSION_AS_STRING "\0" 95 | VALUE "InternalName", "AiClient" "\0" 96 | VALUE "LegalCopyright", "Copyright (C) 2025 dev.hret. All rights reserved." "\0" 97 | VALUE "OriginalFilename", "AiClient.exe" "\0" 98 | VALUE "ProductName", "AiClient" "\0" 99 | VALUE "ProductVersion", VERSION_AS_STRING "\0" 100 | END 101 | END 102 | BLOCK "VarFileInfo" 103 | BEGIN 104 | VALUE "Translation", 0x409, 1252 105 | END 106 | END 107 | 108 | #endif // English (United States) resources 109 | ///////////////////////////////////////////////////////////////////////////// 110 | 111 | 112 | 113 | #ifndef APSTUDIO_INVOKED 114 | ///////////////////////////////////////////////////////////////////////////// 115 | // 116 | // Generated from the TEXTINCLUDE 3 resource. 117 | // 118 | 119 | 120 | ///////////////////////////////////////////////////////////////////////////// 121 | #endif // not APSTUDIO_INVOKED 122 | -------------------------------------------------------------------------------- /windows/runner/flutter_window.cpp: -------------------------------------------------------------------------------- 1 | #include "flutter_window.h" 2 | 3 | #include 4 | 5 | #include "flutter/generated_plugin_registrant.h" 6 | 7 | FlutterWindow::FlutterWindow(const flutter::DartProject& project) 8 | : project_(project) {} 9 | 10 | FlutterWindow::~FlutterWindow() {} 11 | 12 | bool FlutterWindow::OnCreate() { 13 | if (!Win32Window::OnCreate()) { 14 | return false; 15 | } 16 | 17 | RECT frame = GetClientArea(); 18 | 19 | // The size here must match the window dimensions to avoid unnecessary surface 20 | // creation / destruction in the startup path. 21 | flutter_controller_ = std::make_unique( 22 | frame.right - frame.left, frame.bottom - frame.top, project_); 23 | // Ensure that basic setup of the controller was successful. 24 | if (!flutter_controller_->engine() || !flutter_controller_->view()) { 25 | return false; 26 | } 27 | RegisterPlugins(flutter_controller_->engine()); 28 | SetChildContent(flutter_controller_->view()->GetNativeWindow()); 29 | 30 | flutter_controller_->engine()->SetNextFrameCallback([&]() { 31 | this->Show(); 32 | }); 33 | 34 | // Flutter can complete the first frame before the "show window" callback is 35 | // registered. The following call ensures a frame is pending to ensure the 36 | // window is shown. It is a no-op if the first frame hasn't completed yet. 37 | flutter_controller_->ForceRedraw(); 38 | 39 | return true; 40 | } 41 | 42 | void FlutterWindow::OnDestroy() { 43 | if (flutter_controller_) { 44 | flutter_controller_ = nullptr; 45 | } 46 | 47 | Win32Window::OnDestroy(); 48 | } 49 | 50 | LRESULT 51 | FlutterWindow::MessageHandler(HWND hwnd, UINT const message, 52 | WPARAM const wparam, 53 | LPARAM const lparam) noexcept { 54 | // Give Flutter, including plugins, an opportunity to handle window messages. 55 | if (flutter_controller_) { 56 | std::optional result = 57 | flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, 58 | lparam); 59 | if (result) { 60 | return *result; 61 | } 62 | } 63 | 64 | switch (message) { 65 | case WM_FONTCHANGE: 66 | flutter_controller_->engine()->ReloadSystemFonts(); 67 | break; 68 | } 69 | 70 | return Win32Window::MessageHandler(hwnd, message, wparam, lparam); 71 | } 72 | -------------------------------------------------------------------------------- /windows/runner/flutter_window.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_FLUTTER_WINDOW_H_ 2 | #define RUNNER_FLUTTER_WINDOW_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "win32_window.h" 10 | 11 | // A window that does nothing but host a Flutter view. 12 | class FlutterWindow : public Win32Window { 13 | public: 14 | // Creates a new FlutterWindow hosting a Flutter view running |project|. 15 | explicit FlutterWindow(const flutter::DartProject& project); 16 | virtual ~FlutterWindow(); 17 | 18 | protected: 19 | // Win32Window: 20 | bool OnCreate() override; 21 | void OnDestroy() override; 22 | LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, 23 | LPARAM const lparam) noexcept override; 24 | 25 | private: 26 | // The project to run. 27 | flutter::DartProject project_; 28 | 29 | // The Flutter instance hosted by this window. 30 | std::unique_ptr flutter_controller_; 31 | }; 32 | 33 | #endif // RUNNER_FLUTTER_WINDOW_H_ 34 | -------------------------------------------------------------------------------- /windows/runner/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "flutter_window.h" 6 | #include "utils.h" 7 | 8 | int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, 9 | _In_ wchar_t *command_line, _In_ int show_command) { 10 | // Attach to console when present (e.g., 'flutter run') or create a 11 | // new console when running with a debugger. 12 | if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { 13 | CreateAndAttachConsole(); 14 | } 15 | 16 | // Initialize COM, so that it is available for use in the library and/or 17 | // plugins. 18 | ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); 19 | 20 | flutter::DartProject project(L"data"); 21 | 22 | std::vector command_line_arguments = 23 | GetCommandLineArguments(); 24 | 25 | project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); 26 | 27 | FlutterWindow window(project); 28 | Win32Window::Point origin(10, 10); 29 | Win32Window::Size size(1280, 720); 30 | if (!window.Create(L"AiClient", origin, size)) { 31 | return EXIT_FAILURE; 32 | } 33 | window.SetQuitOnClose(true); 34 | 35 | ::MSG msg; 36 | while (::GetMessage(&msg, nullptr, 0, 0)) { 37 | ::TranslateMessage(&msg); 38 | ::DispatchMessage(&msg); 39 | } 40 | 41 | ::CoUninitialize(); 42 | return EXIT_SUCCESS; 43 | } 44 | -------------------------------------------------------------------------------- /windows/runner/resource.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Visual C++ generated include file. 3 | // Used by Runner.rc 4 | // 5 | #define IDI_APP_ICON 101 6 | 7 | // Next default values for new objects 8 | // 9 | #ifdef APSTUDIO_INVOKED 10 | #ifndef APSTUDIO_READONLY_SYMBOLS 11 | #define _APS_NEXT_RESOURCE_VALUE 102 12 | #define _APS_NEXT_COMMAND_VALUE 40001 13 | #define _APS_NEXT_CONTROL_VALUE 1001 14 | #define _APS_NEXT_SYMED_VALUE 101 15 | #endif 16 | #endif 17 | -------------------------------------------------------------------------------- /windows/runner/resources/app_icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HRET-Dev/AiClient/0dbc366dc49a2cdb4087975b18af9b04a6a90af5/windows/runner/resources/app_icon.ico -------------------------------------------------------------------------------- /windows/runner/runner.exe.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PerMonitorV2 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /windows/runner/utils.cpp: -------------------------------------------------------------------------------- 1 | #include "utils.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | void CreateAndAttachConsole() { 11 | if (::AllocConsole()) { 12 | FILE *unused; 13 | if (freopen_s(&unused, "CONOUT$", "w", stdout)) { 14 | _dup2(_fileno(stdout), 1); 15 | } 16 | if (freopen_s(&unused, "CONOUT$", "w", stderr)) { 17 | _dup2(_fileno(stdout), 2); 18 | } 19 | std::ios::sync_with_stdio(); 20 | FlutterDesktopResyncOutputStreams(); 21 | } 22 | } 23 | 24 | std::vector GetCommandLineArguments() { 25 | // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. 26 | int argc; 27 | wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); 28 | if (argv == nullptr) { 29 | return std::vector(); 30 | } 31 | 32 | std::vector command_line_arguments; 33 | 34 | // Skip the first argument as it's the binary name. 35 | for (int i = 1; i < argc; i++) { 36 | command_line_arguments.push_back(Utf8FromUtf16(argv[i])); 37 | } 38 | 39 | ::LocalFree(argv); 40 | 41 | return command_line_arguments; 42 | } 43 | 44 | std::string Utf8FromUtf16(const wchar_t* utf16_string) { 45 | if (utf16_string == nullptr) { 46 | return std::string(); 47 | } 48 | unsigned int target_length = ::WideCharToMultiByte( 49 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 50 | -1, nullptr, 0, nullptr, nullptr) 51 | -1; // remove the trailing null character 52 | int input_length = (int)wcslen(utf16_string); 53 | std::string utf8_string; 54 | if (target_length == 0 || target_length > utf8_string.max_size()) { 55 | return utf8_string; 56 | } 57 | utf8_string.resize(target_length); 58 | int converted_length = ::WideCharToMultiByte( 59 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 60 | input_length, utf8_string.data(), target_length, nullptr, nullptr); 61 | if (converted_length == 0) { 62 | return std::string(); 63 | } 64 | return utf8_string; 65 | } 66 | -------------------------------------------------------------------------------- /windows/runner/utils.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_UTILS_H_ 2 | #define RUNNER_UTILS_H_ 3 | 4 | #include 5 | #include 6 | 7 | // Creates a console for the process, and redirects stdout and stderr to 8 | // it for both the runner and the Flutter library. 9 | void CreateAndAttachConsole(); 10 | 11 | // Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string 12 | // encoded in UTF-8. Returns an empty std::string on failure. 13 | std::string Utf8FromUtf16(const wchar_t* utf16_string); 14 | 15 | // Gets the command line arguments passed in as a std::vector, 16 | // encoded in UTF-8. Returns an empty std::vector on failure. 17 | std::vector GetCommandLineArguments(); 18 | 19 | #endif // RUNNER_UTILS_H_ 20 | -------------------------------------------------------------------------------- /windows/runner/win32_window.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_WIN32_WINDOW_H_ 2 | #define RUNNER_WIN32_WINDOW_H_ 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | // A class abstraction for a high DPI-aware Win32 Window. Intended to be 11 | // inherited from by classes that wish to specialize with custom 12 | // rendering and input handling 13 | class Win32Window { 14 | public: 15 | struct Point { 16 | unsigned int x; 17 | unsigned int y; 18 | Point(unsigned int x, unsigned int y) : x(x), y(y) {} 19 | }; 20 | 21 | struct Size { 22 | unsigned int width; 23 | unsigned int height; 24 | Size(unsigned int width, unsigned int height) 25 | : width(width), height(height) {} 26 | }; 27 | 28 | Win32Window(); 29 | virtual ~Win32Window(); 30 | 31 | // Creates a win32 window with |title| that is positioned and sized using 32 | // |origin| and |size|. New windows are created on the default monitor. Window 33 | // sizes are specified to the OS in physical pixels, hence to ensure a 34 | // consistent size this function will scale the inputted width and height as 35 | // as appropriate for the default monitor. The window is invisible until 36 | // |Show| is called. Returns true if the window was created successfully. 37 | bool Create(const std::wstring& title, const Point& origin, const Size& size); 38 | 39 | // Show the current window. Returns true if the window was successfully shown. 40 | bool Show(); 41 | 42 | // Release OS resources associated with window. 43 | void Destroy(); 44 | 45 | // Inserts |content| into the window tree. 46 | void SetChildContent(HWND content); 47 | 48 | // Returns the backing Window handle to enable clients to set icon and other 49 | // window properties. Returns nullptr if the window has been destroyed. 50 | HWND GetHandle(); 51 | 52 | // If true, closing this window will quit the application. 53 | void SetQuitOnClose(bool quit_on_close); 54 | 55 | // Return a RECT representing the bounds of the current client area. 56 | RECT GetClientArea(); 57 | 58 | protected: 59 | // Processes and route salient window messages for mouse handling, 60 | // size change and DPI. Delegates handling of these to member overloads that 61 | // inheriting classes can handle. 62 | virtual LRESULT MessageHandler(HWND window, 63 | UINT const message, 64 | WPARAM const wparam, 65 | LPARAM const lparam) noexcept; 66 | 67 | // Called when CreateAndShow is called, allowing subclass window-related 68 | // setup. Subclasses should return false if setup fails. 69 | virtual bool OnCreate(); 70 | 71 | // Called when Destroy is called. 72 | virtual void OnDestroy(); 73 | 74 | private: 75 | friend class WindowClassRegistrar; 76 | 77 | // OS callback called by message pump. Handles the WM_NCCREATE message which 78 | // is passed when the non-client area is being created and enables automatic 79 | // non-client DPI scaling so that the non-client area automatically 80 | // responds to changes in DPI. All other messages are handled by 81 | // MessageHandler. 82 | static LRESULT CALLBACK WndProc(HWND const window, 83 | UINT const message, 84 | WPARAM const wparam, 85 | LPARAM const lparam) noexcept; 86 | 87 | // Retrieves a class instance pointer for |window| 88 | static Win32Window* GetThisFromHandle(HWND const window) noexcept; 89 | 90 | // Update the window frame's theme to match the system theme. 91 | static void UpdateTheme(HWND const window); 92 | 93 | bool quit_on_close_ = false; 94 | 95 | // window handle for top level window. 96 | HWND window_handle_ = nullptr; 97 | 98 | // window handle for hosted content. 99 | HWND child_content_ = nullptr; 100 | }; 101 | 102 | #endif // RUNNER_WIN32_WINDOW_H_ 103 | --------------------------------------------------------------------------------