├── .gitignore
├── .metadata
├── CHANGELOG.md
├── README.md
├── _cus_model_jsons
├── model_无问芯穹(少量自选).json
├── model_智谱.json
├── model_深度求索.json
├── model_百度(免费模型).json
├── model_硅基流动(少量自选).json
├── model_腾讯(免费模型).json
├── model_阿里云(少量自选).json
├── model_零一万物.json
└── 平台ApiKey示例.json
├── _dishes
└── HowToCook
│ ├── HowToCook-All合并-持续更新-20240711.json
│ ├── HowToCook-主食-20240708done.json
│ ├── HowToCook-早餐-20240701done.json
│ ├── HowToCook-水产-20240711done.json
│ ├── HowToCook-汤粥-20240626done.json
│ ├── HowToCook-甜品-20240701done.json
│ ├── HowToCook-素菜-20240328done.json
│ ├── HowToCook-荤菜-20240703done.json
│ └── HowToCook-饮料-20240701done.json
├── _doc
├── changelog_pics
│ ├── 0.2.0-beta.1新增内容截图.png
│ └── 0.3.0-beta.1新增内容截图.jpg
├── screenshots
│ ├── 1-1智能对话1.jpg
│ ├── 1-2智能对话2.jpg
│ ├── 2智能多聊.jpg
│ ├── 3文档解读和图片解读.jpg
│ ├── 4文本生图-文生视频.jpg
│ ├── 5创意文字.jpg
│ ├── 6图片生图.jpg
│ ├── 7支持的角色管理模型列表,使用自己的密钥.jpg
│ ├── 8极简记账.jpg
│ ├── 9-1随机菜品1.jpg
│ ├── 9-2随机菜品2.jpg
│ └── brief_version
│ │ ├── 助手工具.jpg
│ │ ├── 对话页面.jpg
│ │ ├── 导入配置等.jpg
│ │ ├── 生活工具.jpg
│ │ └── 角色扮演.jpg
└── suchat_snapshots
│ ├── snapshot.jpg
│ └── suchat_chat_page.jpg
├── analysis_options.yaml
├── android
├── .gitignore
├── app
│ ├── build.gradle
│ ├── proguard-rules.pro
│ └── src
│ │ ├── debug
│ │ └── AndroidManifest.xml
│ │ ├── main
│ │ ├── AndroidManifest.xml
│ │ ├── kotlin
│ │ │ └── com
│ │ │ │ └── swm
│ │ │ │ └── swmate
│ │ │ │ └── MainActivity.kt
│ │ └── res
│ │ │ ├── drawable-v21
│ │ │ └── launch_background.xml
│ │ │ ├── drawable
│ │ │ └── launch_background.xml
│ │ │ ├── mipmap-hdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-mdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xhdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── values-night
│ │ │ └── styles.xml
│ │ │ └── values
│ │ │ └── styles.xml
│ │ └── profile
│ │ └── AndroidManifest.xml
├── build.gradle
├── gradle.properties
├── gradle
│ └── wrapper
│ │ └── gradle-wrapper.properties
└── settings.gradle
├── assets
├── brand.png
├── characters
│ └── default_avatar.png
├── chat_backgrounds
│ ├── bg1.jpg
│ └── bg2.jpg
└── images
│ └── no_image.png
├── devtools_options.yaml
├── lib
├── apis
│ ├── chat_completion
│ │ ├── chat_helper.dart
│ │ ├── common_cc_apis.dart
│ │ └── openai_compatible_apis.dart
│ ├── gen_access_token
│ │ └── xfyun_signature.dart
│ ├── get_app_key_helper.dart
│ ├── life_tools
│ │ ├── animal_lover
│ │ │ ├── dogceo_apis.dart
│ │ │ ├── random_fact_apis.dart
│ │ │ └── thatapi_apis.dart
│ │ ├── bangumi
│ │ │ └── bangumi_apis.dart
│ │ ├── food
│ │ │ ├── nutritionix
│ │ │ │ ├── nix_nutrient_enum_list.dart
│ │ │ │ └── nutritionix_apis.dart
│ │ │ └── usda_food_data_central
│ │ │ │ └── usda_food_data_apis.dart
│ │ ├── free_dictionary
│ │ │ └── free_dictionary_apis.dart
│ │ ├── hitokoto
│ │ │ └── hitokoto_apis.dart
│ │ ├── jikan
│ │ │ └── get_jikan_apis.dart
│ │ ├── news
│ │ │ ├── momoyu_apis.dart
│ │ │ ├── newsapi_apis.dart
│ │ │ ├── readhub_apis.dart
│ │ │ └── sina_roll_news_apis.dart
│ │ └── waifu_pic
│ │ │ └── index.dart
│ └── voice_recognition
│ │ └── xunfei_apis.dart
├── common
│ ├── components
│ │ ├── advanced_options_bottom_sheet.dart
│ │ ├── advanced_options_panel.dart
│ │ ├── bar_chart_widget.dart
│ │ ├── chat_list_area.dart
│ │ ├── cus_cards.dart
│ │ ├── cus_code_field.dart
│ │ ├── cus_markdown_renderer.dart
│ │ ├── cus_platform_and_llm_row.dart
│ │ ├── cus_toggle_button_selector.dart
│ │ ├── custom_entrance_card.dart
│ │ ├── feature_grid_card.dart
│ │ ├── image_pick_and_preview_area.dart
│ │ ├── loading_overlay.dart
│ │ ├── message_item.dart
│ │ ├── modern_feature_card.dart
│ │ ├── optimized_custom_markdown_renderer.dart
│ │ ├── searchable_dropdown.dart
│ │ ├── simple_marquee_or_text.dart
│ │ ├── sounds_message_button
│ │ │ ├── button_widget
│ │ │ │ ├── custom_canvas.dart
│ │ │ │ ├── recording_status_mask.dart
│ │ │ │ └── sounds_message_button.dart
│ │ │ └── utils
│ │ │ │ ├── recording_mask_overlay_data.dart
│ │ │ │ └── sounds_recorder_controller.dart
│ │ ├── style_grid_selector.dart
│ │ ├── tool_widget.dart
│ │ └── voice_chat_bubble.dart
│ ├── constants
│ │ ├── advanced_options_presets.dart
│ │ ├── constants.dart
│ │ ├── default_image_generation_models.dart
│ │ ├── default_models.dart
│ │ ├── default_video_generation_models.dart
│ │ └── inner_system_prompt.dart
│ ├── llm_spec
│ │ ├── constant_llm_enum.dart
│ │ ├── cus_brief_llm_model.dart
│ │ └── cus_brief_llm_model.g.dart
│ └── utils
│ │ ├── advanced_options_utils.dart
│ │ ├── cus_logger.dart
│ │ ├── db_tools
│ │ ├── db_brief_ai_tool_helper.dart
│ │ ├── db_life_tool_helper.dart
│ │ ├── ddl_brief_ai_tool.dart
│ │ ├── ddl_life_tool.dart
│ │ └── init_db.dart
│ │ ├── dio_client
│ │ ├── cus_http_client.dart
│ │ ├── cus_http_options.dart
│ │ ├── cus_http_request.dart
│ │ ├── intercepter_response.dart
│ │ ├── interceptor_error.dart
│ │ └── interceptor_request.dart
│ │ ├── document_parser.dart
│ │ ├── handle_cc_response.dart
│ │ └── tools.dart
├── main.dart
├── models
│ ├── brief_ai_tools
│ │ ├── branch_chat
│ │ │ ├── branch_chat_export_data.dart
│ │ │ ├── branch_chat_export_data.g.dart
│ │ │ ├── branch_chat_message.dart
│ │ │ ├── branch_chat_session.dart
│ │ │ ├── branch_manager.dart
│ │ │ └── branch_store.dart
│ │ ├── character_chat
│ │ │ ├── character_card.dart
│ │ │ ├── character_chat_message.dart
│ │ │ ├── character_chat_session.dart
│ │ │ └── character_store.dart
│ │ ├── chat_competion
│ │ │ ├── com_cc_req.dart
│ │ │ ├── com_cc_req.g.dart
│ │ │ ├── com_cc_resp.dart
│ │ │ ├── com_cc_resp.g.dart
│ │ │ ├── com_cc_state.dart
│ │ │ └── com_cc_state.g.dart
│ │ ├── chat_completions
│ │ │ ├── chat_completion_request.dart
│ │ │ ├── chat_completion_response.dart
│ │ │ ├── chat_completion_response.g.dart
│ │ │ └── chat_completion_tool.dart
│ │ ├── image_generation
│ │ │ ├── image_generation_request.dart
│ │ │ ├── image_generation_request.g.dart
│ │ │ ├── image_generation_response.dart
│ │ │ └── image_generation_response.g.dart
│ │ ├── media_generation_history
│ │ │ ├── media_generation_history.dart
│ │ │ └── media_generation_history.g.dart
│ │ ├── video_generation
│ │ │ ├── video_generation_request.dart
│ │ │ ├── video_generation_request.g.dart
│ │ │ ├── video_generation_response.dart
│ │ │ └── video_generation_response.g.dart
│ │ └── voice_recognition
│ │ │ ├── xunfei_voice_dictation.dart
│ │ │ └── xunfei_voice_dictation.g.dart
│ ├── life_tools
│ │ ├── animal_lover
│ │ │ ├── the_dog_cat_api_breed.dart
│ │ │ ├── the_dog_cat_api_breed.g.dart
│ │ │ ├── the_dog_cat_api_image.dart
│ │ │ └── the_dog_cat_api_image.g.dart
│ │ ├── bangumi
│ │ │ ├── bangumi.dart
│ │ │ └── bangumi.g.dart
│ │ ├── brief_accounting_state.dart
│ │ ├── dish_state.dart
│ │ ├── dog_lover
│ │ │ ├── dog_ceo_resp.dart
│ │ │ └── dog_ceo_resp.g.dart
│ │ ├── food
│ │ │ ├── nutritionix
│ │ │ │ ├── nix_natural_exercise_resp.dart
│ │ │ │ ├── nix_natural_exercise_resp.g.dart
│ │ │ │ ├── nix_natural_nutrient_resp.dart
│ │ │ │ ├── nix_natural_nutrient_resp.g.dart
│ │ │ │ ├── nix_search_instant_resp.dart
│ │ │ │ └── nix_search_instant_resp.g.dart
│ │ │ └── usda_food_data
│ │ │ │ ├── usda_food_item.dart
│ │ │ │ ├── usda_food_item.g.dart
│ │ │ │ ├── usda_food_search_resp.dart
│ │ │ │ └── usda_food_search_resp.g.dart
│ │ ├── free_dictionary
│ │ │ ├── free_dictionary_resp.dart
│ │ │ └── free_dictionary_resp.g.dart
│ │ ├── hitokoto
│ │ │ ├── hitokoto.dart
│ │ │ └── hitokoto.g.dart
│ │ ├── jikan
│ │ │ ├── jikan_data.dart
│ │ │ ├── jikan_data.g.dart
│ │ │ ├── jikan_related_character_resp.dart
│ │ │ ├── jikan_related_character_resp.g.dart
│ │ │ ├── jikan_statistic.dart
│ │ │ └── jikan_statistic.g.dart
│ │ └── news
│ │ │ ├── momoyu_info_resp.dart
│ │ │ ├── momoyu_info_resp.g.dart
│ │ │ ├── news_api_resp.dart
│ │ │ ├── news_api_resp.g.dart
│ │ │ ├── readhub_hot_topic_resp.dart
│ │ │ ├── readhub_hot_topic_resp.g.dart
│ │ │ ├── sina_roll_news_resp.dart
│ │ │ └── sina_roll_news_resp.g.dart
│ └── mapper_utils.dart
├── objectbox-model.json
├── objectbox.g.dart
├── services
│ ├── chat_service.dart
│ ├── cus_get_storage.dart
│ ├── image_generation_service.dart
│ ├── model_manager_service.dart
│ ├── network_service.dart
│ └── video_generation_service.dart
└── views
│ ├── brief_ai_assistant
│ ├── _chat_components
│ │ ├── _small_tool_widgets.dart
│ │ ├── _small_tools.dart
│ │ ├── chat_input_bar.dart
│ │ ├── model_filter.dart
│ │ ├── model_selector.dart
│ │ └── text_selection_dialog.dart
│ ├── _chat_pages
│ │ ├── chat_background_picker_page.dart
│ │ └── chat_export_import_page.dart
│ ├── branch_chat
│ │ ├── branch_chat_page.dart
│ │ ├── components
│ │ │ ├── branch_chat_history_drawer.dart
│ │ │ ├── branch_message_actions.dart
│ │ │ ├── branch_message_item.dart
│ │ │ └── branch_tree_dialog.dart
│ │ └── pages
│ │ │ └── add_model_page.dart
│ ├── character_chat
│ │ ├── character_chat_page.dart
│ │ ├── character_editor_page.dart
│ │ ├── character_list_page.dart
│ │ └── components
│ │ │ ├── character_avatar_preview.dart
│ │ │ ├── character_card_item.dart
│ │ │ ├── character_chat_history_drawer.dart
│ │ │ ├── character_message_item.dart
│ │ │ └── model_selector_dialog.dart
│ ├── chat
│ │ ├── components
│ │ │ ├── chat_history_drawer.dart
│ │ │ ├── chat_input.dart
│ │ │ ├── chat_message_item.dart
│ │ │ └── message_actions.dart
│ │ └── index.dart
│ ├── common
│ │ ├── media_generation_base.dart
│ │ ├── media_manager_base.dart
│ │ ├── media_preview_base.dart
│ │ ├── mime_media_manager_base.dart
│ │ ├── mime_media_preview_base.dart
│ │ └── show_media_info_dialog.dart
│ ├── image
│ │ ├── image_manager.dart
│ │ ├── image_preview.dart
│ │ ├── index.dart
│ │ ├── mime_image_manager.dart
│ │ ├── mime_image_preview.dart
│ │ └── show_media_info_dialog.dart
│ ├── index.dart
│ ├── model_config
│ │ ├── components
│ │ │ ├── api_key_config.dart
│ │ │ └── model_list.dart
│ │ └── index.dart
│ └── video
│ │ ├── index.dart
│ │ ├── mime_video_manager.dart
│ │ ├── mime_video_preview.dart
│ │ ├── video_manager.dart
│ │ ├── video_player_screen.dart
│ │ └── video_preview.dart
│ ├── home.dart
│ ├── life_tools
│ ├── accounting
│ │ ├── bill_item_modify
│ │ │ └── index.dart
│ │ ├── bill_report
│ │ │ └── index.dart
│ │ └── index.dart
│ ├── animal_lover
│ │ ├── animal_system_prompt.dart
│ │ └── dog_cat_index.dart
│ ├── anime_top
│ │ ├── _components.dart
│ │ ├── bangumi_calendar.dart
│ │ ├── bangumi_item_detail.dart
│ │ ├── bangumi_item_episode_detail.dart
│ │ ├── mal_anime_schedule.dart
│ │ ├── mal_item_detail.dart
│ │ └── mal_top_index.dart
│ ├── food
│ │ ├── nutritionix
│ │ │ ├── index.dart
│ │ │ ├── nix_food_item_nutrients_page.dart
│ │ │ └── nix_natural_language_query.dart
│ │ ├── nutritionix_calculator
│ │ │ ├── index.dart
│ │ │ └── two_step_index.dart
│ │ └── usda_food_data
│ │ │ ├── food_item_nutrients.dart
│ │ │ └── index.dart
│ ├── free_dictionary
│ │ └── index.dart
│ ├── index.dart
│ ├── news
│ │ ├── _components
│ │ │ ├── cus_news_card.dart
│ │ │ ├── cus_scrollable_category_list.dart
│ │ │ └── news_item_container.dart
│ │ ├── base_news_page
│ │ │ ├── base_news_page_state.dart
│ │ │ ├── newsapi_page.dart
│ │ │ └── sina_roll_news_page.dart
│ │ ├── daily_60s
│ │ │ └── index.dart
│ │ ├── momoyu
│ │ │ ├── index.dart
│ │ │ └── list.dart
│ │ └── readhub
│ │ │ └── index.dart
│ ├── random_dish
│ │ ├── dish_detail.dart
│ │ ├── dish_json_import.dart
│ │ ├── dish_list.dart
│ │ ├── dish_modify.dart
│ │ └── dish_wheel_index.dart
│ └── waifu_pics
│ │ └── index.dart
│ └── user_and_settings
│ ├── backup_and_restore
│ └── index.dart
│ └── index.dart
├── pubspec.yaml
└── test
└── widget_test.dart
/.gitignore:
--------------------------------------------------------------------------------
1 | # Do not remove or rename entries in this file, only add new ones
2 | # See https://github.com/flutter/flutter/issues/128635 for more context.
3 |
4 | # Miscellaneous
5 | *.class
6 | *.lock
7 | *.log
8 | *.pyc
9 | *.swp
10 | .DS_Store
11 | .atom/
12 | .buildlog/
13 | .history
14 | .svn/
15 |
16 | # IntelliJ related
17 | *.iml
18 | *.ipr
19 | *.iws
20 | .idea/
21 |
22 | # Visual Studio Code related
23 | .classpath
24 | .project
25 | .settings/
26 | .vscode/*
27 |
28 | # Flutter repo-specific
29 | /bin/cache/
30 | /bin/internal/bootstrap.bat
31 | /bin/internal/bootstrap.sh
32 | /bin/mingit/
33 | /dev/benchmarks/mega_gallery/
34 | /dev/bots/.recipe_deps
35 | /dev/bots/android_tools/
36 | /dev/devicelab/ABresults*.json
37 | /dev/docs/doc/
38 | /dev/docs/api_docs.zip
39 | /dev/docs/flutter.docs.zip
40 | /dev/docs/lib/
41 | /dev/docs/pubspec.yaml
42 | /dev/integration_tests/**/xcuserdata
43 | /dev/integration_tests/**/Pods
44 | /packages/flutter/coverage/
45 | version
46 | analysis_benchmark.json
47 |
48 | # packages file containing multi-root paths
49 | .packages.generated
50 |
51 | # Flutter/Dart/Pub related
52 | **/doc/api/
53 | .dart_tool/
54 | .flutter-plugins
55 | .flutter-plugins-dependencies
56 | **/generated_plugin_registrant.dart
57 | .packages
58 | .pub-preload-cache/
59 | .pub-cache/
60 | .pub/
61 | build/
62 | flutter_*.png
63 | linked_*.ds
64 | unlinked.ds
65 | unlinked_spec.ds
66 |
67 | # Android related
68 | **/android/**/gradle-wrapper.jar
69 | .gradle/
70 | **/android/captures/
71 | **/android/gradlew
72 | **/android/gradlew.bat
73 | **/android/local.properties
74 | **/android/**/GeneratedPluginRegistrant.java
75 | **/android/key.properties
76 | *.jks
77 |
78 | # iOS/XCode related
79 | **/ios/**/*.mode1v3
80 | **/ios/**/*.mode2v3
81 | **/ios/**/*.moved-aside
82 | **/ios/**/*.pbxuser
83 | **/ios/**/*.perspectivev3
84 | **/ios/**/*sync/
85 | **/ios/**/.sconsign.dblite
86 | **/ios/**/.tags*
87 | **/ios/**/.vagrant/
88 | **/ios/**/DerivedData/
89 | **/ios/**/Icon?
90 | **/ios/**/Pods/
91 | **/ios/**/.symlinks/
92 | **/ios/**/profile
93 | **/ios/**/xcuserdata
94 | **/ios/.generated/
95 | **/ios/Flutter/.last_build_id
96 | **/ios/Flutter/App.framework
97 | **/ios/Flutter/Flutter.framework
98 | **/ios/Flutter/Flutter.podspec
99 | **/ios/Flutter/Generated.xcconfig
100 | **/ios/Flutter/ephemeral
101 | **/ios/Flutter/app.flx
102 | **/ios/Flutter/app.zip
103 | **/ios/Flutter/flutter_assets/
104 | **/ios/Flutter/flutter_export_environment.sh
105 | **/ios/ServiceDefinitions.json
106 | **/ios/Runner/GeneratedPluginRegistrant.*
107 |
108 | # macOS
109 | **/Flutter/ephemeral/
110 | **/Pods/
111 | **/macos/Flutter/GeneratedPluginRegistrant.swift
112 | **/macos/Flutter/ephemeral
113 | **/xcuserdata/
114 |
115 | # Windows
116 | **/windows/flutter/generated_plugin_registrant.cc
117 | **/windows/flutter/generated_plugin_registrant.h
118 | **/windows/flutter/generated_plugins.cmake
119 |
120 | # Linux
121 | **/linux/flutter/generated_plugin_registrant.cc
122 | **/linux/flutter/generated_plugin_registrant.h
123 | **/linux/flutter/generated_plugins.cmake
124 |
125 | # Coverage
126 | coverage/
127 |
128 | # Symbols
129 | app.*.symbols
130 |
131 | # Exceptions to above rules.
132 | !**/ios/**/default.mode1v3
133 | !**/ios/**/default.mode2v3
134 | !**/ios/**/default.pbxuser
135 | !**/ios/**/default.perspectivev3
136 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
137 | !/dev/ci/**/Gemfile.lock
138 | !.vscode/settings.json
139 |
140 | ## 上面的内容来自:https://github.com/flutter/flutter/blob/master/.gitignore
141 |
142 | # 下面的内容是自己测试的
143 |
144 | # 暂时不关注ios、web、windows、mocos、linux等的构建
145 | ios
146 | web
147 | windows
148 | macos
149 | linux
150 |
151 | # 统计行插件自己生成的文件夹
152 | .VSCodeCounter
153 |
154 | # 一些本地测试的组件页面等
155 | **/_self*
156 | **/*mock*
157 | **/*bak*
158 | **/*demo*
159 | **/*bugreport*
160 | .aider*
161 |
162 | # 2025-03-21 升级后运行时生成的文件夹,应该没用
163 | android/app/.cxx/
--------------------------------------------------------------------------------
/.metadata:
--------------------------------------------------------------------------------
1 | # This file tracks properties of this Flutter project.
2 | # Used by Flutter tool to assess capabilities and perform upgrades etc.
3 | #
4 | # This file should be version controlled and should not be manually edited.
5 |
6 | version:
7 | revision: "761747bfc538b5af34aa0d3fac380f1bc331ec49"
8 | channel: "stable"
9 |
10 | project_type: app
11 |
12 | # Tracks metadata for the flutter migrate command
13 | migration:
14 | platforms:
15 | - platform: root
16 | create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
17 | base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
18 | - platform: android
19 | create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
20 | base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
21 | - platform: ios
22 | create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
23 | base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
24 | - platform: linux
25 | create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
26 | base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
27 | - platform: macos
28 | create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
29 | base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
30 | - platform: web
31 | create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
32 | base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
33 | - platform: windows
34 | create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
35 | base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
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 |
--------------------------------------------------------------------------------
/_cus_model_jsons/model_无问芯穹(少量自选).json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "platform": "infini",
4 | "model": "deepseek-r1",
5 | "modelType": "cc",
6 | "name": "deepseek-r1",
7 | "contextLength": 64000,
8 | "isFree": false,
9 | "inputPrice": 4,
10 | "outputPrice": 16,
11 | "gmtRelease": "2025-02-06"
12 | },
13 | {
14 | "platform": "infini",
15 | "model": "deepseek-v3",
16 | "modelType": "cc",
17 | "name": "deepseek-v3",
18 | "contextLength": 64000,
19 | "isFree": false,
20 | "inputPrice": 2,
21 | "outputPrice": 8,
22 | "gmtRelease": "2025-02-07"
23 | },
24 | {
25 | "platform": "infini",
26 | "model": "deepseek-r1-distill-qwen-32b",
27 | "modelType": "cc",
28 | "name": "deepseek-r1-distill-qwen-32b",
29 | "contextLength": 32000,
30 | "isFree": false,
31 | "inputPrice": 0,
32 | "outputPrice": 0,
33 | "gmtRelease": "2025-01-21"
34 | },
35 | {
36 | "platform": "infini",
37 | "model": "qwq-32b-preview",
38 | "modelType": "cc",
39 | "name": "qwq-32b-preview",
40 | "contextLength": 32,
41 | "isFree": false,
42 | "inputPrice": 0,
43 | "outputPrice": 0,
44 | "gmtRelease": "2024-12-02"
45 | },
46 | {
47 | "platform": "infini",
48 | "model": "qwen2.5-72b-instruct",
49 | "modelType": "cc",
50 | "name": "qwen2.5-72b-instruct",
51 | "contextLength": 32000,
52 | "isFree": false,
53 | "inputPrice": 0,
54 | "outputPrice": 0,
55 | "gmtRelease": "2024-09-19"
56 | },
57 | {
58 | "platform": "infini",
59 | "model": "qwen2.5-32b-instruct",
60 | "modelType": "cc",
61 | "name": "qwen2.5-32b-instruct",
62 | "contextLength": 32000,
63 | "isFree": false,
64 | "inputPrice": 0,
65 | "outputPrice": 0,
66 | "gmtRelease": "2024-09-20"
67 | },
68 | {
69 | "platform": "infini",
70 | "model": "qwen2.5-14b-instruct",
71 | "modelType": "cc",
72 | "name": "qwen2.5-14b-instruct",
73 | "contextLength": 32000,
74 | "isFree": false,
75 | "inputPrice": 0,
76 | "outputPrice": 0,
77 | "gmtRelease": "2024-09-20"
78 | },
79 | {
80 | "platform": "infini",
81 | "model": "qwen2-72b-instruct",
82 | "modelType": "cc",
83 | "name": "qwen2-72b-instruct",
84 | "contextLength": 32000,
85 | "isFree": false,
86 | "inputPrice": 0,
87 | "outputPrice": 0,
88 | "gmtRelease": "2024-06-12"
89 | }
90 | ]
91 |
--------------------------------------------------------------------------------
/_cus_model_jsons/model_智谱.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "platform": "zhipu",
4 | "model": "glm-4-plus",
5 | "modelType": "cc",
6 | "name": "GLM-4-Plus",
7 | "contextLength": 128000,
8 | "isFree": false,
9 | "inputPrice": 50,
10 | "outputPrice": 50,
11 | "gmtRelease": "1970-01-01"
12 | },
13 | {
14 | "platform": "zhipu",
15 | "model": "glm-4-air",
16 | "modelType": "cc",
17 | "name": "GLM-4-Air",
18 | "contextLength": 128000,
19 | "isFree": false,
20 | "inputPrice": 0.5,
21 | "outputPrice": 0.5,
22 | "gmtRelease": "1970-01-01"
23 | },
24 | {
25 | "platform": "zhipu",
26 | "model": "glm-4-long",
27 | "modelType": "cc",
28 | "name": "GLM-4-Long",
29 | "contextLength": 1000000,
30 | "isFree": false,
31 | "inputPrice": 1,
32 | "outputPrice": 1,
33 | "gmtRelease": "1970-01-01"
34 | },
35 | {
36 | "platform": "zhipu",
37 | "model": "glm-zero-preview",
38 | "modelType": "cc",
39 | "name": "GLM-Zero-Preview",
40 | "contextLength": 16000,
41 | "isFree": false,
42 | "inputPrice": 10,
43 | "outputPrice": 10,
44 | "gmtRelease": "1970-01-01"
45 | },
46 | {
47 | "platform": "zhipu",
48 | "model": "glm-4-airx",
49 | "modelType": "cc",
50 | "name": "GLM-4-AirX",
51 | "contextLength": 8000,
52 | "isFree": false,
53 | "inputPrice": 10,
54 | "outputPrice": 10,
55 | "gmtRelease": "1970-01-01"
56 | },
57 | {
58 | "platform": "zhipu",
59 | "model": "glm-4-flashx",
60 | "modelType": "cc",
61 | "name": "GLM-4-FlashX",
62 | "contextLength": 128000,
63 | "isFree": false,
64 | "inputPrice": 0.1,
65 | "outputPrice": 0.1,
66 | "gmtRelease": "1970-01-01"
67 | },
68 | {
69 | "platform": "zhipu",
70 | "model": "glm-4v-plus-0111",
71 | "modelType": "vision",
72 | "name": "GLM-4V-Plus-0111",
73 | "contextLength": 8000,
74 | "isFree": false,
75 | "inputPrice": 4,
76 | "outputPrice": 4,
77 | "gmtRelease": "1970-01-01"
78 | },
79 | {
80 | "platform": "zhipu",
81 | "model": "cogview-4",
82 | "modelType": "image",
83 | "name": "CogView-4",
84 | "isFree": false,
85 | "costPer": 0.06,
86 | "gmtRelease": "1970-01-01"
87 | },
88 | {
89 | "platform": "zhipu",
90 | "model": "cogvideox-2",
91 | "modelType": "video",
92 | "name": "CogVideoX-2",
93 | "isFree": false,
94 | "costPer": 0.5,
95 | "gmtRelease": "1970-01-01"
96 | },
97 |
98 | {
99 | "platform": "zhipu",
100 | "model": "glm-4-flash",
101 | "modelType": "cc",
102 | "name": "CGLM-4-Flash",
103 | "isFree": true,
104 | "inputPrice": 0.0,
105 | "outputPrice": 0.0,
106 | "gmtRelease": "1970-01-01"
107 | },
108 | {
109 | "platform": "zhipu",
110 | "model": "glm-4v-flash",
111 | "modelType": "vision",
112 | "name": "GLM-4V-Flash",
113 | "isFree": true,
114 | "inputPrice": 0.0,
115 | "outputPrice": 0.0,
116 | "gmtRelease": "1970-01-01"
117 | },
118 | {
119 | "platform": "zhipu",
120 | "model": "cogview-3-flash",
121 | "modelType": "image",
122 | "name": "CogView-3-Flash",
123 | "isFree": true,
124 | "costPer": 0.0,
125 | "gmtRelease": "1970-01-01"
126 | },
127 | {
128 | "platform": "zhipu",
129 | "model": "ccogvideox-flash",
130 | "modelType": "video",
131 | "name": "CogVideoX-Flash",
132 | "isFree": true,
133 | "costPer": 0.0,
134 | "gmtRelease": "1970-01-01"
135 | }
136 | ]
137 |
--------------------------------------------------------------------------------
/_cus_model_jsons/model_深度求索.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "platform": "deepseek",
4 | "model": "deepseek-chat",
5 | "modelType": "cc",
6 | "name": "DeepSeek-V3",
7 | "contextLength": 64000,
8 | "isFree": false,
9 | "inputPrice": 2,
10 | "outputPrice": 8,
11 | "gmtRelease": "2024-12-26"
12 | },
13 | {
14 | "platform": "deepseek",
15 | "model": "deepseek-reasoner",
16 | "modelType": "cc",
17 | "name": "DeepSeek-R1",
18 | "contextLength": 64000,
19 | "isFree": false,
20 | "inputPrice": 4,
21 | "outputPrice": 16,
22 | "gmtRelease": "2025-01-20"
23 | }
24 | ]
25 |
--------------------------------------------------------------------------------
/_cus_model_jsons/model_百度(免费模型).json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "platform": "baidu",
4 | "model": "ernie-tiny-8k",
5 | "modelType": "cc",
6 | "name": "ERNIE-Tiny-8K",
7 | "contextLength": 8192,
8 | "isFree": true,
9 | "inputPrice": 0.0,
10 | "outputPrice": 0.0,
11 | "gmtRelease": "2024-10-10"
12 | },
13 | {
14 | "platform": "baidu",
15 | "model": "ernie-lite-8k",
16 | "modelType": "cc",
17 | "name": "ERNIE-Lite-8K",
18 | "contextLength": 8192,
19 | "isFree": true,
20 | "inputPrice": 0.0,
21 | "outputPrice": 0.0,
22 | "gmtRelease": "2024-03-08"
23 | },
24 | {
25 | "platform": "baidu",
26 | "model": "ernie-speed-8k",
27 | "modelType": "cc",
28 | "name": "ERNIE-Speed-8K",
29 | "contextLength": 8192,
30 | "isFree": true,
31 | "inputPrice": 0.0,
32 | "outputPrice": 0.0,
33 | "gmtRelease": "2024-01-11"
34 | },
35 | {
36 | "platform": "baidu",
37 | "model": "ernie-speed-128k",
38 | "modelType": "cc",
39 | "name": "ERNIE-Speed-128K",
40 | "contextLength": 128000,
41 | "isFree": true,
42 | "inputPrice": 0.0,
43 | "outputPrice": 0.0,
44 | "gmtRelease": "2024-03-14"
45 | }
46 | ]
47 |
--------------------------------------------------------------------------------
/_cus_model_jsons/model_腾讯(免费模型).json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "platform": "tencent",
4 | "model": "hunyuan-lite",
5 | "modelType": "cc",
6 | "name": "混元-Lite",
7 | "contextLength": 256000,
8 | "isFree": true,
9 | "inputPrice": 0.0,
10 | "outputPrice": 0.0,
11 | "gmtRelease": "2024-10-30"
12 | }
13 | ]
14 |
--------------------------------------------------------------------------------
/_cus_model_jsons/model_零一万物.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "platform": "lingyiwanwu",
4 | "model": "yi-lightning",
5 | "modelType": "cc",
6 | "name": "yi-lightning",
7 | "contextLength": 16000,
8 | "isFree": false,
9 | "inputPrice": 0.99,
10 | "outputPrice": 0.99,
11 | "gmtRelease": "2024-12-23"
12 | },
13 | {
14 | "platform": "lingyiwanwu",
15 | "model": "yi-vision-v2",
16 | "modelType": "vision",
17 | "name": "yi-vision-v2",
18 | "contextLength": 16000,
19 | "isFree": false,
20 | "inputPrice": 6,
21 | "outputPrice": 6,
22 | "gmtRelease": "2024-12-23"
23 | }
24 | ]
25 |
--------------------------------------------------------------------------------
/_cus_model_jsons/平台ApiKey示例.json:
--------------------------------------------------------------------------------
1 | {
2 | "USER_ALIYUN_API_KEY": "sk-xxx",
3 | "USER_BAIDU_API_KEY_V2": "bce-v3/ALTAK-xx/xxx",
4 | "USER_TENCENT_API_KEY": "sk-xxx",
5 |
6 | "USER_DEEPSEEK_API_KEY": "sk-xxx",
7 | "USER_LINGYIWANWU_API_KEY": "xxx",
8 | "USER_ZHIPU_API_KEY": "xxx.xxx",
9 |
10 | "USER_SILICONCLOUD_API_KEY": "sk-xxx",
11 | "USER_INFINI_GEN_STUDIO_API_KEY": "sk-xxx",
12 |
13 | "USER_VOLCENGINE_API_KEY": "xxx",
14 | "USER_VOLCESBOT_API_KEY": "xxx",
15 |
16 | "USER_XFYUN_APP_ID": "xxx",
17 | "USER_XFYUN_API_KEY": "xxx",
18 | "USER_XFYUN_API_SECRET": "xxx",
19 |
20 | "USER_NUTRITIONIX_APP_ID": "xxx",
21 | "USER_NUTRITIONIX_APP_KEY": "xxx",
22 |
23 | "USER_NEWS_API_KEY": "xxx"
24 | }
25 |
--------------------------------------------------------------------------------
/_doc/changelog_pics/0.2.0-beta.1新增内容截图.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sanotsu/swmate/c2749549a4b9dea523351b517576b4ec110422e5/_doc/changelog_pics/0.2.0-beta.1新增内容截图.png
--------------------------------------------------------------------------------
/_doc/changelog_pics/0.3.0-beta.1新增内容截图.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sanotsu/swmate/c2749549a4b9dea523351b517576b4ec110422e5/_doc/changelog_pics/0.3.0-beta.1新增内容截图.jpg
--------------------------------------------------------------------------------
/_doc/screenshots/1-1智能对话1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sanotsu/swmate/c2749549a4b9dea523351b517576b4ec110422e5/_doc/screenshots/1-1智能对话1.jpg
--------------------------------------------------------------------------------
/_doc/screenshots/1-2智能对话2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sanotsu/swmate/c2749549a4b9dea523351b517576b4ec110422e5/_doc/screenshots/1-2智能对话2.jpg
--------------------------------------------------------------------------------
/_doc/screenshots/2智能多聊.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sanotsu/swmate/c2749549a4b9dea523351b517576b4ec110422e5/_doc/screenshots/2智能多聊.jpg
--------------------------------------------------------------------------------
/_doc/screenshots/3文档解读和图片解读.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sanotsu/swmate/c2749549a4b9dea523351b517576b4ec110422e5/_doc/screenshots/3文档解读和图片解读.jpg
--------------------------------------------------------------------------------
/_doc/screenshots/4文本生图-文生视频.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sanotsu/swmate/c2749549a4b9dea523351b517576b4ec110422e5/_doc/screenshots/4文本生图-文生视频.jpg
--------------------------------------------------------------------------------
/_doc/screenshots/5创意文字.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sanotsu/swmate/c2749549a4b9dea523351b517576b4ec110422e5/_doc/screenshots/5创意文字.jpg
--------------------------------------------------------------------------------
/_doc/screenshots/6图片生图.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sanotsu/swmate/c2749549a4b9dea523351b517576b4ec110422e5/_doc/screenshots/6图片生图.jpg
--------------------------------------------------------------------------------
/_doc/screenshots/7支持的角色管理模型列表,使用自己的密钥.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sanotsu/swmate/c2749549a4b9dea523351b517576b4ec110422e5/_doc/screenshots/7支持的角色管理模型列表,使用自己的密钥.jpg
--------------------------------------------------------------------------------
/_doc/screenshots/8极简记账.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sanotsu/swmate/c2749549a4b9dea523351b517576b4ec110422e5/_doc/screenshots/8极简记账.jpg
--------------------------------------------------------------------------------
/_doc/screenshots/9-1随机菜品1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sanotsu/swmate/c2749549a4b9dea523351b517576b4ec110422e5/_doc/screenshots/9-1随机菜品1.jpg
--------------------------------------------------------------------------------
/_doc/screenshots/9-2随机菜品2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sanotsu/swmate/c2749549a4b9dea523351b517576b4ec110422e5/_doc/screenshots/9-2随机菜品2.jpg
--------------------------------------------------------------------------------
/_doc/screenshots/brief_version/助手工具.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sanotsu/swmate/c2749549a4b9dea523351b517576b4ec110422e5/_doc/screenshots/brief_version/助手工具.jpg
--------------------------------------------------------------------------------
/_doc/screenshots/brief_version/对话页面.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sanotsu/swmate/c2749549a4b9dea523351b517576b4ec110422e5/_doc/screenshots/brief_version/对话页面.jpg
--------------------------------------------------------------------------------
/_doc/screenshots/brief_version/导入配置等.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sanotsu/swmate/c2749549a4b9dea523351b517576b4ec110422e5/_doc/screenshots/brief_version/导入配置等.jpg
--------------------------------------------------------------------------------
/_doc/screenshots/brief_version/生活工具.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sanotsu/swmate/c2749549a4b9dea523351b517576b4ec110422e5/_doc/screenshots/brief_version/生活工具.jpg
--------------------------------------------------------------------------------
/_doc/screenshots/brief_version/角色扮演.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sanotsu/swmate/c2749549a4b9dea523351b517576b4ec110422e5/_doc/screenshots/brief_version/角色扮演.jpg
--------------------------------------------------------------------------------
/_doc/suchat_snapshots/snapshot.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sanotsu/swmate/c2749549a4b9dea523351b517576b4ec110422e5/_doc/suchat_snapshots/snapshot.jpg
--------------------------------------------------------------------------------
/_doc/suchat_snapshots/suchat_chat_page.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sanotsu/swmate/c2749549a4b9dea523351b517576b4ec110422e5/_doc/suchat_snapshots/suchat_chat_page.jpg
--------------------------------------------------------------------------------
/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | # This file configures the analyzer, which statically analyzes Dart code to
2 | # check for errors, warnings, and lints.
3 | #
4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled
5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
6 | # invoked from the command line by running `flutter analyze`.
7 |
8 | # The following line activates a set of recommended lints for Flutter apps,
9 | # packages, and plugins designed to encourage good coding practices.
10 | include: package:flutter_lints/flutter.yaml
11 |
12 | linter:
13 | # The lint rules applied to this project can be customized in the
14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml`
15 | # included above or to enable additional rules. A list of all available lints
16 | # and their documentation is published at https://dart.dev/lints.
17 | #
18 | # Instead of disabling a lint rule for the entire project in the
19 | # section below, it can also be suppressed for a single line of code
20 | # or a specific dart file by using the `// ignore: name_of_lint` and
21 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file
22 | # producing the lint.
23 | rules:
24 | # avoid_print: false # Uncomment to disable the `avoid_print` rule
25 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
26 |
27 | # Additional information about this file can be found at
28 | # https://dart.dev/guides/language/analysis-options
29 |
--------------------------------------------------------------------------------
/android/.gitignore:
--------------------------------------------------------------------------------
1 | gradle-wrapper.jar
2 | /.gradle
3 | /captures/
4 | /gradlew
5 | /gradlew.bat
6 | /local.properties
7 | GeneratedPluginRegistrant.java
8 |
9 | # Remember to never publicly share your keystore.
10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
11 | key.properties
12 | **/*.keystore
13 | **/*.jks
14 |
--------------------------------------------------------------------------------
/android/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # This is generated automatically by the Android Gradle plugin.
2 | -dontwarn java.awt.Color
3 | -dontwarn java.awt.Dimension
4 | -dontwarn java.awt.Rectangle
5 | -dontwarn java.awt.color.ColorSpace
6 | -dontwarn java.awt.geom.AffineTransform
7 | -dontwarn java.awt.geom.Dimension2D
8 | -dontwarn java.awt.geom.Path2D
9 | -dontwarn java.awt.geom.PathIterator
10 | -dontwarn java.awt.geom.Point2D
11 | -dontwarn java.awt.geom.Rectangle2D
12 | -dontwarn java.awt.image.BufferedImage
13 | -dontwarn java.awt.image.ColorModel
14 | -dontwarn java.awt.image.ComponentColorModel
15 | -dontwarn java.awt.image.DirectColorModel
16 | -dontwarn java.awt.image.IndexColorModel
17 | -dontwarn java.awt.image.PackedColorModel
18 | -dontwarn javax.xml.stream.Location
19 | -dontwarn javax.xml.stream.XMLStreamException
20 | -dontwarn javax.xml.stream.XMLStreamReader
21 | -dontwarn net.sf.saxon.Configuration
22 | -dontwarn net.sf.saxon.dom.DOMNodeWrapper
23 | -dontwarn net.sf.saxon.om.Item
24 | -dontwarn net.sf.saxon.om.NodeInfo
25 | -dontwarn net.sf.saxon.om.Sequence
26 | -dontwarn net.sf.saxon.om.SequenceTool
27 | -dontwarn net.sf.saxon.sxpath.IndependentContext
28 | -dontwarn net.sf.saxon.sxpath.XPathDynamicContext
29 | -dontwarn net.sf.saxon.sxpath.XPathEvaluator
30 | -dontwarn net.sf.saxon.sxpath.XPathExpression
31 | -dontwarn net.sf.saxon.sxpath.XPathStaticContext
32 | -dontwarn net.sf.saxon.sxpath.XPathVariable
33 | -dontwarn net.sf.saxon.tree.wrapper.VirtualNode
34 | -dontwarn org.apache.batik.anim.dom.SAXSVGDocumentFactory
35 | -dontwarn org.apache.batik.bridge.BridgeContext
36 | -dontwarn org.apache.batik.bridge.DocumentLoader
37 | -dontwarn org.apache.batik.bridge.GVTBuilder
38 | -dontwarn org.apache.batik.bridge.UserAgent
39 | -dontwarn org.apache.batik.bridge.UserAgentAdapter
40 | -dontwarn org.apache.batik.util.XMLResourceDescriptor
--------------------------------------------------------------------------------
/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
19 |
28 |
32 |
36 |
37 |
38 |
39 |
40 |
41 |
43 |
46 |
47 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/android/app/src/main/kotlin/com/swm/swmate/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.swm.swmate
2 |
3 | import io.flutter.embedding.android.FlutterActivity
4 |
5 | class MainActivity: FlutterActivity()
6 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-v21/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sanotsu/swmate/c2749549a4b9dea523351b517576b4ec110422e5/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sanotsu/swmate/c2749549a4b9dea523351b517576b4ec110422e5/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sanotsu/swmate/c2749549a4b9dea523351b517576b4ec110422e5/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sanotsu/swmate/c2749549a4b9dea523351b517576b4ec110422e5/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sanotsu/swmate/c2749549a4b9dea523351b517576b4ec110422e5/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/values-night/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/android/app/src/profile/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | allprojects {
2 | repositories {
3 | google()
4 | mavenCentral()
5 | }
6 | }
7 |
8 | rootProject.buildDir = "../build"
9 | subprojects {
10 | project.buildDir = "${rootProject.buildDir}/${project.name}"
11 | }
12 | subprojects {
13 | project.evaluationDependsOn(":app")
14 | }
15 |
16 | tasks.register("clean", Delete) {
17 | delete rootProject.buildDir
18 | }
19 |
--------------------------------------------------------------------------------
/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx4G -XX:+HeapDumpOnOutOfMemoryError
2 | android.useAndroidX=true
3 | android.enableJetifier=true
4 | android.enableR8=true
5 | # org.gradle.java.home=D:/DvptTools/Java/openlogic-openjdk-17.0.10+7-windows-x64
6 | org.gradle.java.home=/home/david/.jdks/temurin-17.0.6
--------------------------------------------------------------------------------
/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-7.6.3-all.zip
6 | # distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-all.zip
7 | distributionUrl = https\://services.gradle.org/distributions/gradle-8.7-all.zip
--------------------------------------------------------------------------------
/android/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | def flutterSdkPath = {
3 | def properties = new Properties()
4 | file("local.properties").withInputStream { properties.load(it) }
5 | def flutterSdkPath = properties.getProperty("flutter.sdk")
6 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
7 | return flutterSdkPath
8 | }()
9 |
10 | includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
11 |
12 | repositories {
13 | google()
14 | mavenCentral()
15 | gradlePluginPortal()
16 | }
17 | }
18 |
19 | // 2024-11-04 更新到最新(注意,这里的插件版本最小需要的gradle版本不一定一样)
20 | // 文档:https://developer.android.com/build/releases/gradle-plugin?hl=zh-cn#kts
21 | plugins {
22 | id "dev.flutter.flutter-plugin-loader" version "1.0.0"
23 | // id "com.android.application" version "8.3.1" apply false
24 | // id "org.jetbrains.kotlin.android" version "1.8.22" apply false
25 |
26 | id 'com.android.application' version '8.6.0' apply false
27 | id 'com.android.library' version '8.6.0' apply false
28 | id 'org.jetbrains.kotlin.android' version '2.0.20' apply false
29 | }
30 |
31 |
32 | include ":app"
33 |
--------------------------------------------------------------------------------
/assets/brand.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sanotsu/swmate/c2749549a4b9dea523351b517576b4ec110422e5/assets/brand.png
--------------------------------------------------------------------------------
/assets/characters/default_avatar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sanotsu/swmate/c2749549a4b9dea523351b517576b4ec110422e5/assets/characters/default_avatar.png
--------------------------------------------------------------------------------
/assets/chat_backgrounds/bg1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sanotsu/swmate/c2749549a4b9dea523351b517576b4ec110422e5/assets/chat_backgrounds/bg1.jpg
--------------------------------------------------------------------------------
/assets/chat_backgrounds/bg2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sanotsu/swmate/c2749549a4b9dea523351b517576b4ec110422e5/assets/chat_backgrounds/bg2.jpg
--------------------------------------------------------------------------------
/assets/images/no_image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sanotsu/swmate/c2749549a4b9dea523351b517576b4ec110422e5/assets/images/no_image.png
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/lib/apis/chat_completion/chat_helper.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | import '../../common/utils/tools.dart';
4 |
5 | ///
6 | /// dio 中处理SSE的解析器
7 | /// 来源: https://github.com/cfug/dio/issues/1279#issuecomment-1326121953
8 | ///
9 | class SseTransformer extends StreamTransformerBase {
10 | const SseTransformer();
11 | @override
12 | Stream bind(Stream stream) {
13 | return Stream.eventTransformed(stream, (sink) => SseEventSink(sink));
14 | }
15 | }
16 |
17 | class SseEventSink implements EventSink {
18 | final EventSink _eventSink;
19 |
20 | String? _id;
21 | String _event = "message";
22 | String _data = "";
23 | int? _retry;
24 |
25 | SseEventSink(this._eventSink);
26 |
27 | @override
28 | void add(String event) {
29 | if (event.startsWith("id:")) {
30 | _id = event.substring(3);
31 | return;
32 | }
33 | if (event.startsWith("event:")) {
34 | _event = event.substring(6);
35 | return;
36 | }
37 | if (event.startsWith("data:")) {
38 | _data = event.substring(5);
39 | return;
40 | }
41 | if (event.startsWith("retry:")) {
42 | _retry = int.tryParse(event.substring(6));
43 | return;
44 | }
45 | if (event.isEmpty) {
46 | _eventSink.add(
47 | SseMessage(id: _id, event: _event, data: _data, retry: _retry),
48 | );
49 | _id = null;
50 | _event = "message";
51 | _data = "";
52 | _retry = null;
53 | }
54 |
55 | // 自己加的,请求报错时不是一个正常的流的结构,是个json,直接添加即可
56 | if (isJsonString(event)) {
57 | _eventSink.add(
58 | SseMessage(id: _id, event: _event, data: event, retry: _retry),
59 | );
60 |
61 | _id = null;
62 | _event = "message";
63 | _data = "";
64 | _retry = null;
65 | }
66 | }
67 |
68 | @override
69 | void addError(Object error, [StackTrace? stackTrace]) {
70 | _eventSink.addError(error, stackTrace);
71 | }
72 |
73 | @override
74 | void close() {
75 | _eventSink.close();
76 | }
77 | }
78 |
79 | class SseMessage {
80 | final String? id;
81 | final String event;
82 | final String data;
83 | final int? retry;
84 |
85 | const SseMessage({
86 | this.id,
87 | required this.event,
88 | required this.data,
89 | this.retry,
90 | });
91 | }
92 |
--------------------------------------------------------------------------------
/lib/apis/gen_access_token/xfyun_signature.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 | import 'dart:io';
3 | import 'package:crypto/crypto.dart';
4 |
5 | /// 讯飞平台的 WebAPI 通用鉴权
6 | //@hostUrl : 比如语言识别的 wss://iat-api.xfyun.cn/v2/iat
7 | //@apiKey : apiKey
8 | //@apiSecret : apiSecret
9 | //@method :get 或者post请求
10 | String genXfyunAssembleAuthUrl(
11 | String hosturl,
12 | String apiKey,
13 | String apiSecret,
14 | String method,
15 | ) {
16 | var ul = Uri.parse(hosturl);
17 |
18 | // 签名时间
19 | String date = HttpDate.format(DateTime.now().toUtc());
20 |
21 | // 参与签名的字段 host, date, request-line
22 | List signString = [
23 | "host: ${ul.host}",
24 | "date: $date",
25 | // "GET ${ul.path} HTTP/1.1",
26 | "$method ${ul.path} HTTP/1.1",
27 | ];
28 |
29 | // 拼接签名字符串
30 | String sgin = signString.join("\n");
31 |
32 | // 签名结果
33 | String sha = hmacSha256ToBase64(sgin, apiSecret);
34 |
35 | // 构建请求参数 此时不需要urlencoding
36 | String authUrl =
37 | 'api_key="$apiKey", algorithm="hmac-sha256", headers="host date request-line", signature="$sha"';
38 |
39 | // 将请求参数使用base64编码
40 | String authorization = base64Encode(utf8.encode(authUrl));
41 |
42 | // 将编码后的字符串url encode后添加到url后面
43 | String callurl =
44 | '$hosturl?host=${Uri.encodeComponent(ul.host)}&date=${Uri.encodeComponent(date)}&authorization=${Uri.encodeComponent(authorization)}';
45 |
46 | return callurl;
47 | }
48 |
49 | String hmacSha256ToBase64(String data, String key) {
50 | var keyBytes = utf8.encode(key);
51 | var dataBytes = utf8.encode(data);
52 | var hmacSha256 = Hmac(sha256, keyBytes);
53 | var digest = hmacSha256.convert(dataBytes);
54 | return base64Encode(digest.bytes);
55 | }
56 |
--------------------------------------------------------------------------------
/lib/apis/get_app_key_helper.dart:
--------------------------------------------------------------------------------
1 | // 用户自定义输入的平台的密钥的相关key枚举,
2 | // 在表单验证、保存到缓存、读取时都使用的关键字,避免过多魔法值出错
3 | // SelfKeyName
4 | import '../services/cus_get_storage.dart';
5 |
6 | // 从缓存中获取用户自定义的密钥,没取到就用预设的
7 | String getStoredUserKey(String key, String defaultValue) {
8 | return MyGetStorage().getUserAKMap()[key] != null &&
9 | MyGetStorage().getUserAKMap()[key]!.isNotEmpty
10 | ? MyGetStorage().getUserAKMap()[key]!
11 | : defaultValue;
12 | }
13 |
--------------------------------------------------------------------------------
/lib/apis/life_tools/animal_lover/random_fact_apis.dart:
--------------------------------------------------------------------------------
1 | // 收集
2 |
3 | import 'dart:convert';
4 | import 'dart:math';
5 |
6 | import '../../../common/utils/dio_client/cus_http_client.dart';
7 |
8 | /// 获取犬科事实(dog facts)的地址:https://dogapi.dog/api/v2/facts
9 | /// 随机cat facts :https://meowfacts.herokuapp.com/?lang=zho
10 | /// 随机1个事实(总共332个事实):https://catfact.ninja/fact
11 | ///
12 | /// 随机一张狗图:https://random.dog/woof.json
13 | /// 随机一张猫图:https://cataas.com/cat
14 |
15 | enum FactSource {
16 | dogapi,
17 | // meowfacts,
18 | catfact,
19 | }
20 |
21 | const Map apiUrls = {
22 | FactSource.dogapi: "https://dogapi.dog/api/v2/facts",
23 | // 2024-10-07 国内不能访问
24 | // FactSource.meowfacts: "https://meowfacts.herokuapp.com/?lang=zho",
25 | FactSource.catfact: "https://catfact.ninja/fact",
26 | };
27 |
28 | Future getAnimalFact() async {
29 | var apikey = FactSource.values[Random().nextInt(FactSource.values.length)];
30 |
31 | try {
32 | var url = apiUrls[apikey]!;
33 |
34 | var respData = await HttpUtils.get(
35 | path: url,
36 | headers: {"Content-Type": "application/json"},
37 | );
38 |
39 | /// 2024-06-06 注意,这里报错的时候,响应的是String,而正常获取回复响应是_Map
40 | if (respData.runtimeType == String) {
41 | respData = json.decode(respData);
42 | }
43 |
44 | // 不同的API响应的结果不一样,但最终都是返回一个字符串
45 | if (apikey == FactSource.dogapi) {
46 | // 结构类似 {"data":[{"id":"a264e88f-ef76-48b5-ab35-9a1c2e71cab9","type":"fact",
47 | // "attributes":{"body":"Fifty-eight percent of people put pets in family and holiday portraits."}}]}
48 | return ((respData["data"] as List).first["attributes"]
49 | as Map)["body"];
50 | }
51 | // else if (apikey == FactSource.meowfacts) {
52 | // // 结构类似 {"data":["貓咪極速奔跑可達時速 50 公里。"]}
53 | // return (respData["data"] as List).first;
54 | // }
55 | else if (apikey == FactSource.catfact) {
56 | // 结构类似 {"fact":"Cats take between 20-40 breaths per minute.","length":43}
57 | return respData["fact"];
58 | }
59 |
60 | return "<暂未获取数据>";
61 | } catch (e) {
62 | // API请求报错,显示报错信息
63 | rethrow;
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/lib/apis/life_tools/animal_lover/thatapi_apis.dart:
--------------------------------------------------------------------------------
1 | import '../../../common/utils/dio_client/cus_http_client.dart';
2 | import '../../../models/life_tools/animal_lover/the_dog_cat_api_breed.dart';
3 | import '../../../models/life_tools/animal_lover/the_dog_cat_api_image.dart';
4 |
5 | /// 猫狗都有,需要ak,可以用来获取品种和图片(说是有60k+ Images. Breeds. Facts.),其他使用大模型来获取
6 | /// https://portal.thatapicompany.com/
7 | ///
8 | /// 随机图片不需要AK,但指定品种的话,需要AK(如果是狗的话,把thecatapi改为thedogapi即可)
9 | /// 随机1张图片:https://api.thecatapi.com/v1/images/search
10 | /// 随机10张图片:https://api.thecatapi.com/v1/images/search?limit=10
11 | /// 指定品种:https://api.thecatapi.com/v1/images/search?limit=10&breed_ids=beng&api_key=REPLACE_ME
12 | /// 随机指定品种的一张图(没有ak只有1张):https://api.thecatapi.com/v1/images/search?breed_ids=beng
13 | /// 获取所有品种:https://api.thecatapi.com/v1/breeds
14 | ///
15 | /// 获取指定图片:https://api.thecatapi.com/v1/images/{reference_image_id}
16 | /// 在上面获取所有品种时,每个品种都有一个reference_image_id,替换上面编号可以得到指定图片的详情
17 |
18 | ///
19 | /// 获取品种信息
20 | ///
21 | Future> getThatApiBreeds({
22 | String type = 'cat', // 查询猫或者狗
23 | }) async {
24 | try {
25 | var url = "https://api.the${type}api.com/v1/breeds";
26 |
27 | List respData = await HttpUtils.get(path: url);
28 |
29 | // 响应是json格式的列表 List
30 | return respData.map((e) => Breed.fromJson(e)).toList();
31 | } catch (e) {
32 | // API请求报错,显示报错信息
33 | rethrow;
34 | }
35 | }
36 |
37 | ///
38 | /// 获取图片信息
39 | ///
40 | Future> getThatApiImages({
41 | String? type = 'dog', // 猫还是狗 cat dog
42 | bool isRandom = false, // 是否随机
43 | int? number, // 随机数量
44 | // 品种,多个用逗号连接(thecarapi栏位是String,thedogapi栏位是int)
45 | dynamic breedIds,
46 | }) async {
47 | // 如果传了number,则是随机某几个
48 | // 如果只传breed,则是查询单个主品种信息
49 | // 如果传了breed和subBreed,则是查询单个子品种信息
50 |
51 | try {
52 | var url = "https://api.the${type}api.com/v1/images/search";
53 |
54 | if (isRandom) {
55 | if (number != null) {
56 | url += "?limit=${number <= 10 ? 1 : 10}";
57 | } else {
58 | url = url;
59 | }
60 | } else if (breedIds != null) {
61 | url += "?breed_ids=$breedIds";
62 | }
63 |
64 | List respData = await HttpUtils.get(path: url);
65 |
66 | // 响应是json格式的列表 List
67 | return respData.map((e) => TheDogCatApiImage.fromJson(e)).toList();
68 | } catch (e) {
69 | // API请求报错,显示报错信息
70 | rethrow;
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/lib/apis/life_tools/food/usda_food_data_central/usda_food_data_apis.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 |
3 | import '../../../../common/utils/dio_client/cus_http_client.dart';
4 | import '../../../../models/life_tools/food/usda_food_data/usda_food_item.dart';
5 | import '../../../../models/life_tools/food/usda_food_data/usda_food_search_resp.dart';
6 |
7 | /// 来源:https://fdc.nal.usda.gov/api-guide.html
8 | /// 关于数据类型的说明:https://fdc.nal.usda.gov/data-documentation.html
9 | /// 2024-10-17 目前API是测试用的demo,后续可以去网站注册,可以提高到API调用每日上限到1000次。
10 |
11 | var baseUsda = "https://api.nal.usda.gov/fdc/v1";
12 |
13 | /// 条件查询食品信息
14 | Future searchUSDAFoods(
15 | // 关键字,不可为null,但可以为空字串
16 | String query, {
17 | List? dataType, // 数据类型 [Branded,Foundation,Survey (FNDDS),SR Legacy]
18 | int? pageSize = 10,
19 | int? pageNumber = 1,
20 | // 排序的字段:dataType.keyword, lowercaseDescription.keyword, fdcId, publishedDate
21 | String? sortBy = "dataType.keyword",
22 | // 排序的方式
23 | String? sortOrder = "asc", // asc desc
24 | // 品牌,只适用在品牌分类的食品中
25 | String? brandOwner,
26 | }) async {
27 | try {
28 | var respData = await HttpUtils.get(
29 | // 这里不传就随机一个类型
30 | path: "$baseUsda/foods/search",
31 | // 因为上拉下拉有加载圈,就不显示请求的加载了
32 | showLoading: false,
33 | // 因为存在404找不到单词也保存,但单独处理了,就不在http拦截器中报错了
34 | showErrorMessage: false,
35 | queryParameters: {
36 | "api_key": "DEMO_KEY",
37 | "query": query,
38 | // 2024-10-14 根据定义,暂时固定为查询基础数据
39 | // "dataType": ["Foundation"],
40 | "dataType": dataType,
41 | "pageSize": pageSize,
42 | "pageNumber": pageNumber,
43 | },
44 | );
45 |
46 | if (respData.runtimeType == String) {
47 | respData = json.decode(respData);
48 | }
49 |
50 | return USDASearchResultResp.fromJson(respData);
51 | } catch (e) {
52 | rethrow;
53 | }
54 | }
55 |
56 | // 查询指定编号的食品详情
57 | Future getUSDAFoodById(
58 | // 食品编号
59 | int fdcId, {
60 | // 返回的栏位
61 | String? format = "full", // abridged, full
62 | }) async {
63 | try {
64 | var respData = await HttpUtils.get(
65 | // 这里不传就随机一个类型
66 | path: "$baseUsda/food/$fdcId",
67 | // 因为上拉下拉有加载圈,就不显示请求的加载了
68 | showLoading: false,
69 | // 因为存在404找不到单词也保存,但单独处理了,就不在http拦截器中报错了
70 | showErrorMessage: false,
71 | queryParameters: {
72 | "api_key": "DEMO_KEY",
73 | "format": format,
74 | },
75 | );
76 |
77 | if (respData.runtimeType == String) {
78 | respData = json.decode(respData);
79 | }
80 |
81 | return USDAFoodItem.fromJson(respData);
82 | } catch (e) {
83 | rethrow;
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/lib/apis/life_tools/free_dictionary/free_dictionary_apis.dart:
--------------------------------------------------------------------------------
1 | // 知道分类编号查询
2 | import 'dart:convert';
3 |
4 | import '../../../common/utils/dio_client/cus_http_client.dart';
5 | import '../../../common/utils/dio_client/interceptor_error.dart';
6 | import '../../../models/life_tools/free_dictionary/free_dictionary_resp.dart';
7 |
8 | /// API来源和说明:https://github.com/meetDeveloper/freeDictionaryAPI
9 |
10 | var fdBase = "https://api.dictionaryapi.dev/api/v2/entries/en";
11 |
12 | Future getFreeDictionaryItem(String word) async {
13 | try {
14 | var respData = await HttpUtils.get(
15 | path: "$fdBase/$word",
16 | // 因为上拉下拉有加载圈,就不显示请求的加载了
17 | showLoading: false,
18 | // 因为存在404找不到单词也保存,但单独处理了,就不在http拦截器中报错了
19 | showErrorMessage: false,
20 | );
21 |
22 | if (respData.runtimeType == String) {
23 | respData = json.decode(respData);
24 | }
25 |
26 | if (respData is List) {
27 | return FreeDictionaryItem.fromJson(
28 | respData.first as Map,
29 | );
30 | } else {
31 | return FreeDictionaryItem.fromJson(respData);
32 | }
33 | } on CusHttpException catch (e) {
34 | // API请求报错,显示报错信息
35 | // 如果找不到输入的单词,是响应404错误
36 | // 可以构建一下,方便展示
37 | return FreeDictionaryItem.fromJson(json.decode(e.errRespString));
38 | } catch (e) {
39 | rethrow;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/lib/apis/life_tools/hitokoto/hitokoto_apis.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 |
3 | import '../../../common/utils/dio_client/cus_http_client.dart';
4 | import '../../../models/life_tools/hitokoto/hitokoto.dart';
5 |
6 | var hitoBase = "https://v1.hitokoto.cn";
7 |
8 | Future getHitokoto({
9 | // 类型:a动画;b漫画;c游戏;d文学;e原创;f来自网络;g其他;h影视;i诗词;j网易云;k哲学;l抖机灵;其他作为 动画 类型处理
10 | String? cate,
11 | }) async {
12 | try {
13 | var respData = await HttpUtils.get(
14 | // 这里不传就随机一个类型
15 | path: "$hitoBase?c=${cate ?? ''}",
16 | // 因为上拉下拉有加载圈,就不显示请求的加载了
17 | showLoading: false,
18 | // 因为存在404找不到单词也保存,但单独处理了,就不在http拦截器中报错了
19 | showErrorMessage: false,
20 | );
21 |
22 | if (respData.runtimeType == String) {
23 | respData = json.decode(respData);
24 | }
25 |
26 | return Hitokoto.fromJson(respData);
27 | } catch (e) {
28 | rethrow;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/lib/apis/life_tools/news/newsapi_apis.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 |
3 | import '../../../common/constants/default_models.dart';
4 | import '../../../common/utils/dio_client/cus_http_client.dart';
5 | import '../../../models/life_tools/news/news_api_resp.dart';
6 | import '../../get_app_key_helper.dart';
7 |
8 | var newsapiBase = "https://newsapi.org/v2";
9 |
10 | // 查询热门话题
11 | Future getNewsapiList({
12 | int page = 1,
13 | int pageSize = 100,
14 | // 热点 top-headlines | 所有 everything
15 | String type = "top-headlines",
16 | String? query,
17 | String? category,
18 | }) async {
19 | var params = {
20 | "apiKey": getStoredUserKey("USER_NEWS_API_KEY", DefaultApiKeys.newsApiKey),
21 |
22 | // 新闻来源,可以在 https://newsapi.org/v2/top-headlines/sources 查到相关信息(结构体的id栏位)
23 |
24 | /// 不能和 country 或 category 一起用
25 | // "sources": '',
26 |
27 | "page": page,
28 | // 默认100。为了减少请求次数,可以保留个大数字
29 | "pageSize": pageSize,
30 | };
31 |
32 | // 2024-11-06 就只给两个选项,热榜和所有的查询
33 | if (type == "top-headlines") {
34 | /// 热榜时,这几个栏位不可都为空: sources, q, country, category
35 | params.addAll({
36 | // 查询热榜暂时不用关键字查询,默认分类显示所有
37 | // "q": query ?? "",
38 |
39 | // ISO 3166-1编码的两个字母的国家编号【目前免费的看起来只能用 us 才有值】
40 | // 不能和 sources 参数一起用
41 | // https://www.iso.org/obp/ui/#search
42 | // "country": 'us',
43 |
44 | // 新闻的分类: business entertainment general health science sports technology
45 | // 不能和 sources 参数一起用
46 | "category": category ?? 'general',
47 | });
48 | } else {
49 | // 所有搜索时,后面栏位不可全为空: q, qInTitle, sources, domains.
50 | params.addAll({
51 | // 搜索的关键字(带双引号可以强制匹配)
52 | // 2024-11-07 查询热榜时只有分类就不带上查询了,查询就从所有新闻来
53 | "q": "$query",
54 |
55 | // 搜索限制到的字段,可以用逗号添加多个
56 | // title | description | content
57 | "searchIn": "title,description",
58 |
59 | // 新闻的网域,多个用逗号连接,例如 bbc.co.uk,techcrunch.com,engadget.com
60 | // "domains": "",
61 |
62 | // 排除的域,多个用逗号连接,例如 bbc.co.uk,techcrunch.com,engadget.com
63 | // "excludeDomains": "",
64 |
65 | // 搜索的时间范围,ISO 8601 格式字符串
66 | // "from": '',
67 | // "to": '',
68 |
69 | // 标题的语言,可选性
70 | // ar de en es fr he it nl no pt ru sv ud zh
71 | // "language": "zh",
72 |
73 | // 排序方式
74 | // relevancy: 与q关系更密切的文章排在前面
75 | // popularity: 来自流行来源和出版商的文章优先
76 | // publishedAt(默认): 最新文章排在第一位
77 | "sortBy": "publishedAt",
78 | });
79 | }
80 |
81 | try {
82 | var respData = await HttpUtils.get(
83 | path: "$newsapiBase/$type",
84 | // 因为上拉下拉有加载圈,就不显示请求的加载了
85 | showLoading: false,
86 | queryParameters: params,
87 | );
88 |
89 | if (respData.runtimeType == String) {
90 | respData = json.decode(respData);
91 | }
92 |
93 | return NewsApiResp.fromJson(respData);
94 | } catch (e) {
95 | // API请求报错,显示报错信息
96 | rethrow;
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/lib/apis/life_tools/news/readhub_apis.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 |
3 | import '../../../common/utils/dio_client/cus_http_client.dart';
4 | import '../../../models/life_tools/news/readhub_hot_topic_resp.dart';
5 |
6 | var readhubBase = "https://api.readhub.cn";
7 |
8 | // 查询热门话题
9 | Future getReadhubHotTopicList({
10 | int page = 1,
11 | int size = 10,
12 | }) async {
13 | try {
14 | var respData = await HttpUtils.get(
15 | path: "$readhubBase/topic/list",
16 | // 因为上拉下拉有加载圈,就不显示请求的加载了
17 | showLoading: false,
18 | queryParameters: {"page": page, "size": size},
19 | );
20 |
21 | if (respData.runtimeType == String) {
22 | respData = json.decode(respData);
23 | }
24 |
25 | if (respData["data"] == null) {
26 | throw Exception("返回结果不正确: $respData");
27 | }
28 |
29 | return ReadhubHotTopicResp.fromJson(respData["data"]);
30 | } catch (e) {
31 | // API请求报错,显示报错信息
32 | rethrow;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/lib/apis/life_tools/news/sina_roll_news_apis.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 |
3 | import '../../../common/utils/dio_client/cus_http_client.dart';
4 | import '../../../models/life_tools/news/sina_roll_news_resp.dart';
5 |
6 | var sinaRollNewsBase = "https://feed.mix.sina.com.cn/api/roll/get";
7 |
8 | // 查询热门话题
9 | Future getSinaRollNewsList({
10 | int page = 1,
11 | int size = 10,
12 | int lid = 2509,
13 | }) async {
14 | try {
15 | var respData = await HttpUtils.get(
16 | path: sinaRollNewsBase,
17 | // 因为上拉下拉有加载圈,就不显示请求的加载了
18 | showLoading: false,
19 | queryParameters: {
20 | "pageid": 153,
21 | "lid": lid,
22 | "page": page,
23 | "num": size,
24 | },
25 | );
26 |
27 | if (respData.runtimeType == String) {
28 | respData = json.decode(respData);
29 | }
30 |
31 | if (respData["result"] == null) {
32 | throw Exception("返回结果不正确: $respData");
33 | }
34 |
35 | return SinaRollNewsResp.fromJson(respData["result"]);
36 | } catch (e) {
37 | // API请求报错,显示报错信息
38 | rethrow;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/lib/apis/life_tools/waifu_pic/index.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 |
3 | import '../../../common/utils/dio_client/cus_http_client.dart';
4 | import '../../../common/utils/dio_client/cus_http_request.dart';
5 |
6 | ///
7 | /// 获取 waifu.pics 图片
8 | ///
9 | /// 单个图片: GET https://api.waifu.pics/{type}/{category}
10 | /// 随机30张图片: POST https://api.waifu.pics/many/{type}/{category}
11 | ///
12 | /// 获取waifu.im 的图片 https://docs.waifu.im/reference/api-reference/search
13 | /// 参数更多,但不知道是不是同一个源
14 | /// https://api.waifu.im/search?included_tags={array[string]}&is_nsfw={string}
15 | ///
16 | /*
17 | curl --request POST \
18 | --url https://api.waifu.pics/many/sfw/pat \
19 | --header 'accept: application/json' \
20 | --header 'content-type: application/json' \
21 | --data '
22 | {
23 | "type": "sfw",
24 | "category":"pat"
25 | }
26 | '
27 | */
28 | Future> getWaifuPicImages({
29 | String? type = "sfw", // 只有2个 sfw | nsfw
30 | String? category, // 分类
31 | bool isMany = false, // 是否多个,是则为POST 否则为GET
32 | // 现在两个源 pics | im
33 | // 上面3个参数是默认 pics 的,im 的带上前缀(虽然原本支持参数很多,但这里只提供少数)
34 | String source = "pics",
35 | String imIncludedTags = "waifu", // 虽说是支持多个,但添加多个tag时报错
36 | // 如果tag本身就是nsfw的,那这个bool没效果,都是工作场合不安全;
37 | // 但tag不是nsfw,不加上这个false,则可能出现工作场合不安全
38 | bool imIsNsfw = false,
39 | int imLimit = 1,
40 | }) async {
41 | try {
42 | if (source == "im") {
43 | var respData = await HttpUtils.get(
44 | path:
45 | "https://api.waifu.im/search?included_tags=$imIncludedTags&is_nsfw=$imIsNsfw${imLimit > 1 ? "&limit=$imLimit" : ""}",
46 | );
47 |
48 | if (respData.runtimeType == String) {
49 | respData = json.decode(respData);
50 | }
51 |
52 | // 这个响应体内容很多,暂时只取得地址即可
53 | return (respData["images"] as List)
54 | .map((e) => (e["url"] as String))
55 | .toList();
56 | } else {
57 | var respData = (isMany)
58 | ? await HttpUtils.post(
59 | path: "https://api.waifu.pics/many/$type/$category",
60 | method: CusHttpMethod.post,
61 | headers: {"Content-Type": "application/json"},
62 | data: {"type": type, "category": category})
63 | : await HttpUtils.get(
64 | path: "https://api.waifu.pics/$type/$category",
65 | );
66 |
67 | if (respData.runtimeType == String) {
68 | respData = json.decode(respData);
69 | }
70 |
71 | if (isMany) {
72 | return (respData["files"] as List).cast();
73 | } else {
74 | return [respData["url"]];
75 | }
76 | }
77 | } catch (e) {
78 | // API请求报错,显示报错信息
79 | rethrow;
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/lib/apis/voice_recognition/xunfei_apis.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 | import 'dart:convert';
3 | import 'dart:async';
4 | import 'package:flutter_easyloading/flutter_easyloading.dart';
5 | import 'package:web_socket_channel/web_socket_channel.dart';
6 |
7 | import '../../common/constants/default_models.dart';
8 | import '../../models/brief_ai_tools/voice_recognition/xunfei_voice_dictation.dart';
9 | import '../gen_access_token/xfyun_signature.dart';
10 | import '../get_app_key_helper.dart';
11 |
12 | const voiceRegUrl = "wss://iat-api.xfyun.cn/v2/iat";
13 |
14 | Future getTextFromAudioFromXFYun(String audioPath) async {
15 | // 生成鉴权url
16 | var authUrl = genXfyunAssembleAuthUrl(
17 | voiceRegUrl,
18 | getStoredUserKey("USER_XFYUN_API_KEY", DefaultApiKeys.xfyunApiKey),
19 | getStoredUserKey("USER_XFYUN_API_SECRET", DefaultApiKeys.xfyunApiSecret),
20 | "GET",
21 | );
22 |
23 | // 把音频文件转为base64
24 | final file = File(audioPath);
25 | final audioBytes = await file.readAsBytes();
26 | final audioBase64 = base64Encode(audioBytes);
27 |
28 | // 建立WebSocket连接
29 | final channel = WebSocketChannel.connect(Uri.parse(authUrl));
30 |
31 | String transcription = '';
32 | final completer = Completer();
33 |
34 | try {
35 | EasyLoading.show(status: '【语音转文字中...】');
36 |
37 | await channel.ready;
38 | } catch (e) {
39 | // print("channel.ready error: $e");
40 | rethrow;
41 | }
42 |
43 | channel.stream.listen(
44 | (message) {
45 | ///??? 2024-08-03 保存的时候类型就是 String而不是Map
46 | if (message.runtimeType == String) {
47 | message = json.decode(message);
48 | }
49 |
50 | var data = XunfeiVoiceDictation.fromJson(message);
51 |
52 | transcription = data.data?.result?.ws
53 | ?.map((e) => e.cw?.map((e) => e.w).join())
54 | .join() ??
55 | "";
56 |
57 | // 2024-08-17??具体哪里释放还拿不准
58 | EasyLoading.dismiss();
59 |
60 | // 2024-09-03 这里语言转文字时可能有报错 Bad state: Future already completed
61 | if (!completer.isCompleted) {
62 | completer.complete(transcription);
63 | }
64 | },
65 | onDone: () {
66 | if (!completer.isCompleted) {
67 | completer.complete(transcription);
68 | }
69 | },
70 | onError: (error) {
71 | // print('WebSocket error: ${error.toString()}');
72 | completer.completeError(error);
73 |
74 | EasyLoading.dismiss();
75 | },
76 | );
77 |
78 | // 发送参数
79 | var params = {
80 | 'common': {
81 | 'app_id': getStoredUserKey(
82 | "USER_XFYUN_APP_ID",
83 | DefaultApiKeys.xfyunAppId,
84 | ),
85 | },
86 | 'business': {
87 | // 语种。zh_cn:中文(支持简单的英文识别)
88 | 'language': 'zh_cn',
89 | // 应用领域。iat:日常用语
90 | 'domain': 'iat',
91 | // 方言,当前仅在language为中文时,支持方言选择。
92 | 'accent': 'mandarin', // 默认中文普通话
93 | // 设置端点检测的静默时间,单位是毫秒。
94 | // 'vad_eos': 5000,
95 | // 动态修正返回参数
96 | // 'dwa': 'wpgs',
97 | },
98 | 'data': {
99 | // 音频的状态。0 :第一帧音频; 1 :中间的音频;2 :最后一帧音频,最后一帧必须要发送
100 | 'status': 0,
101 | // 音频的采样率支持16k和8k
102 | 'format': 'audio/L16;rate=16000',
103 | // 音频数据格式:raw、speex、speex-wb、lame
104 | 'encoding': 'raw',
105 | // 音频内容,采用base64编码
106 | 'audio': audioBase64,
107 | },
108 | };
109 | channel.sink.add(jsonEncode(params));
110 |
111 | // 发送结束信号
112 | var endParams = {
113 | 'data': {'status': 2},
114 | };
115 | channel.sink.add(jsonEncode(endParams));
116 |
117 | return completer.future;
118 | }
119 |
--------------------------------------------------------------------------------
/lib/common/components/cus_toggle_button_selector.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_screenutil/flutter_screenutil.dart';
3 |
4 | class CusToggleButtonSelector extends StatefulWidget {
5 | // 用于选择的列表
6 | final List items;
7 | // 被选中后的回调
8 | final Function(T) onItemSelected;
9 | // 用于从 T 类型的 item 中提取 label 字段
10 | final String Function(T) labelBuilder;
11 |
12 | const CusToggleButtonSelector({
13 | super.key,
14 | required this.items,
15 | required this.onItemSelected,
16 | required this.labelBuilder,
17 | });
18 |
19 | @override
20 | State createState() => _CusToggleButtonSelectorState();
21 | }
22 |
23 | class _CusToggleButtonSelectorState
24 | extends State> {
25 | // 选中状态列表
26 | List _selections = [];
27 |
28 | @override
29 | void initState() {
30 | super.initState();
31 |
32 | // 传入的列表默认选中第一个
33 | _selections = List.generate(widget.items.length, (index) {
34 | if (index == 0) {
35 | return true;
36 | }
37 | return false;
38 | });
39 | }
40 |
41 | @override
42 | Widget build(BuildContext context) {
43 | return SingleChildScrollView(
44 | scrollDirection: Axis.horizontal,
45 | child: Row(
46 | children: [
47 | ToggleButtons(
48 | isSelected: _selections,
49 | onPressed: (int index) {
50 | setState(() {
51 | for (int i = 0; i < _selections.length; i++) {
52 | _selections[i] = i == index;
53 | }
54 |
55 | widget.onItemSelected(widget.items[index]);
56 | });
57 | },
58 | // 这个是预设只有3个的情况,宽度各3分之一
59 | // constraints: BoxConstraints(minHeight: 36.sp, minWidth: 0.32.sw),
60 | // 一般长度不定,还是这个
61 | // 设置按钮的最小高度和最小宽度(得根据传入的label来判断)
62 | constraints: BoxConstraints(minHeight: 36.sp, minWidth: 80.sp),
63 | // 设置选中按钮的文本颜色
64 | selectedColor: Colors.white,
65 | // 设置选中按钮的边框颜色
66 | selectedBorderColor: Colors.blue,
67 | // 设置选中按钮的填充颜色
68 | fillColor: Colors.blue,
69 | // 设置按钮的圆角半径
70 | borderRadius: BorderRadius.circular(5.sp),
71 | // 设置按钮的边框宽度
72 | borderWidth: 1.sp,
73 | // 设置按钮的边框颜色
74 | borderColor: Colors.grey,
75 | children: List.generate(
76 | widget.items.length,
77 | (index) => Text(widget.labelBuilder(widget.items[index])),
78 | ),
79 | ),
80 | ],
81 | ),
82 | );
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/lib/common/components/feature_grid_card.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_screenutil/flutter_screenutil.dart';
3 |
4 | class FeatureGridCard extends StatelessWidget {
5 | final Widget targetPage;
6 | final String title;
7 | final IconData icon;
8 | final Color? accentColor;
9 | final bool isNew;
10 |
11 | const FeatureGridCard({
12 | super.key,
13 | required this.targetPage,
14 | required this.title,
15 | required this.icon,
16 | this.accentColor,
17 | this.isNew = false,
18 | });
19 |
20 | @override
21 | Widget build(BuildContext context) {
22 | final theme = Theme.of(context);
23 | final color = accentColor ?? theme.primaryColor;
24 |
25 | return Card(
26 | elevation: 3,
27 | shadowColor: color.withValues(alpha: 0.3),
28 | shape: RoundedRectangleBorder(
29 | borderRadius: BorderRadius.circular(20.sp),
30 | ),
31 | child: InkWell(
32 | borderRadius: BorderRadius.circular(20.sp),
33 | onTap: () {
34 | Navigator.of(context).push(
35 | MaterialPageRoute(builder: (context) => targetPage),
36 | );
37 | },
38 | child: Stack(
39 | children: [
40 | Padding(
41 | padding: EdgeInsets.all(16.sp),
42 | child: Column(
43 | mainAxisAlignment: MainAxisAlignment.center,
44 | crossAxisAlignment: CrossAxisAlignment.center,
45 | children: [
46 | Container(
47 | width: 64.sp,
48 | height: 64.sp,
49 | decoration: BoxDecoration(
50 | color: color.withValues(alpha: 0.1),
51 | borderRadius: BorderRadius.circular(16.sp),
52 | ),
53 | child: Icon(
54 | icon,
55 | color: color,
56 | size: 32.sp,
57 | ),
58 | ),
59 | SizedBox(height: 16.sp),
60 | Text(
61 | title,
62 | style: TextStyle(
63 | fontSize: 16.sp,
64 | fontWeight: FontWeight.w600,
65 | ),
66 | textAlign: TextAlign.center,
67 | ),
68 | ],
69 | ),
70 | ),
71 | if (isNew)
72 | Positioned(
73 | top: 12.sp,
74 | right: 12.sp,
75 | child: Container(
76 | padding: EdgeInsets.symmetric(
77 | horizontal: 8.sp,
78 | vertical: 4.sp,
79 | ),
80 | decoration: BoxDecoration(
81 | color: Colors.red,
82 | borderRadius: BorderRadius.circular(12.sp),
83 | boxShadow: [
84 | BoxShadow(
85 | color: Colors.red.withValues(alpha: 0.3),
86 | blurRadius: 4,
87 | offset: const Offset(0, 2),
88 | ),
89 | ],
90 | ),
91 | child: Text(
92 | '新',
93 | style: TextStyle(
94 | color: Colors.white,
95 | fontSize: 12.sp,
96 | fontWeight: FontWeight.bold,
97 | ),
98 | ),
99 | ),
100 | ),
101 | ],
102 | ),
103 | ),
104 | );
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/lib/common/components/image_pick_and_preview_area.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 |
3 | import 'package:flutter/material.dart';
4 | import 'package:flutter_screenutil/flutter_screenutil.dart';
5 | import 'package:image_picker/image_picker.dart';
6 |
7 | import 'tool_widget.dart';
8 |
9 | // 新版本可以用在图片选择和预览的地方
10 | class ImagePickAndPreviewArea extends StatelessWidget {
11 | final Function(ImageSource) imageSelectedHandle;
12 | final Function() imageClearHandle;
13 | final File? selectedImage;
14 | final String imagePlaceholder;
15 |
16 | const ImagePickAndPreviewArea({
17 | super.key,
18 | required this.imageSelectedHandle,
19 | required this.imageClearHandle,
20 | required this.selectedImage,
21 | this.imagePlaceholder = "请选择参考图",
22 | });
23 |
24 | @override
25 | Widget build(BuildContext context) {
26 | return Container(
27 | height: 100.sp,
28 | margin: EdgeInsets.fromLTRB(5.sp, 5.sp, 5.sp, 0),
29 | decoration: BoxDecoration(
30 | border: Border.all(color: Colors.grey, width: 1.sp),
31 | borderRadius: BorderRadius.circular(5.sp),
32 | ),
33 | child: Row(
34 | mainAxisAlignment: MainAxisAlignment.center,
35 | children: [
36 | IconButton(
37 | onPressed: () {
38 | showDialog(
39 | context: context,
40 | builder: (BuildContext context) {
41 | return AlertDialog(
42 | title: Text(
43 | "选择图片来源",
44 | style: TextStyle(fontSize: 18.sp),
45 | ),
46 | actions: [
47 | TextButton(
48 | onPressed: () {
49 | Navigator.of(context).pop();
50 | imageSelectedHandle(ImageSource.camera);
51 | },
52 | child: Text(
53 | "拍照",
54 | style: TextStyle(fontSize: 16.sp),
55 | ),
56 | ),
57 | TextButton(
58 | onPressed: () {
59 | Navigator.of(context).pop();
60 | imageSelectedHandle(ImageSource.gallery);
61 | },
62 | child: Text(
63 | "相册",
64 | style: TextStyle(fontSize: 16.sp),
65 | ),
66 | ),
67 | ],
68 | );
69 | },
70 | );
71 | },
72 | icon: const Icon(Icons.file_upload),
73 | ),
74 | Expanded(
75 | flex: 3,
76 | child: buildImageView(
77 | selectedImage,
78 | context,
79 | imagePlaceholder: imagePlaceholder,
80 | ),
81 | ),
82 | if (selectedImage != null)
83 | IconButton(
84 | onPressed: imageClearHandle,
85 | icon: const Icon(Icons.clear),
86 | ),
87 | ],
88 | ),
89 | );
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/lib/common/components/loading_overlay.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_screenutil/flutter_screenutil.dart';
3 |
4 | class LoadingOverlay {
5 | static OverlayEntry? _overlayEntry;
6 |
7 | static void show(BuildContext context, {VoidCallback? onCancel}) {
8 | if (_overlayEntry != null) return;
9 |
10 | OverlayState overlayState = Overlay.of(context);
11 | _overlayEntry = OverlayEntry(
12 | builder: (context) {
13 | return Container(
14 | width: double.infinity,
15 | height: double.infinity,
16 | color: Colors.black.withValues(alpha: 0.8),
17 | child: Center(
18 | child: Column(
19 | crossAxisAlignment: CrossAxisAlignment.center,
20 | mainAxisAlignment: MainAxisAlignment.center,
21 | children: [
22 | const CircularProgressIndicator(color: Colors.white),
23 | SizedBox(height: 10.sp),
24 | Text(
25 | "图片或视频生成中",
26 | style: TextStyle(fontSize: 16.sp, color: Colors.white),
27 | ),
28 | Text(
29 | "请耐心等待一会儿",
30 | style: TextStyle(fontSize: 16.sp, color: Colors.white),
31 | ),
32 | Text(
33 | "请勿退出当前页面",
34 | style: TextStyle(fontSize: 16.sp, color: Colors.white),
35 | ),
36 | SizedBox(height: 16.sp),
37 | ElevatedButton(
38 | onPressed: () {
39 | hide();
40 | onCancel?.call();
41 | },
42 | child: const Text("取消"),
43 | ),
44 | ],
45 | ),
46 | ),
47 | );
48 | },
49 | );
50 | overlayState.insert(_overlayEntry!);
51 | }
52 |
53 | static void hide() {
54 | _overlayEntry?.remove();
55 | _overlayEntry = null;
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/lib/common/components/modern_feature_card.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_screenutil/flutter_screenutil.dart';
3 |
4 | class ModernFeatureCard extends StatelessWidget {
5 | final Widget targetPage;
6 | final String title;
7 | final String subtitle;
8 | final IconData icon;
9 | final Color? accentColor;
10 | final bool showArrow;
11 |
12 | const ModernFeatureCard({
13 | super.key,
14 | required this.targetPage,
15 | required this.title,
16 | required this.subtitle,
17 | required this.icon,
18 | this.accentColor,
19 | this.showArrow = true,
20 | });
21 |
22 | @override
23 | Widget build(BuildContext context) {
24 | final theme = Theme.of(context);
25 | final textColor = theme.textTheme.titleLarge?.color ?? Colors.black;
26 | final color = accentColor ?? theme.primaryColor;
27 |
28 | return Card(
29 | elevation: 0,
30 | shape: RoundedRectangleBorder(
31 | borderRadius: BorderRadius.circular(16.sp),
32 | side: BorderSide(color: Colors.grey.shade200, width: 1),
33 | ),
34 | child: InkWell(
35 | borderRadius: BorderRadius.circular(16.sp),
36 | onTap: () {
37 | Navigator.of(context).push(
38 | MaterialPageRoute(builder: (context) => targetPage),
39 | );
40 | },
41 | child: Padding(
42 | padding: EdgeInsets.all(20.sp),
43 | child: Row(
44 | children: [
45 | // 左侧图标
46 | Container(
47 | width: 56.sp,
48 | height: 56.sp,
49 | decoration: BoxDecoration(
50 | color: color.withValues(alpha: 0.1),
51 | borderRadius: BorderRadius.circular(12.sp),
52 | ),
53 | child: Icon(
54 | icon,
55 | color: color,
56 | size: 28.sp,
57 | ),
58 | ),
59 | SizedBox(width: 16.sp),
60 |
61 | // 中间文本
62 | Expanded(
63 | child: Column(
64 | crossAxisAlignment: CrossAxisAlignment.start,
65 | children: [
66 | Text(
67 | title,
68 | style: TextStyle(
69 | fontSize: 18.sp,
70 | fontWeight: FontWeight.bold,
71 | color: textColor,
72 | ),
73 | ),
74 | SizedBox(height: 4.sp),
75 | Text(
76 | subtitle,
77 | style: TextStyle(
78 | fontSize: 14.sp,
79 | color: Colors.grey.shade600,
80 | ),
81 | maxLines: 2,
82 | overflow: TextOverflow.ellipsis,
83 | ),
84 | ],
85 | ),
86 | ),
87 |
88 | // 右侧箭头
89 | if (showArrow)
90 | Icon(
91 | Icons.arrow_forward_ios,
92 | size: 16.sp,
93 | color: Colors.grey.shade400,
94 | ),
95 | ],
96 | ),
97 | ),
98 | ),
99 | );
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/lib/common/components/simple_marquee_or_text.dart:
--------------------------------------------------------------------------------
1 | // text文字超过一行则水平滚动(预设特性不可修改),不超过一行则单纯Text显示
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter_screenutil/flutter_screenutil.dart';
4 | import 'package:marquee/marquee.dart';
5 |
6 | class SimpleMarqueeOrText extends StatefulWidget {
7 | const SimpleMarqueeOrText({
8 | super.key,
9 | required this.data,
10 | required this.style,
11 | this.velocity,
12 | this.showLines,
13 | this.height,
14 | this.width,
15 | this.textAlignment = Alignment.center,
16 | });
17 |
18 | final String data;
19 | final TextStyle style;
20 | // 传入速度
21 | final double? velocity;
22 | // 传入显示的行数(大于此才滚动)
23 | final int? showLines;
24 | // 滚动条的高度
25 | final double? height;
26 | // 滚动条的宽度
27 | final double? width;
28 | // 文字的显示位置
29 | final AlignmentGeometry? textAlignment;
30 |
31 | @override
32 | State createState() => _SimpleMarqueeOrTextState();
33 | }
34 |
35 | class _SimpleMarqueeOrTextState extends State {
36 | @override
37 | Widget build(BuildContext context) {
38 | return LayoutBuilder(builder: (context, constraints) {
39 | // 这里是获取文本的行数
40 | final span = TextSpan(text: widget.data, style: widget.style);
41 | final tp = TextPainter(text: span, textDirection: TextDirection.ltr);
42 |
43 | // tp.layout(maxWidth: constraints.maxWidth);
44 | // 注意,如果是builder的约束,默认为设备的总宽度(测试机为360.sp),不是全宽跑马灯的话宽度要自定,才能得到准确的行数
45 | // tp.layout(maxWidth: widget.width ?? 300.sp); // 和下面的sizedbox宽度一致
46 |
47 | // 看起来上面的不太准确,这个最小宽度可能是本widget的父widget的宽度。但是在播放详情页面标题和专辑信息时,传过来就是0.0。
48 | // 所以,要么在使用的时候指定父组件,要么就在这里设定一个最小值。
49 | // 综上优先级:手动传入宽度 > 大于50的父组件 > 预设的300
50 | var showWidth = widget.width ??
51 | (constraints.minWidth > 50.sp ? constraints.minWidth : 300.sp);
52 | tp.layout(maxWidth: showWidth);
53 | final numLines = tp.computeLineMetrics().length;
54 |
55 | return SizedBox(
56 | height: widget.height ?? 30.sp,
57 | width: showWidth,
58 | child: numLines > (widget.showLines ?? 1)
59 | ? Marquee(
60 | text: "${widget.data} ", // 超过一行时滚动的字串加点空白以便识别文字起止
61 | style: widget.style,
62 | velocity: widget.velocity ?? 10.0, // 滚动速度
63 | )
64 | : Align(
65 | alignment: widget.textAlignment ?? Alignment.center,
66 | child: Text(widget.data, style: widget.style),
67 | ),
68 | );
69 | });
70 | }
71 | }
--------------------------------------------------------------------------------
/lib/common/components/sounds_message_button/button_widget/custom_canvas.dart:
--------------------------------------------------------------------------------
1 | part of 'sounds_message_button.dart';
2 |
3 | /// 自定义画布
4 | /// 按住说话下那个小扇形
5 | class _RecordingPainter extends CustomPainter {
6 | final bool isFocus;
7 | _RecordingPainter(this.isFocus);
8 |
9 | @override
10 | void paint(Canvas canvas, Size size) async {
11 | final bgOvalRect = Rect.fromCenter(
12 | center: Offset(size.width / 2, size.height * 3 / 2),
13 | width: size.width * 1.8,
14 | height: size.height * 3,
15 | );
16 |
17 | final paint = Paint()
18 | ..color = const Color(0xff393939)
19 | ..style = PaintingStyle.fill;
20 | Path path = Path()..addOval(bgOvalRect);
21 |
22 | if (isFocus) {
23 | paint.color = const Color(0xffb0b0b0);
24 | canvas.drawPath(path, paint);
25 |
26 | final scale = (size.height * 3 - 8.sp) / (size.height * 3);
27 |
28 | final bgShaderRect = Rect.fromCenter(
29 | center: bgOvalRect.center,
30 | width: bgOvalRect.width * scale,
31 | height: bgOvalRect.height * scale,
32 | );
33 | canvas.drawPath(
34 | Path()..addOval(bgShaderRect),
35 | Paint()
36 | ..shader = ui.Gradient.linear(
37 | Offset(size.width / 2, size.height),
38 | Offset(size.width / 2, 0),
39 | [
40 | const Color(0xffd5d5d5),
41 | const Color(0xff999999),
42 | ],
43 | ),
44 | );
45 | } else {
46 | canvas.drawPath(path, paint);
47 | }
48 | }
49 |
50 | @override
51 | bool shouldRepaint(_RecordingPainter oldDelegate) => true;
52 |
53 | @override
54 | bool shouldRebuildSemantics(_RecordingPainter oldDelegate) => false;
55 | }
56 |
57 | /// 绘制气泡
58 | /// 录音振幅或者语言转文字那个像对话框的画布
59 | class _BubblePainter extends CustomPainter {
60 | final RecordingMaskOverlayData data;
61 | final SoundsMessageStatus status;
62 | final double paddingSide;
63 | _BubblePainter(this.data, this.status, this.paddingSide);
64 |
65 | @override
66 | void paint(Canvas canvas, Size size) {
67 | final paint = Paint()
68 | ..color = const Color(0xff95ec6a)
69 | ..style = PaintingStyle.fill;
70 |
71 | if (status == SoundsMessageStatus.canceling) {
72 | paint.color = const Color(0xfffa5251);
73 | }
74 |
75 | final rect = Offset.zero & size;
76 | final rrect = RRect.fromRectAndRadius(rect, const Radius.circular(12));
77 |
78 | final path = Path();
79 |
80 | // 三角形
81 | var dx = rect.center.dx;
82 | if (status == SoundsMessageStatus.textProcessing ||
83 | status == SoundsMessageStatus.textProcessed) {
84 | dx = size.width + 24.sp - paddingSide - data.iconFocusSize / 2;
85 | }
86 | path.moveTo(dx - 7.sp, size.height);
87 | path.lineTo(dx, size.height + 6.sp);
88 | path.lineTo(dx + 7.sp, size.height);
89 |
90 | // 矩形
91 | path.addRRect(rrect);
92 |
93 | canvas.drawPath(path, paint);
94 | }
95 |
96 | @override
97 | bool shouldRepaint(_BubblePainter oldDelegate) => false;
98 |
99 | @override
100 | bool shouldRebuildSemantics(_BubblePainter oldDelegate) => false;
101 | }
102 |
--------------------------------------------------------------------------------
/lib/common/components/sounds_message_button/utils/recording_mask_overlay_data.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | /// 当正在录制的时候,页面显示的 `OverlayEntry`
4 | class RecordingMaskOverlayData {
5 | /// 底部圆形的高度
6 | final double sendAreaHeight;
7 |
8 | /// 圆形图形大小
9 | final double iconSize;
10 |
11 | /// 圆形图形大小 - 响应
12 | final double iconFocusSize;
13 |
14 | /// 录音气泡大小
15 | // final EdgeInsets soundsMargin;
16 |
17 | /// 圆形图形颜色
18 | final Color iconColor;
19 |
20 | /// 圆形图形颜色 - 响应
21 | final Color iconFocusColor;
22 |
23 | /// 文字颜色
24 | final Color iconTxtColor;
25 |
26 | /// 文字颜色 - 响应
27 | final Color iconFocusTxtColor;
28 |
29 | /// 遮罩文字样式
30 | final TextStyle maskTxtStyle;
31 |
32 | const RecordingMaskOverlayData({
33 | this.sendAreaHeight = 120,
34 | this.iconSize = 68,
35 | this.iconFocusSize = 80,
36 | this.iconColor = const Color(0xff393939),
37 | this.iconFocusColor = const Color(0xffffffff),
38 | this.iconTxtColor = const Color(0xff909090),
39 | this.iconFocusTxtColor = const Color(0xff000000),
40 | // this.soundsMargin = const EdgeInsets.symmetric(horizontal: 24),
41 | this.maskTxtStyle = const TextStyle(
42 | fontSize: 14,
43 | fontWeight: FontWeight.bold,
44 | color: Color(0xff909090),
45 | ),
46 | });
47 | }
48 |
--------------------------------------------------------------------------------
/lib/common/components/style_grid_selector.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_screenutil/flutter_screenutil.dart';
3 |
4 | ///
5 | /// 文生图样式选择grid
6 | ///
7 | class StyleGrid extends StatelessWidget {
8 | final List imageUrls;
9 | final List labels;
10 | final List subLabels;
11 | final int selectedIndex;
12 | final Function(int) onTap;
13 | final int crossAxisCount;
14 |
15 | const StyleGrid({
16 | super.key,
17 | required this.imageUrls,
18 | required this.labels,
19 | required this.subLabels,
20 | required this.selectedIndex,
21 | required this.onTap,
22 | this.crossAxisCount = 4,
23 | });
24 |
25 | @override
26 | Widget build(BuildContext context) {
27 | return GridView.count(
28 | crossAxisCount: crossAxisCount,
29 | shrinkWrap: true,
30 | physics: const NeverScrollableScrollPhysics(),
31 | children: List.generate(imageUrls.length, (index) {
32 | return GridTile(
33 | child: GestureDetector(
34 | onTap: () => onTap(index),
35 | child: Container(
36 | decoration: BoxDecoration(
37 | border: Border.all(
38 | color:
39 | selectedIndex == index ? Colors.blue : Colors.transparent,
40 | width: 3.sp,
41 | ),
42 | borderRadius: BorderRadius.circular(5.0),
43 | ),
44 | child: ImageStack(
45 | url: imageUrls[index],
46 | label: labels[index],
47 | subLabel: subLabels[index],
48 | ),
49 | ),
50 | ),
51 | );
52 | }),
53 | );
54 | }
55 | }
56 |
57 | ///
58 | /// 文生图每种样式展示小部件
59 | ///
60 | class ImageStack extends StatelessWidget {
61 | final String url;
62 | final String label;
63 | final String subLabel;
64 |
65 | const ImageStack({
66 | super.key,
67 | required this.url,
68 | required this.label,
69 | required this.subLabel,
70 | });
71 |
72 | @override
73 | Widget build(BuildContext context) {
74 | return Stack(
75 | fit: StackFit.expand,
76 | children: [
77 | Positioned.fill(
78 | child: Image.asset(
79 | url,
80 | fit: BoxFit.cover,
81 | errorBuilder: (BuildContext context, Object exception,
82 | StackTrace? stackTrace) {
83 | return Container(color: Colors.grey.shade300);
84 | },
85 | ),
86 | ),
87 | Align(
88 | alignment: Alignment.bottomCenter,
89 | child: RichText(
90 | softWrap: true,
91 | textAlign: TextAlign.center,
92 | overflow: TextOverflow.ellipsis,
93 | maxLines: 3,
94 | text: TextSpan(
95 | children: [
96 | TextSpan(
97 | text: label,
98 | style: TextStyle(
99 | fontSize: 10.sp,
100 | fontWeight: FontWeight.bold,
101 | ),
102 | ),
103 | TextSpan(
104 | text: "\n$subLabel",
105 | style: TextStyle(fontWeight: FontWeight.bold, fontSize: 9.sp),
106 | ),
107 | ],
108 | ),
109 | ),
110 | ),
111 | ],
112 | );
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/lib/common/constants/default_image_generation_models.dart:
--------------------------------------------------------------------------------
1 | import '../llm_spec/cus_brief_llm_model.dart';
2 | import '../llm_spec/constant_llm_enum.dart';
3 |
4 | /// 内置的默认模型列表
5 | final defaultImageGenerationModels = [
6 | /// 智谱AI
7 | CusBriefLLMSpec(
8 | ApiPlatform.zhipu,
9 | 'cogview-3-flash',
10 | LLModelType.tti,
11 | name: 'CogView-3-Flash',
12 | isFree: true,
13 | costPer: 0.0,
14 | cusLlmSpecId: 'zhipu_cogview_3_flash_builtin',
15 | gmtRelease: DateTime.parse('1970-01-01'),
16 | gmtCreate: DateTime.now(),
17 | isBuiltin: true,
18 | ),
19 | ];
20 |
--------------------------------------------------------------------------------
/lib/common/constants/default_video_generation_models.dart:
--------------------------------------------------------------------------------
1 | import '../llm_spec/cus_brief_llm_model.dart';
2 | import '../llm_spec/constant_llm_enum.dart';
3 |
4 | final defaultVideoGenerationModels = [
5 | CusBriefLLMSpec(
6 | ApiPlatform.zhipu,
7 | 'cogvideox-flash',
8 | LLModelType.video,
9 | name: 'cogvideox-flash',
10 | isFree: true,
11 | costPer: 0,
12 | cusLlmSpecId: 'zhipu_cogvideox_flash_builtin',
13 | gmtRelease: DateTime.parse('1970-01-01'),
14 | gmtCreate: DateTime.now(),
15 | isBuiltin: true,
16 | ),
17 | ];
18 |
--------------------------------------------------------------------------------
/lib/common/llm_spec/constant_llm_enum.dart:
--------------------------------------------------------------------------------
1 | // ignore_for_file: constant_identifier_names, non_constant_identifier_names
2 |
3 | ///
4 | /// 这里是付费的大模型通用
5 | ///
6 | /// 定义云平台
7 | /// 2024-07-08 这里的AI助手,估计只需要这个付费的就好了
8 | ///
9 | /// 因为零一万物的API兼容openAI的api,后续付费的应该都是这样的,而不是之前免费的三大平台乱七八糟的
10 | ///
11 | enum ApiPlatform {
12 | // 用户使用的模型不属于预设平台(比如谷歌等)
13 | // 那么就是统一custom,并在自定义模型中直接新增url、apikey等栏位去取用
14 | // 这个不是默认的有效平台,不需要用户导入啥的
15 | custom,
16 |
17 | aliyun,
18 | baidu,
19 | tencent,
20 |
21 | deepseek,
22 | lingyiwanwu,
23 | zhipu,
24 |
25 | siliconCloud,
26 | infini,
27 |
28 | // 2025-03-24 火山引擎默认调用和关联应用(比如配置了联网搜索)使用的url不一样
29 | // 避免出现冲突,分成两个且互不包含
30 | volcengine,
31 | volcesBot,
32 | }
33 |
34 | // 用户自行导入密钥时,json文件的key
35 | // 2025-03-14 这里的label,需要完整包含上面平台的枚举值,
36 | // 否则无法用户单个添加指定平台的模型时,正确识别
37 | enum ApiPlatformAKLabel {
38 | // 传统主流多模型平台(自研+第三方)
39 | USER_ALIYUN_API_KEY,
40 | USER_BAIDU_API_KEY_V2,
41 | USER_TENCENT_API_KEY,
42 |
43 | // 自平台(只有自研)
44 | USER_DEEPSEEK_API_KEY, // 深度求索
45 | USER_LINGYIWANWU_API_KEY, // 零一万物
46 | USER_ZHIPU_API_KEY, // 智谱AI
47 |
48 | // 第三方多模型平台(只有第三方)
49 | USER_SILICONCLOUD_API_KEY, // 硅基流动
50 | USER_INFINI_GEN_STUDIO_API_KEY, // 无问芯穹的genStudio
51 | USER_VOLCENGINE_API_KEY, // 火山引擎
52 | USER_VOLCESBOT_API_KEY, // 火山引擎的bot
53 |
54 | // 讯飞, 语音转写需要
55 | USER_XFYUN_APP_ID,
56 | USER_XFYUN_API_KEY,
57 | USER_XFYUN_API_SECRET,
58 | }
59 |
60 | // 模型对应的中文名
61 | final Map CP_NAME_MAP = {
62 | ApiPlatform.aliyun: '阿里',
63 | ApiPlatform.baidu: '百度',
64 | ApiPlatform.tencent: '腾讯',
65 | ApiPlatform.deepseek: '深度求索',
66 | ApiPlatform.lingyiwanwu: '零一万物',
67 | ApiPlatform.zhipu: '智谱',
68 | ApiPlatform.siliconCloud: '硅基流动',
69 | ApiPlatform.infini: '无问芯穹',
70 | ApiPlatform.volcengine: '火山引擎',
71 | ApiPlatform.volcesBot: '火山Bot',
72 | ApiPlatform.custom: '[自定义]',
73 | };
74 |
75 | // 大模型的分类,在不同页面可以用作模型的筛选
76 | enum LLModelType {
77 | cc, // Chat Completions
78 | // 2025-03-06 推理模型(深度思考)有思考过程,且支持的参数和对话模型差异很大,所以单独分类
79 | reasoner,
80 | vision, // 视觉大模型
81 | vision_reasoner, // 视觉推理
82 |
83 | // 图片生成大模型分3种: 单独文生图、单独图生图、文生图生都可以
84 | tti, // Text To Image
85 | iti, // Image To Image
86 | image,
87 |
88 | // 视频生成大模型分3种: 单独文生视频、单独图生视频、文生图生都可以
89 | ttv, // Text To Video
90 | itv, // Image To Video
91 | video,
92 | }
93 |
94 | // 模型类型对应的中文名
95 | final Map MT_NAME_MAP = {
96 | LLModelType.cc: '文本对话',
97 | LLModelType.reasoner: '深度思考',
98 | LLModelType.vision: '图片解读',
99 | LLModelType.vision_reasoner: '视觉推理',
100 | LLModelType.tti: '文本生图',
101 | LLModelType.iti: '图片生图',
102 | LLModelType.image: '图片生成',
103 | LLModelType.ttv: '文生视频',
104 | LLModelType.itv: '图生视频',
105 | LLModelType.video: '视频生成',
106 | };
107 |
--------------------------------------------------------------------------------
/lib/common/llm_spec/cus_brief_llm_model.g.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 |
3 | part of 'cus_brief_llm_model.dart';
4 |
5 | // **************************************************************************
6 | // JsonSerializableGenerator
7 | // **************************************************************************
8 |
9 | CusBriefLLMSpec _$CusBriefLLMSpecFromJson(Map json) =>
10 | CusBriefLLMSpec(
11 | $enumDecode(_$ApiPlatformEnumMap, json['platform']),
12 | json['model'] as String,
13 | $enumDecode(_$LLModelTypeEnumMap, json['modelType']),
14 | name: json['name'] as String?,
15 | isFree: json['isFree'] as bool?,
16 | inputPrice: (json['inputPrice'] as num?)?.toDouble(),
17 | outputPrice: (json['outputPrice'] as num?)?.toDouble(),
18 | costPer: (json['costPer'] as num?)?.toDouble(),
19 | contextLength: (json['contextLength'] as num?)?.toInt(),
20 | cusLlmSpecId: json['cusLlmSpecId'] as String,
21 | gmtRelease: json['gmtRelease'] == null
22 | ? null
23 | : DateTime.parse(json['gmtRelease'] as String),
24 | gmtCreate: json['gmtCreate'] == null
25 | ? null
26 | : DateTime.parse(json['gmtCreate'] as String),
27 | isBuiltin: json['isBuiltin'] as bool? ?? false,
28 | baseUrl: json['baseUrl'] as String?,
29 | apiKey: json['apiKey'] as String?,
30 | description: json['description'] as String?,
31 | );
32 |
33 | Map _$CusBriefLLMSpecToJson(CusBriefLLMSpec instance) =>
34 | {
35 | 'cusLlmSpecId': instance.cusLlmSpecId,
36 | 'platform': _$ApiPlatformEnumMap[instance.platform]!,
37 | 'model': instance.model,
38 | 'modelType': _$LLModelTypeEnumMap[instance.modelType]!,
39 | 'name': instance.name,
40 | 'isFree': instance.isFree,
41 | 'inputPrice': instance.inputPrice,
42 | 'outputPrice': instance.outputPrice,
43 | 'costPer': instance.costPer,
44 | 'contextLength': instance.contextLength,
45 | 'gmtRelease': instance.gmtRelease?.toIso8601String(),
46 | 'gmtCreate': instance.gmtCreate?.toIso8601String(),
47 | 'isBuiltin': instance.isBuiltin,
48 | 'baseUrl': instance.baseUrl,
49 | 'apiKey': instance.apiKey,
50 | 'description': instance.description,
51 | };
52 |
53 | const _$ApiPlatformEnumMap = {
54 | ApiPlatform.custom: 'custom',
55 | ApiPlatform.aliyun: 'aliyun',
56 | ApiPlatform.baidu: 'baidu',
57 | ApiPlatform.tencent: 'tencent',
58 | ApiPlatform.deepseek: 'deepseek',
59 | ApiPlatform.lingyiwanwu: 'lingyiwanwu',
60 | ApiPlatform.zhipu: 'zhipu',
61 | ApiPlatform.siliconCloud: 'siliconCloud',
62 | ApiPlatform.infini: 'infini',
63 | ApiPlatform.volcengine: 'volcengine',
64 | ApiPlatform.volcesBot: 'volcesBot',
65 | };
66 |
67 | const _$LLModelTypeEnumMap = {
68 | LLModelType.cc: 'cc',
69 | LLModelType.reasoner: 'reasoner',
70 | LLModelType.vision: 'vision',
71 | LLModelType.vision_reasoner: 'vision_reasoner',
72 | LLModelType.tti: 'tti',
73 | LLModelType.iti: 'iti',
74 | LLModelType.image: 'image',
75 | LLModelType.ttv: 'ttv',
76 | LLModelType.itv: 'itv',
77 | LLModelType.video: 'video',
78 | };
79 |
--------------------------------------------------------------------------------
/lib/common/utils/advanced_options_utils.dart:
--------------------------------------------------------------------------------
1 | // ignore_for_file: avoid_print
2 |
3 | import 'package:flutter/material.dart';
4 | import 'package:flutter_screenutil/flutter_screenutil.dart';
5 | import '../components/advanced_options_bottom_sheet.dart';
6 | import '../components/advanced_options_panel.dart';
7 | import '../llm_spec/constant_llm_enum.dart';
8 | import '../constants/advanced_options_presets.dart';
9 |
10 | /// 高级选项结果
11 | class AdvancedOptionsResult {
12 | /// 是否启用高级选项
13 | final bool enabled;
14 |
15 | /// 高级选项参数值
16 | final Map options;
17 |
18 | const AdvancedOptionsResult({
19 | required this.enabled,
20 | required this.options,
21 | });
22 | }
23 |
24 | /// 高级选项工具类
25 | class AdvancedOptionsUtils {
26 | /// 显示高级选项弹窗
27 | static Future showAdvancedOptions({
28 | required BuildContext context,
29 | required ApiPlatform platform,
30 | required LLModelType modelType,
31 | required bool currentEnabled,
32 | required Map currentOptions,
33 | }) async {
34 | final List options =
35 | AdvancedOptionsManager.getAvailableOptions(platform, modelType);
36 |
37 | for (var i = 0; i < options.length; i++) {
38 | print('显示高级选项弹窗中的参数 $i: ${options[i].key}');
39 | }
40 |
41 | if (options.isEmpty) {
42 | if (!context.mounted) return null;
43 | ScaffoldMessenger.of(context).showSnackBar(
44 | const SnackBar(content: Text('当前模型没有可配置的高级参数')),
45 | );
46 | return null;
47 | }
48 |
49 | return await showModalBottomSheet(
50 | context: context,
51 | isScrollControlled: true, // 允许弹窗内容滚动
52 | shape: RoundedRectangleBorder(
53 | borderRadius: BorderRadius.vertical(top: Radius.circular(15.sp)),
54 | ),
55 | builder: (context) {
56 | return DraggableScrollableSheet(
57 | initialChildSize: 0.7, // 初始高度为屏幕的70%
58 | minChildSize: 0.5, // 最小高度为50%
59 | maxChildSize: 0.95, // 最大高度为95%
60 | expand: false,
61 | builder: (context, scrollController) {
62 | return AdvancedOptionsBottomSheet(
63 | enabled: currentEnabled,
64 | currentOptions: currentOptions,
65 | options: options,
66 | );
67 | },
68 | );
69 | },
70 | );
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/lib/common/utils/cus_logger.dart:
--------------------------------------------------------------------------------
1 | // ignore_for_file: avoid_print
2 |
3 | import 'package:logger/logger.dart';
4 |
5 | class NoLimitLogPrinter extends LogPrinter {
6 | final LogPrinter _defaultPrinter;
7 |
8 | NoLimitLogPrinter(this._defaultPrinter);
9 |
10 | @override
11 | List log(LogEvent event) {
12 | final lines = _defaultPrinter.log(event);
13 | return lines.map((line) => line.replaceAll('\n', ' ')).toList();
14 | }
15 | }
16 |
17 | class NoLimitLogOutput extends LogOutput {
18 | @override
19 | void output(OutputEvent event) {
20 | for (var line in event.lines) {
21 | print(line); // 使用 print 输出日志,没有长度限制
22 | }
23 | }
24 | }
25 |
26 | var logger = Logger(
27 | output: NoLimitLogOutput(), // 使用自定义的 LogOutput
28 | printer: NoLimitLogPrinter(PrettyPrinter()), // 使用自定义的 LogPrinter
29 | );
30 |
--------------------------------------------------------------------------------
/lib/common/utils/db_tools/ddl_brief_ai_tool.dart:
--------------------------------------------------------------------------------
1 | // ignore_for_file: constant_identifier_names
2 |
3 | import 'init_db.dart';
4 |
5 | /// 数据库中【简洁版AI工具】相关表的创建
6 | /// 2025-02-28 之后AI工具相关都只有简洁版本了
7 | class BriefAIToolDdl {
8 | /// 2025-02-24 新版本的对话留存表栏位和旧版本差不多,只是表名不同,不互相干扰
9 | static const tableNameOfBriefChatHistory =
10 | '${DB_TABLE_PREFIX}brief_chat_history';
11 |
12 | static const String ddlForBriefChatHistory = """
13 | CREATE TABLE $tableNameOfBriefChatHistory (
14 | uuid TEXT NOT NULL,
15 | title TEXT NOT NULL,
16 | gmtCreate TEXT NOT NULL,
17 | gmtModified TEXT NOT NULL,
18 | messages TEXT NOT NULL,
19 | modelType TEXT NOT NULL,
20 | llmSpec TEXT NOT NULL,
21 | PRIMARY KEY(uuid)
22 | );
23 | """;
24 |
25 | // 2025-02-14 新的简洁版生成式任务记录
26 | // 2025-02-19 图片生成、视频生成任务都放在这里面,后续可能音频生成相关的也放在这里
27 | // 图片可能直接返回结果,那么task相关栏位就为空
28 | // 但阿里云的图片和所有的视频生成,都是先返回任务提交结果,然后查询任务状态,这些内容都放在这个生成记录中
29 | // 栏位只保留必要的,其他参数通过otherParams字段存入json
30 | // 不同平台的任务状态枚举不一样,所以除了存放taskStatus,还存放了是否完成等栏位
31 | // isSuccess + isProcessing + isFailed ,都是前端根据taskStatus来判断,方便直接查询
32 | // 因为多种媒体资源生成任务和结果都在这里,所以需要指定调用模型的类型modelType和模型信息llmSpec
33 | static const tableNameOfMediaGenerationHistory =
34 | '${DB_TABLE_PREFIX}brief_media_generation_history';
35 |
36 | static const String ddlForMediaGenerationHistory = """
37 | CREATE TABLE $tableNameOfMediaGenerationHistory (
38 | requestId TEXT NOT NULL,
39 | prompt TEXT NOT NULL,
40 | negativePrompt TEXT,
41 | refImageUrls TEXT,
42 | modelType TEXT NOT NULL,
43 | llmSpec TEXT NOT NULL,
44 | taskId TEXT,
45 | taskStatus TEXT,
46 | isSuccess INTEGER,
47 | isProcessing INTEGER,
48 | isFailed INTEGER,
49 | imageUrls TEXT,
50 | videoUrls TEXT,
51 | audioUrls TEXT,
52 | otherParams TEXT,
53 | gmtCreate TEXT NOT NULL,
54 | gmtModified TEXT,
55 | PRIMARY KEY(requestId)
56 | );
57 | """;
58 |
59 | // 2025-02-14 新的简洁版用户自动导入的模型配置
60 | // (栏位更新,和旧版本的cus_llm_spec区分开,不影响旧版本业务)
61 | static const tableNameOfCusBriefLlmSpec =
62 | '${DB_TABLE_PREFIX}brief_cus_llm_spec';
63 |
64 | static const String ddlForCusBriefLlmSpec = """
65 | CREATE TABLE $tableNameOfCusBriefLlmSpec (
66 | cusLlmSpecId TEXT NOT NULL,
67 | platform TEXT NOT NULL,
68 | model TEXT NOT NULL,
69 | name TEXT,
70 | contextLength INTEGER,
71 | isFree INTEGER,
72 | modelType TEXT NOT NULL,
73 | inputPrice REAL,
74 | outputPrice REAL,
75 | costPer REAL,
76 | gmtRelease TEXT,
77 | gmtCreate TEXT NOT NULL,
78 | isBuiltin INTEGER NOT NULL,
79 | baseUrl TEXT,
80 | apiKey TEXT,
81 | description TEXT,
82 | PRIMARY KEY(cusLlmSpecId),
83 | UNIQUE(platform,model,modelType)
84 | );
85 | """;
86 | }
87 |
--------------------------------------------------------------------------------
/lib/common/utils/db_tools/ddl_life_tool.dart:
--------------------------------------------------------------------------------
1 | // ignore_for_file: constant_identifier_names
2 |
3 | import 'init_db.dart';
4 |
5 | /// 数据库中【生活工具】相关表的创建
6 | class LifeToolDdl {
7 | // 账单条目表
8 | static const tableNameOfBillItem = '${DB_TABLE_PREFIX}bill_item';
9 |
10 | static const String ddlForBillItem = """
11 | CREATE TABLE $tableNameOfBillItem (
12 | bill_item_id TEXT NOT NULL,
13 | item_type INTEGER NOT NULL,
14 | date TEXT,
15 | category TEXT,
16 | item TEXT NOT NULL,
17 | value REAL NOT NULL,
18 | gmt_modified TEXT NOT NULL,
19 | PRIMARY KEY(bill_item_id)
20 | );
21 | """;
22 |
23 | ///
24 | /// 菜品基础表
25 | ///
26 | static const tableNameOfDish = '${DB_TABLE_PREFIX}dish';
27 |
28 | // 2023-03-10 避免导入时重复导入,还是加一个unique
29 | static const String ddlForDish = """
30 | CREATE TABLE $tableNameOfDish (
31 | dish_id TEXT NOT NULL PRIMARY KEY,
32 | dish_name TEXT NOT NULL,
33 | description TEXT,
34 | photos TEXT,
35 | videos TEXT,
36 | tags TEXT,
37 | meal_categories TEXT,
38 | recipe TEXT,
39 | recipe_picture TEXT,
40 | UNIQUE(dish_name,tags)
41 | );
42 | """;
43 |
44 | ///
45 | /// 动物的品种,简单保留品种和亚种即可(统一的话就一个品种信息,品种亚种都有则保留有更详细的)
46 | /// 2024-09-14 暂时没用到
47 | ///
48 | static const tableNameOfAnimalBreed = '${DB_TABLE_PREFIX}animal_breed';
49 |
50 | static const String ddlForAnimalBreed = """
51 | CREATE TABLE $tableNameOfAnimalBreed (
52 | id TEXT NOT NULL,
53 | breed TEXT NOT NULL,
54 | subBreed TEXT,
55 | temperament TEXT,
56 | origin TEXT,
57 | description TEXT,
58 | lifeSpan TEXT,
59 | altNames TEXT,
60 | wikipediaUrl TEXT,
61 | referenceImageUrl TEXT,
62 | dataSource TEXT,
63 | PRIMARY KEY(id)
64 | );
65 | """;
66 | }
67 |
--------------------------------------------------------------------------------
/lib/common/utils/dio_client/cus_http_client.dart:
--------------------------------------------------------------------------------
1 | import 'package:dio/dio.dart';
2 |
3 | import 'cus_http_request.dart';
4 |
5 | // 来源:https://www.cnblogs.com/luoshang/p/16987781.html
6 |
7 | /// 调用底层的request,重新提供get,post等方便方法
8 |
9 | class HttpUtils {
10 | static HttpRequest httpRequest = HttpRequest();
11 |
12 | /// get
13 | static Future get({
14 | required String path,
15 | Map? queryParameters,
16 | dynamic headers,
17 | CusRespType? responseType,
18 | bool showLoading = true,
19 | bool showErrorMessage = true,
20 | }) {
21 | return httpRequest.request(
22 | path: path,
23 | method: CusHttpMethod.get,
24 | queryParameters: queryParameters,
25 | responseType: responseType,
26 | headers: headers,
27 | showLoading: showLoading,
28 | showErrorMessage: showErrorMessage,
29 | );
30 | }
31 |
32 | /// post
33 | static Future post({
34 | required String path,
35 | required CusHttpMethod method,
36 | dynamic data,
37 | dynamic headers,
38 | CusRespType? responseType,
39 | CancelToken? cancelToken,
40 | bool showLoading = true,
41 | bool showErrorMessage = true,
42 | }) {
43 | return httpRequest.request(
44 | path: path,
45 | method: CusHttpMethod.post,
46 | data: data,
47 | responseType: responseType,
48 | headers: headers,
49 | cancelToken: cancelToken,
50 | showLoading: showLoading,
51 | showErrorMessage: showErrorMessage,
52 | );
53 | }
54 | }
55 |
56 | /*
57 | 使用方法:
58 | import 'cus_dio_client.dart';
59 |
60 | HttpUtils.get(
61 | path: '11111'
62 | );
63 |
64 | HttpUtils.post(
65 | path: '1111',
66 | method: HttpMethod.post //可以更改其他的
67 | );
68 | */
--------------------------------------------------------------------------------
/lib/common/utils/dio_client/cus_http_options.dart:
--------------------------------------------------------------------------------
1 | // 超时时间
2 | class HttpOptions {
3 | // 请求地址,这个应该别处传来(使用时带上完整地址即可)
4 | static const String baseUrl = '';
5 | //单位时间是ms
6 | static const Duration connectTimeout = Duration(milliseconds: 60 * 1000);
7 | static const Duration receiveTimeout = Duration(milliseconds: 5 * 60 * 1000);
8 | static const Duration sendTimeout = Duration(milliseconds: 30 * 1000);
9 | // 自定义content-type
10 | static const String contentType = "application/json;charset=utf-8";
11 | }
12 |
--------------------------------------------------------------------------------
/lib/common/utils/dio_client/intercepter_response.dart:
--------------------------------------------------------------------------------
1 | // ignore_for_file: avoid_print
2 |
3 | import 'package:dio/dio.dart';
4 |
5 | class ResponseIntercepter extends Interceptor {
6 | const ResponseIntercepter();
7 |
8 | @override
9 | Future onResponse(
10 | Response response,
11 | ResponseInterceptorHandler handler,
12 | ) async {
13 | print('【onResponse】进入了dio的响应拦截器');
14 |
15 | // ??? 这里打印response和response.data是一样的
16 |
17 | // print("************************** ${response}");
18 | // print("************************** ${response.data}");
19 |
20 | // 判断返回数据中是否包含"token失效"的信息
21 | // if (response.data.contains("token失效")) {
22 | // // 导航到登录页面
23 | // navigatorKey.currentState!.pushReplacementNamed('/login');
24 | // }
25 |
26 | handler.next(response);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/lib/common/utils/dio_client/interceptor_request.dart:
--------------------------------------------------------------------------------
1 | // ignore_for_file: avoid_print
2 |
3 | import 'package:dio/dio.dart';
4 |
5 | class RequestInterceptor extends Interceptor {
6 | const RequestInterceptor();
7 |
8 | @override
9 | Future onRequest(
10 | RequestOptions options,
11 | RequestInterceptorHandler handler,
12 | ) async {
13 | print('【onRequest】进入了dio的请求拦截器');
14 |
15 | // 可以在这里添加 authorization 自定义头等操作
16 |
17 | return handler.next(options);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/lib/models/brief_ai_tools/branch_chat/branch_chat_message.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 | import 'package:flutter/foundation.dart';
3 | import 'package:objectbox/objectbox.dart';
4 | import 'branch_chat_session.dart';
5 |
6 | @Entity()
7 | class BranchChatMessage {
8 | @Id(assignable: true)
9 | int id;
10 |
11 | String messageId;
12 | String role;
13 | String content;
14 | DateTime createTime;
15 |
16 | // 可选字段
17 | String? reasoningContent;
18 | int? thinkingDuration;
19 | String? contentVoicePath;
20 | String? imagesUrl;
21 | String? videosUrl;
22 |
23 | // 2025-03-24 联网搜索参考内容
24 | // 使用字符串存储序列化后的JSON
25 | String? referencesJson;
26 |
27 | // 非持久化字段,用于运行时
28 | @Transient()
29 | List