From 80912606bc41bc71ed058ad0cfb70a02bce89aa2 Mon Sep 17 00:00:00 2001 From: lollipopkit Date: Wed, 29 May 2024 21:31:52 +0800 Subject: [PATCH] new: switch model on home (#9) --- ios/Runner.xcodeproj/project.pbxproj | 6 +- lib/app.dart | 19 +---- lib/data/res/build.dart | 6 +- lib/data/res/openai.dart | 53 ++++++++----- lib/l10n/app_en.arb | 1 + lib/l10n/app_zh.arb | 1 + lib/view/page/home/appbar.dart | 109 +++++++++++++++++++++------ lib/view/page/home/enum.dart | 2 +- lib/view/page/home/url_scheme.dart | 11 ++- lib/view/page/setting.dart | 47 ++++++++---- pubspec.lock | 4 +- pubspec.yaml | 2 +- 12 files changed, 171 insertions(+), 90 deletions(-) diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 2d6a246..5024bbd 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -363,7 +363,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; - CURRENT_PROJECT_VERSION = 166; + CURRENT_PROJECT_VERSION = 167; DEVELOPMENT_TEAM = BA88US33G6; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; @@ -494,7 +494,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; - CURRENT_PROJECT_VERSION = 166; + CURRENT_PROJECT_VERSION = 167; DEVELOPMENT_TEAM = BA88US33G6; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; @@ -519,7 +519,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; - CURRENT_PROJECT_VERSION = 166; + CURRENT_PROJECT_VERSION = 167; DEVELOPMENT_TEAM = BA88US33G6; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; diff --git a/lib/app.dart b/lib/app.dart index 282cf9b..960350b 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -33,10 +33,10 @@ class MyApp extends StatelessWidget { supportedLocales: AppLocalizations.supportedLocales, themeMode: themeMode, theme: ThemeData(colorSchemeSeed: UIs.colorSeed), - darkTheme: _getAmoledTheme(ThemeData( + darkTheme: ThemeData( brightness: Brightness.dark, colorSchemeSeed: UIs.colorSeed, - )), + ).toAmoled, home: Builder( builder: (context) { final l10n_ = AppLocalizations.of(context); @@ -50,18 +50,3 @@ class MyApp extends StatelessWidget { ); } } - -ThemeData _getAmoledTheme(ThemeData darkTheme) => darkTheme.copyWith( - scaffoldBackgroundColor: Colors.black, - dialogBackgroundColor: Colors.black, - drawerTheme: const DrawerThemeData(backgroundColor: Colors.black), - appBarTheme: const AppBarTheme(backgroundColor: Colors.black), - dialogTheme: const DialogTheme(backgroundColor: Colors.black), - bottomSheetTheme: - const BottomSheetThemeData(backgroundColor: Colors.black), - listTileTheme: const ListTileThemeData(tileColor: Colors.transparent), - cardTheme: const CardTheme(color: Colors.black12), - navigationBarTheme: - const NavigationBarThemeData(backgroundColor: Colors.black), - popupMenuTheme: const PopupMenuThemeData(color: Colors.black), - ); diff --git a/lib/data/res/build.dart b/lib/data/res/build.dart index e9fdbe9..eb57bff 100644 --- a/lib/data/res/build.dart +++ b/lib/data/res/build.dart @@ -2,8 +2,8 @@ class Build { static const String name = "GPTBox"; - static const int build = 166; + static const int build = 167; static const String engine = "3.22.1"; - static const String buildAt = "2024-05-29 16:09:39"; - static const int modifications = 5; + static const String buildAt = "2024-05-29 17:09:26"; + static const int modifications = 1; } diff --git a/lib/data/res/openai.dart b/lib/data/res/openai.dart index 00250c6..f7d72cb 100644 --- a/lib/data/res/openai.dart +++ b/lib/data/res/openai.dart @@ -1,56 +1,69 @@ import 'package:dart_openai/dart_openai.dart'; import 'package:fl_lib/fl_lib.dart'; -import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; import 'package:gpt_box/data/model/chat/config.dart'; +import 'package:gpt_box/data/res/l10n.dart'; import 'package:gpt_box/data/store/all.dart'; abstract final class OpenAICfg { - static final nameNotifier = ValueNotifier(_cfg.name); static final models = [].vn; - static ChatConfig _cfg = () { + static final vn = () { final selectedKey = Stores.config.selectedKey.fetch(); final selected = Stores.config.fetch(selectedKey); return selected ?? ChatConfig.defaultOne; - }(); + }() + .vn; - static ChatConfig get current => _cfg; - static set current(ChatConfig config) { - _cfg = config; + static ChatConfig get current => vn.value; + + static void setTo(ChatConfig config, BuildContext context) { + final old = vn.value; + vn.value = config; apply(); config.save(); Stores.config.selectedKey.put(config.id); - nameNotifier.value = config.name; - updateModels(); + if (old.id != config.id) { + Funcs.throttle( + updateModels, + id: 'setTo-${config.id}', + duration: 1000 * 30, + )?.then((ok) { + if (!ok && context.mounted) { + context.showSnackBar('${l10n.failed}: update models list'); + } + }); + } } - static void updateModels() { + static Future updateModels() async { try { - OpenAI.instance.model.list().then((value) { - models.value = value.map((e) => e.id).toList(); - }); + final val = await OpenAI.instance.model.list(); + models.value = val.map((e) => e.id).toList(); + return true; } catch (e) { Loggers.app.warning('Failed to update models', e); + return false; } } static void apply() { - if (_cfg.id == ChatConfig.defaultId) { + if (vn.value.id == ChatConfig.defaultId) { Loggers.app.info('Using default profile'); } else { - Loggers.app.info('Profile [${_cfg.name}]'); + Loggers.app.info('Profile [${vn.value.name}]'); } - OpenAI.apiKey = _cfg.key; - OpenAI.baseUrl = _cfg.url; + OpenAI.apiKey = vn.value.key; + OpenAI.baseUrl = vn.value.url; } - static void switchToDefault() { + static void switchToDefault(BuildContext context) { final cfg = Stores.config.fetch(ChatConfig.defaultId); if (cfg != null) { - current = cfg; + setTo(cfg, context); } else { - current = ChatConfig.defaultOne; + setTo(ChatConfig.defaultOne, context); Loggers.app.warning('Default config not found'); } } diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index e98c660..b94cf41 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -42,6 +42,7 @@ "empty": "Empty", "emptyFields": "{fields} is empty", "error": "Error", + "failed": "Failed", "file": "File", "fileNotFound": "File({file}) not found", "fileTooLarge": "File too large: {size}", diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index 8559f92..3255aee 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -42,6 +42,7 @@ "empty": "空", "emptyFields": "{fields} 为空", "error": "错误", + "failed": "失败", "file": "文件", "fileNotFound": "文件({file})未能找到", "fileTooLarge": "文件过大:{size}", diff --git a/lib/view/page/home/appbar.dart b/lib/view/page/home/appbar.dart index 5c85de2..159c58c 100644 --- a/lib/view/page/home/appbar.dart +++ b/lib/view/page/home/appbar.dart @@ -6,10 +6,6 @@ CustomAppBar _buildAppBar(BuildContext context) { builder: (_, __) { final entity = _curChat; if (entity == null) return Text(l10n.empty); - final len = '${entity.items.length} ${l10n.message}'; - final time = entity.items.lastOrNull?.createdAt - .difference(DateTime.now()) - .toAgoStr; return AnimatedSwitcher( duration: _durationMedium, switchInCurve: Easing.standardDecelerate, @@ -25,39 +21,80 @@ CustomAppBar _buildAppBar(BuildContext context) { child: SizedBox( key: Key(entity.id), width: (_media?.size.width ?? 300) * 0.5, - child: RichText( - maxLines: 2, + child: Text( + _curChat?.name ?? l10n.untitled, + maxLines: 1, overflow: TextOverflow.ellipsis, textAlign: TextAlign.left, - text: TextSpan( - children: [ - TextSpan( - text: _curChat?.name ?? l10n.untitled, - style: UIs.text13 - .copyWith(color: UIs.textColor.fromBool(context.isDark)), - ), - TextSpan( - text: '\n$len · ${time ?? l10n.empty}', - style: UIs.text11Grey, - ), - ], - ), + style: UIs.text15, ), ), ); }, ); + + final subtitle = ValBuilder( + listenable: OpenAICfg.vn, + builder: (val) { + final text = val.name.isEmpty ? l10n.defaulT : val.name; + return AnimatedSwitcher( + duration: _durationMedium, + switchInCurve: Easing.standardDecelerate, + switchOutCurve: Easing.standardDecelerate, + transitionBuilder: (child, animation) => SlideTransitionX( + position: animation, + child: FadeTransition( + opacity: animation, + child: child, + ), + ), + // Use a SizedBox to avoid the title jumping when switching chats. + child: Text( + key: ValueKey(val), + '$text·${val.model}', + maxLines: 1, + overflow: TextOverflow.ellipsis, + textAlign: TextAlign.left, + style: UIs.text12Grey, + ), + ); + }, + ); + return CustomAppBar( centerTitle: false, title: GestureDetector( onLongPress: () => Routes.debug.go( context, args: DebugPageArgs( - notifier: Pros.debug.widgets, - onClear: Pros.debug.clear, - title: 'Logs(${Build.build})'), + notifier: Pros.debug.widgets, + onClear: Pros.debug.clear, + title: 'Logs(${Build.build})', + ), + ), + onTap: () => _onSwitchModel(context), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + title, + SizedBox( + width: (_media?.size.width ?? 300) * 0.5, + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + subtitle, + const Icon( + Icons.keyboard_arrow_down, + size: 15, + color: Colors.grey, + ) + ], + ), + ), + ], ), - child: title, ), actions: [ ValueListenableBuilder( @@ -67,3 +104,29 @@ CustomAppBar _buildAppBar(BuildContext context) { ], ); } + +void _onSwitchModel(BuildContext context) async { + final cfg = OpenAICfg.current; + if (cfg.key.isEmpty) { + context.showRoundDialog( + title: l10n.attention, + child: Text(l10n.needOpenAIKey), + actions: Btns.oks(onTap: context.pop), + ); + return; + } + final models = OpenAICfg.models.value; + final modelStrs = List.from(models); + modelStrs.removeWhere((element) => !element.startsWith('gpt')); + if (modelStrs.isEmpty) { + modelStrs.addAll(models); + } + + final model = await context.showPickSingleDialog( + items: modelStrs, + initial: cfg.model, + title: l10n.model, + ); + if (model == null) return; + OpenAICfg.setTo(cfg.copyWith(model: model), context); +} diff --git a/lib/view/page/home/enum.dart b/lib/view/page/home/enum.dart index 6bea37a..48893d2 100644 --- a/lib/view/page/home/enum.dart +++ b/lib/view/page/home/enum.dart @@ -61,7 +61,7 @@ enum HomePageEnum { : null, ); if (select == null) return; - OpenAICfg.current = select; + OpenAICfg.setTo(select, context); }, icon: const Icon(Icons.switch_account), tooltip: l10n.profile, diff --git a/lib/view/page/home/url_scheme.dart b/lib/view/page/home/url_scheme.dart index 50198c7..55e7ac0 100644 --- a/lib/view/page/home/url_scheme.dart +++ b/lib/view/page/home/url_scheme.dart @@ -110,10 +110,13 @@ abstract final class AppLink { Loggers.app.warning(msg); return true; } - OpenAICfg.current = OpenAICfg.current.copyWith( - url: openAiUrl, - key: openAiKey, - model: openAiModel, + OpenAICfg.setTo( + OpenAICfg.current.copyWith( + url: openAiUrl, + key: openAiKey, + model: openAiModel, + ), + context, ); return true; default: diff --git a/lib/view/page/setting.dart b/lib/view/page/setting.dart index e9fb01b..a0643ba 100644 --- a/lib/view/page/setting.dart +++ b/lib/view/page/setting.dart @@ -251,7 +251,7 @@ class _SettingPageState extends State { _cfgRN.build(); context.pop(); if (cfg.id == value.id) { - OpenAICfg.switchToDefault(); + OpenAICfg.switchToDefault(context); _cfgRN.build(); } }, @@ -265,14 +265,14 @@ class _SettingPageState extends State { value: cfg.id == value.id, onChanged: (val) { if (val != true) return; - OpenAICfg.current = value; + OpenAICfg.setTo(value, context); _cfgRN.build(); }, ), title: Text(value.name.isEmpty ? l10n.defaulT : value.name), onTap: () { if (cfg.id == value.id) return; - OpenAICfg.current = value; + OpenAICfg.setTo(value, context); _cfgRN.build(); }, trailing: value.id != ChatConfig.defaultId ? delBtn : null, @@ -297,7 +297,7 @@ class _SettingPageState extends State { id: shortid.generate(), name: name, )..save(); - OpenAICfg.current = cfg; + OpenAICfg.setTo(cfg, context); _cfgRN.build(); }, title: Text(l10n.add), @@ -344,7 +344,7 @@ class _SettingPageState extends State { ), ); if (result == null) return; - OpenAICfg.current = OpenAICfg.current.copyWith(key: result); + OpenAICfg.setTo(OpenAICfg.current.copyWith(key: result), context); _cfgRN.build(); }, ); @@ -384,7 +384,7 @@ class _SettingPageState extends State { ); if (sure != true) return; } - OpenAICfg.current = OpenAICfg.current.copyWith(url: result); + OpenAICfg.setTo(OpenAICfg.current.copyWith(url: result), context); _cfgRN.build(); }, ); @@ -438,7 +438,10 @@ class _SettingPageState extends State { onPressed: () { void onSave(String s) { context.pop(); - OpenAICfg.current = OpenAICfg.current.copyWith(model: s); + OpenAICfg.setTo( + OpenAICfg.current.copyWith(model: s), + context, + ); _cfgRN.build(); } @@ -459,7 +462,7 @@ class _SettingPageState extends State { ], ); if (model != null) { - OpenAICfg.current = OpenAICfg.current.copyWith(model: model); + OpenAICfg.setTo(OpenAICfg.current.copyWith(model: model), context); _cfgRN.build(); } }, @@ -497,7 +500,10 @@ class _SettingPageState extends State { onPressed: () { void onSave(String s) { context.pop(); - OpenAICfg.current = OpenAICfg.current.copyWith(model: s); + OpenAICfg.setTo( + OpenAICfg.current.copyWith(model: s), + context, + ); _cfgRN.build(); } @@ -518,7 +524,7 @@ class _SettingPageState extends State { ], ); if (model != null) { - OpenAICfg.current = OpenAICfg.current.copyWith(model: model); + OpenAICfg.setTo(OpenAICfg.current.copyWith(model: model), context); _cfgRN.build(); } }, @@ -556,7 +562,10 @@ class _SettingPageState extends State { onPressed: () { void onSave(String s) { context.pop(); - OpenAICfg.current = OpenAICfg.current.copyWith(model: s); + OpenAICfg.setTo( + OpenAICfg.current.copyWith(model: s), + context, + ); _cfgRN.build(); } @@ -577,7 +586,7 @@ class _SettingPageState extends State { ], ); if (model != null) { - OpenAICfg.current = OpenAICfg.current.copyWith(model: model); + OpenAICfg.setTo(OpenAICfg.current.copyWith(model: model), context); _cfgRN.build(); } }, @@ -615,7 +624,10 @@ class _SettingPageState extends State { onPressed: () { void onSave(String s) { context.pop(); - OpenAICfg.current = OpenAICfg.current.copyWith(model: s); + OpenAICfg.setTo( + OpenAICfg.current.copyWith(model: s), + context, + ); _cfgRN.build(); } @@ -636,7 +648,7 @@ class _SettingPageState extends State { ], ); if (model != null) { - OpenAICfg.current = OpenAICfg.current.copyWith(model: model); + OpenAICfg.setTo(OpenAICfg.current.copyWith(model: model), context); _cfgRN.build(); } }, @@ -669,7 +681,7 @@ class _SettingPageState extends State { ), ); if (result == null) return; - OpenAICfg.current = OpenAICfg.current.copyWith(prompt: result); + OpenAICfg.setTo(OpenAICfg.current.copyWith(prompt: result), context); _cfgRN.build(); }, ); @@ -703,7 +715,10 @@ class _SettingPageState extends State { context.showSnackBar('Invalid number: $result'); return; } - OpenAICfg.current = OpenAICfg.current.copyWith(historyLen: newVal); + OpenAICfg.setTo( + OpenAICfg.current.copyWith(historyLen: newVal), + context, + ); _cfgRN.build(); }, ); diff --git a/pubspec.lock b/pubspec.lock index 477b74a..87fb8fa 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -407,8 +407,8 @@ packages: dependency: "direct main" description: path: "." - ref: "72a52694b7c023c309475043627f599dd916f5ef" - resolved-ref: "72a52694b7c023c309475043627f599dd916f5ef" + ref: "v1.0.24" + resolved-ref: "3c95d07aa27446f1c05baf8fa02bd2e7392f52d0" url: "https://github.com/lollipopkit/fl_lib" source: git version: "0.0.1" diff --git a/pubspec.yaml b/pubspec.yaml index 035a47d..823f5e8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -29,7 +29,7 @@ dependencies: fl_lib: git: url: https://github.com/lollipopkit/fl_lib - ref: 72a52694b7c023c309475043627f599dd916f5ef + ref: v1.0.24 # path: ../fl_lib dev_dependencies: