kasy-cli 1.21.9 → 1.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/commands/add.js +93 -80
- package/lib/commands/configure.js +100 -32
- package/lib/commands/doctor.js +28 -2
- package/lib/commands/new.js +80 -37
- package/lib/commands/notifications.js +1 -1
- package/lib/commands/remove.js +43 -15
- package/lib/commands/run.js +2 -2
- package/lib/commands/update.js +2 -2
- package/lib/scaffold/CHANGELOG.json +14 -0
- package/lib/scaffold/backends/api/generator.js +14 -14
- package/lib/scaffold/backends/api/patch/README.md +83 -0
- package/lib/scaffold/backends/api/patch/lib/core/data/api/storage_api.dart +1 -1
- package/lib/scaffold/backends/api/patch/lib/core/data/entities/user_entity.dart +5 -0
- package/lib/scaffold/backends/api/patch/lib/{environnements.dart → environments.dart} +3 -11
- package/lib/scaffold/backends/api/patch/lib/features/ai_chat/api/ai_chat_api.dart +108 -0
- package/lib/scaffold/backends/api/patch/lib/features/ai_chat/api/ai_chat_conversation_entity.dart +51 -0
- package/lib/scaffold/backends/api/patch/lib/features/{llm_chat/api/llm_chat_message_entity.dart → ai_chat/api/ai_chat_message_entity.dart} +5 -5
- package/lib/scaffold/backends/api/patch/lib/features/{llm_chat/providers/llm_chat_notifier.dart → ai_chat/providers/ai_chat_notifier.dart} +71 -38
- package/lib/scaffold/backends/api/patch/lib/features/authentication/api/authentication_api.dart +2 -2
- package/lib/scaffold/backends/api/patch/lib/features/feedbacks/api/feature_request_api.dart +54 -0
- package/lib/scaffold/backends/api/patch/lib/features/onboarding/models/user_info.dart +16 -16
- package/lib/scaffold/backends/api/patch/lib/features/onboarding/ui/components/onboarding_att_setup.dart +4 -3
- package/lib/scaffold/backends/api/patch/lib/features/onboarding/ui/widgets/onboarding_radio_question.dart +38 -28
- package/lib/scaffold/backends/api/patch/lib/features/settings/ui/components/admin/admin_users_api.dart +100 -0
- package/lib/scaffold/backends/api/patch/lib/features/{subscription → subscriptions}/api/entities/subscription_entity.dart +13 -0
- package/lib/scaffold/backends/api/patch/lib/features/subscriptions/api/stripe_backend_api.dart +60 -0
- package/lib/scaffold/backends/api/patch/lib/features/{subscription → subscriptions}/api/subscription_api.dart +1 -1
- package/lib/scaffold/backends/api/pubspec.yaml.tpl +4 -5
- package/lib/scaffold/backends/firebase/deploy.js +87 -13
- package/lib/scaffold/backends/firebase/generator.js +5 -5
- package/lib/scaffold/backends/firebase/tokens.js +4 -4
- package/lib/scaffold/backends/supabase/deploy.js +63 -11
- package/lib/scaffold/backends/supabase/edge-functions/admin-list-users/index.ts +149 -0
- package/lib/scaffold/backends/supabase/edge-functions/{llm-chat → ai-chat}/index.ts +19 -19
- package/lib/scaffold/backends/supabase/edge-functions/revenuecat-webhook/index.ts +2 -0
- package/lib/scaffold/backends/supabase/edge-functions/stripe-create-checkout-session/index.ts +123 -0
- package/lib/scaffold/backends/supabase/edge-functions/stripe-create-portal-session/index.ts +97 -0
- package/lib/scaffold/backends/supabase/edge-functions/stripe-list-prices/index.ts +83 -0
- package/lib/scaffold/backends/supabase/edge-functions/stripe-webhook/index.ts +138 -0
- package/lib/scaffold/backends/supabase/generator.js +17 -17
- package/lib/scaffold/backends/supabase/migrations/20240101000009_ai_messages.sql +50 -0
- package/lib/scaffold/backends/supabase/migrations/20240101000012_stripe_customers.sql +36 -0
- package/lib/scaffold/backends/supabase/migrations/20240101000013_admin_role.sql +62 -0
- package/lib/scaffold/backends/supabase/patch/lib/core/data/entities/user_entity.dart +4 -0
- package/lib/scaffold/backends/supabase/patch/lib/{environnements.dart → environments.dart} +3 -13
- package/lib/scaffold/backends/supabase/patch/lib/features/ai_chat/api/ai_chat_api.dart +95 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/ai_chat/api/ai_chat_conversation_entity.dart +52 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/{llm_chat/api/llm_chat_message_entity.dart → ai_chat/api/ai_chat_message_entity.dart} +6 -6
- package/lib/scaffold/backends/supabase/patch/lib/features/{llm_chat/providers/llm_chat_notifier.dart → ai_chat/providers/ai_chat_notifier.dart} +63 -35
- package/lib/scaffold/backends/supabase/patch/lib/features/authentication/api/authentication_api.dart +1 -1
- package/lib/scaffold/backends/supabase/patch/lib/features/feedbacks/api/feature_request_api.dart +46 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/models/user_info.dart +16 -16
- package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/ui/components/onboarding_att_setup.dart +4 -3
- package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/ui/widgets/onboarding_radio_question.dart +38 -28
- package/lib/scaffold/backends/supabase/patch/lib/features/settings/ui/components/admin/admin_users_api.dart +93 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/{subscription → subscriptions}/api/entities/subscription_entity.dart +13 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/subscriptions/api/stripe_backend_api.dart +52 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/{subscription → subscriptions}/api/subscription_api.dart +1 -1
- package/lib/scaffold/backends/supabase/pubspec.yaml.tpl +4 -5
- package/lib/scaffold/backends/supabase/tokens.js +3 -3
- package/lib/scaffold/catalog.js +9 -11
- package/lib/scaffold/generate.js +45 -31
- package/lib/scaffold/shared/generator-utils.js +188 -81
- package/lib/scaffold/shared/sort-imports.js +191 -0
- package/lib/scaffold/shared/template-strings.js +3 -3
- package/lib/utils/checks.js +2 -2
- package/lib/utils/i18n/messages-en.js +50 -35
- package/lib/utils/i18n/messages-es.js +50 -35
- package/lib/utils/i18n/messages-pt.js +52 -37
- package/lib/utils/updates.js +15 -15
- package/package.json +1 -1
- package/templates/firebase/.env.example +2 -2
- package/templates/firebase/android/app/src/main/res/drawable/background.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night/background.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-v21/background.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-v21/background.png +0 -0
- package/templates/firebase/android/app/src/main/res/values-night-v31/styles.xml +1 -1
- package/templates/firebase/android/app/src/main/res/values-v31/styles.xml +1 -1
- package/templates/firebase/assets/images/logo_wordmark_dark.png +0 -0
- package/templates/firebase/assets/images/logo_wordmark_light.png +0 -0
- package/templates/firebase/docs/revenuecat-setup.es.md +1 -1
- package/templates/firebase/docs/revenuecat-setup.pt.md +1 -1
- package/templates/firebase/firestore.rules +24 -5
- package/templates/firebase/functions/package-lock.json +22 -1
- package/templates/firebase/functions/package.json +2 -1
- package/templates/firebase/functions/src/admin/functions.ts +113 -0
- package/templates/firebase/functions/src/{llm_chat → ai_chat}/index.ts +16 -16
- package/templates/firebase/functions/src/index.ts +8 -2
- package/templates/firebase/functions/src/notifications/device_triggers.ts +2 -2
- package/templates/firebase/functions/src/notifications/triggers.ts +3 -3
- package/templates/firebase/functions/src/subscriptions/models/subscription_status.ts +2 -0
- package/templates/firebase/functions/src/subscriptions/stripe_functions.ts +222 -0
- package/templates/firebase/functions/src/subscriptions/subscriptions_functions.ts +2 -2
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png +0 -0
- package/templates/firebase/lib/components/components.dart +4 -1
- package/templates/firebase/lib/components/kasy_app_bar.dart +22 -7
- package/templates/firebase/lib/components/kasy_avatar.dart +7 -6
- package/templates/firebase/lib/components/kasy_button.dart +23 -99
- package/templates/firebase/lib/components/kasy_dialog.dart +11 -11
- package/templates/firebase/lib/components/kasy_image_viewer.dart +84 -0
- package/templates/firebase/lib/components/{kasy_sidebar_pro.dart → kasy_sidebar.dart} +692 -425
- package/templates/firebase/lib/components/kasy_status_tag.dart +91 -0
- package/templates/firebase/lib/components/kasy_text_area.dart +1 -3
- package/templates/firebase/lib/components/kasy_text_field.dart +5 -6
- package/templates/firebase/lib/components/kasy_text_field_otp.dart +1 -3
- package/templates/firebase/lib/components/kasy_toast.dart +2 -2
- package/templates/firebase/lib/components/kasy_web_header.dart +209 -0
- package/templates/firebase/lib/core/ads/ads_provider.dart +1 -1
- package/templates/firebase/lib/core/bottom_menu/bottom_menu.dart +57 -4
- package/templates/firebase/lib/core/bottom_menu/bottom_router.dart +19 -91
- package/templates/firebase/lib/core/bottom_menu/kasy_bottom_bar_factory.dart +196 -96
- package/templates/firebase/lib/core/bottom_menu/web_content_wrapper.dart +41 -0
- package/templates/firebase/lib/core/config/app_env.dart +5 -11
- package/templates/firebase/lib/core/config/features.dart +5 -4
- package/templates/firebase/lib/core/data/api/analytics_api.dart +1 -1
- package/templates/firebase/lib/core/data/api/http_client.dart +1 -1
- package/templates/firebase/lib/core/data/entities/user_entity.dart +3 -0
- package/templates/firebase/lib/core/data/models/entitlement.dart +35 -0
- package/templates/firebase/lib/core/data/models/subscription.dart +13 -186
- package/templates/firebase/lib/core/data/models/user.dart +11 -0
- package/templates/firebase/lib/core/data/repositories/user_repository.dart +1 -1
- package/templates/firebase/lib/core/home_widgets/home_widget_background_task.dart +1 -1
- package/templates/firebase/lib/core/home_widgets/home_widget_mywidget_service.dart +1 -1
- package/templates/firebase/lib/core/icons/kasy_icons.dart +31 -1
- package/templates/firebase/lib/core/rating/api/rating_api.dart +2 -2
- package/templates/firebase/lib/core/states/logout_action.dart +25 -0
- package/templates/firebase/lib/core/states/user_state_notifier.dart +3 -3
- package/templates/firebase/lib/core/theme/colors.dart +488 -188
- package/templates/firebase/lib/core/theme/radius.dart +22 -11
- package/templates/firebase/lib/core/theme/shadows.dart +66 -0
- package/templates/firebase/lib/core/theme/texts.dart +75 -41
- package/templates/firebase/lib/core/theme/universal_theme.dart +9 -4
- package/templates/firebase/lib/core/web_device_preview/web_device_preview.dart +5 -4
- package/templates/firebase/lib/core/web_viewport_scale.dart +52 -0
- package/templates/firebase/lib/core/widgets/kasy_brand_badge.dart +118 -0
- package/templates/firebase/lib/core/widgets/kasy_brand_logo.dart +40 -0
- package/templates/firebase/lib/core/widgets/kasy_focus_ring.dart +125 -0
- package/templates/firebase/lib/core/widgets/kasy_hover.dart +33 -13
- package/templates/firebase/lib/core/widgets/kasy_pressable_depth.dart +18 -13
- package/templates/firebase/lib/{environnements.dart → environments.dart} +3 -14
- package/templates/firebase/lib/features/ai_chat/ai_chat_page.dart +159 -0
- package/templates/firebase/lib/features/ai_chat/api/ai_chat_api.dart +107 -0
- package/templates/firebase/lib/features/ai_chat/api/ai_chat_conversation_entity.dart +64 -0
- package/templates/firebase/lib/features/{llm_chat/api/llm_chat_message_entity.dart → ai_chat/api/ai_chat_message_entity.dart} +6 -6
- package/templates/firebase/lib/features/{llm_chat/providers/llm_chat_notifier.dart → ai_chat/providers/ai_chat_notifier.dart} +73 -38
- package/templates/firebase/lib/features/ai_chat/providers/ai_conversations_notifier.dart +103 -0
- package/templates/firebase/lib/features/{llm_chat/ui/widgets/llm_chat_avatars.dart → ai_chat/ui/widgets/ai_chat_avatars.dart} +13 -13
- package/templates/firebase/lib/features/{llm_chat/ui/widgets/llm_chat_composer.dart → ai_chat/ui/widgets/ai_chat_composer.dart} +10 -10
- package/templates/firebase/lib/features/{llm_chat/llm_chat_page.dart → ai_chat/ui/widgets/ai_chat_conversation_view.dart} +80 -67
- package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_conversation_list.dart +285 -0
- package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_conversation_tile.dart +163 -0
- package/templates/firebase/lib/features/authentication/api/authentication_api.dart +52 -13
- package/templates/firebase/lib/features/authentication/api/popup_dismiss_watcher.dart +12 -0
- package/templates/firebase/lib/features/authentication/api/popup_dismiss_watcher_web.dart +35 -0
- package/templates/firebase/lib/features/authentication/ui/recover_password_page.dart +108 -68
- package/templates/firebase/lib/features/authentication/ui/signin_page.dart +38 -51
- package/templates/firebase/lib/features/authentication/ui/signup_page.dart +38 -51
- package/templates/firebase/lib/features/authentication/ui/widgets/auth_card_scaffold.dart +118 -0
- package/templates/firebase/lib/features/authentication/ui/widgets/recover_password_result.dart +61 -44
- package/templates/firebase/lib/features/feedbacks/api/feature_request_api.dart +32 -0
- package/templates/firebase/lib/features/feedbacks/models/feedback_state.dart +5 -5
- package/templates/firebase/lib/features/feedbacks/providers/feedback_page_notifier.dart +2 -2
- package/templates/firebase/lib/features/home/design_system_page.dart +808 -170
- package/templates/firebase/lib/features/home/home_components_page.dart +6 -3
- package/templates/firebase/lib/features/home/home_components_preview_page.dart +6 -6
- package/templates/firebase/lib/features/home/home_components_preview_registry.dart +325 -186
- package/templates/firebase/lib/features/home/home_feed.dart +289 -0
- package/templates/firebase/lib/features/home/home_image_grid.dart +355 -0
- package/templates/firebase/lib/features/home/home_page.dart +11 -250
- package/templates/firebase/lib/features/{local_reminder → local_reminders}/providers/reminder_notifier.dart +1 -1
- package/templates/firebase/lib/features/{local_reminder → local_reminders}/ui/reminder_page.dart +2 -2
- package/templates/firebase/lib/features/notifications/shared/att_permission.dart +1 -1
- package/templates/firebase/lib/features/notifications/ui/request_notification_permission.dart +25 -61
- package/templates/firebase/lib/features/notifications/ui/widgets/permission_request_view.dart +117 -0
- package/templates/firebase/lib/features/onboarding/models/user_info.dart +16 -16
- package/templates/firebase/lib/features/onboarding/ui/components/onboarding_att_setup.dart +4 -3
- package/templates/firebase/lib/features/onboarding/ui/components/onboarding_features.dart +7 -9
- package/templates/firebase/lib/features/onboarding/ui/components/onboarding_loader.dart +71 -48
- package/templates/firebase/lib/features/onboarding/ui/components/onboarding_notifications_setup.dart +3 -2
- package/templates/firebase/lib/features/onboarding/ui/components/onboarding_questions.dart +5 -5
- package/templates/firebase/lib/features/onboarding/ui/onboarding_page.dart +4 -4
- package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_background.dart +4 -2
- package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_feature.dart +39 -121
- package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_illustration_scaffold.dart +105 -70
- package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_module_mockups.dart +639 -0
- package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_progress.dart +62 -50
- package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_radio_question.dart +38 -28
- package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_step_header.dart +75 -0
- package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_sticky_footer.dart +16 -5
- package/templates/firebase/lib/features/onboarding/ui/widgets/selectable_row_tile.dart +26 -22
- package/templates/firebase/lib/features/settings/settings_page.dart +601 -90
- package/templates/firebase/lib/features/settings/ui/components/admin/admin_page.dart +1193 -0
- package/templates/firebase/lib/features/settings/ui/components/admin/admin_paywalls.dart +1 -1
- package/templates/firebase/lib/features/settings/ui/components/admin/admin_routes.dart +2 -3
- package/templates/firebase/lib/features/settings/ui/components/admin/admin_users_api.dart +86 -0
- package/templates/firebase/lib/features/settings/ui/components/admin/admin_users_tab.dart +1215 -0
- package/templates/firebase/lib/features/settings/ui/components/admin/send_push_notification_page.dart +236 -0
- package/templates/firebase/lib/features/settings/ui/components/avatar_component.dart +3 -3
- package/templates/firebase/lib/features/settings/ui/widgets/kasy_user_avatar.dart +77 -0
- package/templates/firebase/lib/features/settings/ui/widgets/settings_tile.dart +1 -0
- package/templates/firebase/lib/features/{subscription → subscriptions}/api/entities/subscription_entity.dart +17 -0
- package/templates/firebase/lib/features/{subscription → subscriptions}/api/inapp_subscription_api.dart +67 -46
- package/templates/firebase/lib/features/subscriptions/api/revenuecat_product.dart +189 -0
- package/templates/firebase/lib/features/subscriptions/api/stripe_backend_api.dart +55 -0
- package/templates/firebase/lib/features/subscriptions/api/stripe_payment_api.dart +82 -0
- package/templates/firebase/lib/features/subscriptions/api/stripe_product.dart +178 -0
- package/templates/firebase/lib/features/{subscription → subscriptions}/api/subscription_api.dart +1 -1
- package/templates/firebase/lib/features/subscriptions/api/subscription_payment_api.dart +53 -0
- package/templates/firebase/lib/features/subscriptions/api/subscription_payment_api_provider.dart +21 -0
- package/templates/firebase/lib/features/{subscription → subscriptions}/providers/premium_page_provider.dart +9 -6
- package/templates/firebase/lib/features/{subscription → subscriptions}/repositories/subscription_repository.dart +26 -30
- package/{lib/scaffold/backends/supabase/patch/lib/features/subscription → templates/firebase/lib/features/subscriptions}/shared/maybeshow_premium.dart +0 -2
- package/templates/firebase/lib/features/subscriptions/shared/subscription_management.dart +45 -0
- package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/active_premium_content.dart +28 -4
- package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/paywall_minimal.dart +7 -7
- package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/paywall_row.dart +8 -8
- package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/paywall_with_switch.dart +7 -7
- package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/premium_content.dart +7 -7
- package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/premium_page_factory.dart +5 -5
- package/templates/firebase/lib/features/{subscription → subscriptions}/ui/premium_page.dart +5 -5
- package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/comparison_table.dart +1 -1
- package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/paywall_empty_state.dart +4 -4
- package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_bottom_menu.dart +3 -4
- package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/selectable_col.dart +1 -1
- package/templates/firebase/lib/i18n/en.i18n.json +171 -46
- package/templates/firebase/lib/i18n/es.i18n.json +175 -50
- package/templates/firebase/lib/i18n/pt.i18n.json +166 -41
- package/templates/firebase/lib/main.dart +6 -3
- package/templates/firebase/lib/router.dart +15 -23
- package/templates/firebase/pubspec.yaml +4 -5
- package/templates/firebase/test/core/data/repositories/user_repository_test.dart +3 -3
- package/templates/firebase/test/core/states/user_state_notifier_test.dart +4 -4
- package/templates/firebase/test/core/widgets/focus_ring_shape_test.dart +55 -0
- package/templates/firebase/test/features/{subscription → subscriptions}/api/fake_inapp_subscription_api.dart +11 -4
- package/templates/firebase/test/features/{subscription → subscriptions}/api/fake_revenuecat_product.dart +1 -0
- package/templates/firebase/test/features/{subscription → subscriptions}/api/fake_subscription_api.dart +2 -2
- package/templates/firebase/test/features/{subscription → subscriptions}/subscription_page_test.dart +4 -4
- package/templates/firebase/test/test_utils.dart +6 -6
- package/templates/firebase/web/index.html +5 -2
- package/lib/scaffold/backends/api/patch/lib/features/llm_chat/api/llm_chat_api.dart +0 -58
- package/lib/scaffold/backends/api/patch/lib/features/subscription/shared/maybeshow_premium.dart +0 -86
- package/lib/scaffold/backends/supabase/migrations/20240101000009_llm_messages.sql +0 -22
- package/lib/scaffold/backends/supabase/patch/lib/features/llm_chat/api/llm_chat_api.dart +0 -47
- package/templates/firebase/assets/images/onboarding/authentication-login-template.jpg +0 -0
- package/templates/firebase/assets/images/onboarding/img2.jpg +0 -0
- package/templates/firebase/assets/images/onboarding/img3.jpg +0 -0
- package/templates/firebase/assets/images/onboarding/notifications.png +0 -0
- package/templates/firebase/assets/images/onboarding/purchase.png +0 -0
- package/templates/firebase/lib/core/sidebar/kasy_sidebar.dart +0 -2021
- package/templates/firebase/lib/features/authentication/ui/widgets/auth_brand.dart +0 -35
- package/templates/firebase/lib/features/home/home_features_page.dart +0 -207
- package/templates/firebase/lib/features/llm_chat/api/llm_chat_api.dart +0 -50
- package/templates/firebase/lib/features/settings/ui/components/admin/admin_bottom_sheet.dart +0 -316
- package/templates/firebase/lib/features/subscription/shared/maybeshow_premium.dart +0 -85
- /package/templates/firebase/lib/features/{local_reminder → local_reminders}/repositories/reminder_preferences.dart +0 -0
- /package/templates/firebase/lib/features/{subscription → subscriptions}/providers/models/premium_state.dart +0 -0
- /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/feature_line.dart +0 -0
- /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_background.dart +0 -0
- /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_background_gradient.dart +0 -0
- /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_banner.dart +0 -0
- /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_card.dart +0 -0
- /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_close_button.dart +0 -0
- /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_feature.dart +0 -0
- /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/selectable_row.dart +0 -0
- /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/trial_switcher.dart +0 -0
- /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/unsubscribe_feedback_popup.dart +0 -0
|
@@ -6,8 +6,9 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
|
6
6
|
import 'package:kasy_kit/core/config/app_env.dart';
|
|
7
7
|
import 'package:kasy_kit/core/states/translations.dart';
|
|
8
8
|
import 'package:kasy_kit/core/states/user_state_notifier.dart';
|
|
9
|
-
import 'package:kasy_kit/features/
|
|
10
|
-
import 'package:kasy_kit/features/
|
|
9
|
+
import 'package:kasy_kit/features/ai_chat/api/ai_chat_api.dart';
|
|
10
|
+
import 'package:kasy_kit/features/ai_chat/api/ai_chat_message_entity.dart';
|
|
11
|
+
import 'package:kasy_kit/features/ai_chat/providers/ai_conversations_notifier.dart';
|
|
11
12
|
import 'package:logger/logger.dart';
|
|
12
13
|
|
|
13
14
|
/// Maximum number of recent messages sent to the AI as context.
|
|
@@ -34,39 +35,39 @@ class ChatMessage {
|
|
|
34
35
|
factory ChatMessage.assistant(String content) =>
|
|
35
36
|
ChatMessage(role: 'assistant', content: content);
|
|
36
37
|
|
|
37
|
-
factory ChatMessage.fromEntity(
|
|
38
|
+
factory ChatMessage.fromEntity(AiChatMessageEntity entity) =>
|
|
38
39
|
ChatMessage(role: entity.role, content: entity.content);
|
|
39
40
|
|
|
40
|
-
|
|
41
|
+
AiChatMessageEntity toEntity() => AiChatMessageEntity(
|
|
41
42
|
role: role,
|
|
42
43
|
content: content,
|
|
43
44
|
createdAt: DateTime.now(),
|
|
44
45
|
);
|
|
45
46
|
}
|
|
46
47
|
|
|
47
|
-
/// UI state for the
|
|
48
|
-
class
|
|
48
|
+
/// UI state for the AI chat screen.
|
|
49
|
+
class AiChatState {
|
|
49
50
|
final List<ChatMessage> messages;
|
|
50
51
|
|
|
51
|
-
/// True while the HTTP request to the
|
|
52
|
+
/// True while the HTTP request to the AI backend is in-flight.
|
|
52
53
|
final bool isReplying;
|
|
53
54
|
|
|
54
55
|
/// True once the first SSE chunk has been received.
|
|
55
56
|
/// While true the last message in [messages] is the partial assistant reply.
|
|
56
57
|
final bool streamingStarted;
|
|
57
58
|
|
|
58
|
-
const
|
|
59
|
+
const AiChatState({
|
|
59
60
|
required this.messages,
|
|
60
61
|
this.isReplying = false,
|
|
61
62
|
this.streamingStarted = false,
|
|
62
63
|
});
|
|
63
64
|
|
|
64
|
-
|
|
65
|
+
AiChatState copyWith({
|
|
65
66
|
List<ChatMessage>? messages,
|
|
66
67
|
bool? isReplying,
|
|
67
68
|
bool? streamingStarted,
|
|
68
69
|
}) {
|
|
69
|
-
return
|
|
70
|
+
return AiChatState(
|
|
70
71
|
messages: messages ?? this.messages,
|
|
71
72
|
isReplying: isReplying ?? this.isReplying,
|
|
72
73
|
streamingStarted: streamingStarted ?? this.streamingStarted,
|
|
@@ -74,39 +75,56 @@ class LlmChatState {
|
|
|
74
75
|
}
|
|
75
76
|
}
|
|
76
77
|
|
|
77
|
-
/// Manages
|
|
78
|
+
/// Manages AI chat state for the conversation currently selected
|
|
79
|
+
/// ([selectedConversationIdProvider]): loads that conversation's history,
|
|
78
80
|
/// persists each message, and streams the assistant reply word-by-word via SSE.
|
|
79
|
-
|
|
80
|
-
|
|
81
|
+
/// Re-runs [build] whenever the selected conversation changes.
|
|
82
|
+
final aiChatNotifierProvider =
|
|
83
|
+
AsyncNotifierProvider<AiChatNotifier, AiChatState>(AiChatNotifier.new);
|
|
81
84
|
|
|
82
|
-
class
|
|
85
|
+
class AiChatNotifier extends AsyncNotifier<AiChatState> {
|
|
83
86
|
final Logger _logger = Logger();
|
|
84
87
|
|
|
88
|
+
/// The conversation a reply is currently streaming into. Used to ignore UI
|
|
89
|
+
/// updates if the user switches conversations mid-stream (the reply is still
|
|
90
|
+
/// persisted to the right conversation, just not shown on the wrong one).
|
|
91
|
+
String? _streamingConversationId;
|
|
92
|
+
|
|
93
|
+
bool get _streamStillActive =>
|
|
94
|
+
ref.read(selectedConversationIdProvider) == _streamingConversationId;
|
|
95
|
+
|
|
85
96
|
@override
|
|
86
|
-
Future<
|
|
97
|
+
Future<AiChatState> build() async {
|
|
98
|
+
final conversationId = ref.watch(selectedConversationIdProvider);
|
|
87
99
|
final userId = ref.read(userStateNotifierProvider).user.idOrNull;
|
|
88
|
-
if (
|
|
100
|
+
if (conversationId == null || userId == null) {
|
|
101
|
+
return const AiChatState(messages: []);
|
|
102
|
+
}
|
|
89
103
|
|
|
90
104
|
try {
|
|
91
|
-
final entities = await ref
|
|
92
|
-
|
|
105
|
+
final entities = await ref
|
|
106
|
+
.read(aiChatApiProvider)
|
|
107
|
+
.loadMessages(userId, conversationId);
|
|
108
|
+
return AiChatState(
|
|
93
109
|
messages: entities.map(ChatMessage.fromEntity).toList(),
|
|
94
110
|
);
|
|
95
111
|
} catch (e) {
|
|
96
|
-
_logger.e('Failed to load
|
|
112
|
+
_logger.e('Failed to load AI chat history: $e');
|
|
97
113
|
// Graceful fallback: start fresh if the backend is unavailable.
|
|
98
|
-
return const
|
|
114
|
+
return const AiChatState(messages: []);
|
|
99
115
|
}
|
|
100
116
|
}
|
|
101
117
|
|
|
102
118
|
/// Adds the user message to the conversation, persists it, then streams
|
|
103
119
|
/// the assistant reply chunk-by-chunk via SSE, updating state on each token.
|
|
104
120
|
Future<void> sendMessage(String prompt) async {
|
|
121
|
+
final conversationId = ref.read(selectedConversationIdProvider);
|
|
105
122
|
final current = switch (state) {
|
|
106
123
|
AsyncData(:final value) => value,
|
|
107
124
|
_ => null,
|
|
108
125
|
};
|
|
109
|
-
if (current == null || current.isReplying) return;
|
|
126
|
+
if (conversationId == null || current == null || current.isReplying) return;
|
|
127
|
+
_streamingConversationId = conversationId;
|
|
110
128
|
|
|
111
129
|
final userMsg = ChatMessage.user(prompt);
|
|
112
130
|
state = AsyncData(
|
|
@@ -117,26 +135,29 @@ class LlmChatNotifier extends AsyncNotifier<LlmChatState> {
|
|
|
117
135
|
),
|
|
118
136
|
);
|
|
119
137
|
|
|
120
|
-
_persistMessage(userMsg);
|
|
138
|
+
_persistMessage(userMsg, conversationId);
|
|
121
139
|
|
|
122
140
|
// Pass only the last _kMaxContextMessages messages to the AI.
|
|
123
141
|
// The full history is still stored in the DB for the user to read.
|
|
124
|
-
final allMessages = (state as AsyncData<
|
|
142
|
+
final allMessages = (state as AsyncData<AiChatState>).value.messages;
|
|
125
143
|
final history = allMessages.length > _kMaxContextMessages
|
|
126
144
|
? allMessages.sublist(allMessages.length - _kMaxContextMessages)
|
|
127
145
|
: allMessages;
|
|
128
|
-
await _requestReplyStream(history);
|
|
146
|
+
await _requestReplyStream(history, conversationId);
|
|
129
147
|
}
|
|
130
148
|
|
|
131
149
|
// ---------------------------------------------------------------------------
|
|
132
150
|
// SSE streaming
|
|
133
151
|
// ---------------------------------------------------------------------------
|
|
134
152
|
|
|
135
|
-
Future<void> _requestReplyStream(
|
|
136
|
-
|
|
137
|
-
|
|
153
|
+
Future<void> _requestReplyStream(
|
|
154
|
+
List<ChatMessage> history,
|
|
155
|
+
String conversationId,
|
|
156
|
+
) async {
|
|
157
|
+
final t = ref.read(translationsProvider).ai_chat;
|
|
158
|
+
final String aiChatEndpoint = AppEnv.aiChatEndpoint;
|
|
138
159
|
|
|
139
|
-
if (
|
|
160
|
+
if (aiChatEndpoint.isEmpty) {
|
|
140
161
|
_finalizeAssistantMessage(t.error_not_configured);
|
|
141
162
|
return;
|
|
142
163
|
}
|
|
@@ -158,7 +179,7 @@ class LlmChatNotifier extends AsyncNotifier<LlmChatState> {
|
|
|
158
179
|
|
|
159
180
|
try {
|
|
160
181
|
final response = await dio.post<ResponseBody>(
|
|
161
|
-
|
|
182
|
+
aiChatEndpoint,
|
|
162
183
|
data: {
|
|
163
184
|
'message': history.last.content,
|
|
164
185
|
'history': history
|
|
@@ -199,7 +220,7 @@ class LlmChatNotifier extends AsyncNotifier<LlmChatState> {
|
|
|
199
220
|
// Persist the complete assistant reply once the stream ends.
|
|
200
221
|
final fullContent = contentBuffer.toString();
|
|
201
222
|
if (fullContent.isNotEmpty) {
|
|
202
|
-
_persistMessage(ChatMessage.assistant(fullContent));
|
|
223
|
+
_persistMessage(ChatMessage.assistant(fullContent), conversationId);
|
|
203
224
|
} else {
|
|
204
225
|
// Stream ended with no content (e.g. error event)
|
|
205
226
|
_finalizeAssistantMessage(t.error_no_reply);
|
|
@@ -211,19 +232,21 @@ class LlmChatNotifier extends AsyncNotifier<LlmChatState> {
|
|
|
211
232
|
AsyncData(:final value) => value,
|
|
212
233
|
_ => null,
|
|
213
234
|
};
|
|
214
|
-
if (latest != null) {
|
|
235
|
+
if (latest != null && _streamStillActive) {
|
|
215
236
|
state = AsyncData(
|
|
216
237
|
latest.copyWith(isReplying: false, streamingStarted: false),
|
|
217
238
|
);
|
|
218
239
|
}
|
|
219
240
|
} catch (error) {
|
|
220
|
-
_logger.e('
|
|
241
|
+
_logger.e('AI chat stream failed: $error');
|
|
221
242
|
_finalizeAssistantMessage(t.error_network);
|
|
222
243
|
}
|
|
223
244
|
}
|
|
224
245
|
|
|
225
246
|
/// Called on every incoming SSE token to update the last assistant bubble.
|
|
226
247
|
void _appendStreamingChunk(String partialContent) {
|
|
248
|
+
// Ignore chunks if the user navigated to another conversation mid-stream.
|
|
249
|
+
if (!_streamStillActive) return;
|
|
227
250
|
final current = switch (state) {
|
|
228
251
|
AsyncData(:final value) => value,
|
|
229
252
|
_ => null,
|
|
@@ -241,22 +264,24 @@ class LlmChatNotifier extends AsyncNotifier<LlmChatState> {
|
|
|
241
264
|
: [...msgs, ChatMessage.assistant(partialContent)];
|
|
242
265
|
|
|
243
266
|
state = AsyncData(
|
|
244
|
-
|
|
267
|
+
AiChatState(messages: newMsgs, isReplying: true, streamingStarted: true),
|
|
245
268
|
);
|
|
246
269
|
}
|
|
247
270
|
|
|
248
271
|
/// Replaces (or appends) the assistant bubble with [content] and marks
|
|
249
272
|
/// the reply as finished. Used for error messages and empty-stream fallback.
|
|
250
273
|
void _finalizeAssistantMessage(String content) {
|
|
274
|
+
// Ignore if the user navigated to another conversation mid-stream.
|
|
275
|
+
if (!_streamStillActive) return;
|
|
251
276
|
final current = switch (state) {
|
|
252
277
|
AsyncData(:final value) => value,
|
|
253
|
-
_ => const
|
|
278
|
+
_ => const AiChatState(messages: []),
|
|
254
279
|
};
|
|
255
280
|
final msgs = current.messages;
|
|
256
281
|
final newMsgs = current.streamingStarted
|
|
257
282
|
? [...msgs.sublist(0, msgs.length - 1), ChatMessage.assistant(content)]
|
|
258
283
|
: [...msgs, ChatMessage.assistant(content)];
|
|
259
|
-
state = AsyncData(
|
|
284
|
+
state = AsyncData(AiChatState(messages: newMsgs));
|
|
260
285
|
}
|
|
261
286
|
|
|
262
287
|
// ---------------------------------------------------------------------------
|
|
@@ -302,14 +327,24 @@ class LlmChatNotifier extends AsyncNotifier<LlmChatState> {
|
|
|
302
327
|
// Persistence
|
|
303
328
|
// ---------------------------------------------------------------------------
|
|
304
329
|
|
|
305
|
-
void _persistMessage(ChatMessage message) {
|
|
330
|
+
void _persistMessage(ChatMessage message, String conversationId) {
|
|
306
331
|
final userId = ref.read(userStateNotifierProvider).user.idOrNull;
|
|
307
332
|
if (userId == null) return;
|
|
333
|
+
final entity = message.toEntity();
|
|
308
334
|
ref
|
|
309
|
-
.read(
|
|
310
|
-
.saveMessage(userId,
|
|
335
|
+
.read(aiChatApiProvider)
|
|
336
|
+
.saveMessage(userId, conversationId, entity)
|
|
311
337
|
.catchError((e) {
|
|
312
338
|
_logger.e('Failed to persist message: $e');
|
|
313
339
|
});
|
|
340
|
+
// Keep the conversation list preview + ordering in sync.
|
|
341
|
+
ref
|
|
342
|
+
.read(aiConversationsNotifierProvider.notifier)
|
|
343
|
+
.touch(
|
|
344
|
+
conversationId,
|
|
345
|
+
role: message.role,
|
|
346
|
+
content: message.content,
|
|
347
|
+
at: entity.createdAt,
|
|
348
|
+
);
|
|
314
349
|
}
|
|
315
350
|
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
2
|
+
import 'package:flutter_riverpod/legacy.dart';
|
|
3
|
+
import 'package:kasy_kit/core/states/user_state_notifier.dart';
|
|
4
|
+
import 'package:kasy_kit/features/ai_chat/api/ai_chat_api.dart';
|
|
5
|
+
import 'package:kasy_kit/features/ai_chat/api/ai_chat_conversation_entity.dart';
|
|
6
|
+
import 'package:logger/logger.dart';
|
|
7
|
+
|
|
8
|
+
/// The conversation currently open in the detail pane (null = none selected).
|
|
9
|
+
///
|
|
10
|
+
/// On wide layouts a null value shows the "pick a conversation" placeholder;
|
|
11
|
+
/// on phones a null value means the list is showing (no conversation pushed).
|
|
12
|
+
final selectedConversationIdProvider = StateProvider<String?>((ref) => null);
|
|
13
|
+
|
|
14
|
+
/// Loads and mutates the user's conversation list. Backend-agnostic: it only
|
|
15
|
+
/// talks to [aiChatApiProvider] and [AiChatConversationEntity], so this single
|
|
16
|
+
/// file is shared across the Firebase / Supabase / API templates.
|
|
17
|
+
final aiConversationsNotifierProvider =
|
|
18
|
+
AsyncNotifierProvider<
|
|
19
|
+
AiConversationsNotifier,
|
|
20
|
+
List<AiChatConversationEntity>
|
|
21
|
+
>(AiConversationsNotifier.new);
|
|
22
|
+
|
|
23
|
+
class AiConversationsNotifier
|
|
24
|
+
extends AsyncNotifier<List<AiChatConversationEntity>> {
|
|
25
|
+
final Logger _logger = Logger();
|
|
26
|
+
|
|
27
|
+
String? get _userId => ref.read(userStateNotifierProvider).user.idOrNull;
|
|
28
|
+
|
|
29
|
+
List<AiChatConversationEntity>? get _current => switch (state) {
|
|
30
|
+
AsyncData(:final value) => value,
|
|
31
|
+
_ => null,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
@override
|
|
35
|
+
Future<List<AiChatConversationEntity>> build() async {
|
|
36
|
+
final userId = _userId;
|
|
37
|
+
if (userId == null) return const [];
|
|
38
|
+
try {
|
|
39
|
+
return await ref.read(aiChatApiProvider).loadConversations(userId);
|
|
40
|
+
} catch (e) {
|
|
41
|
+
_logger.e('Failed to load conversations: $e');
|
|
42
|
+
// Graceful fallback: start with an empty list if the backend is down.
|
|
43
|
+
return const [];
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/// Creates an empty conversation, prepends it to the list, and returns its id.
|
|
48
|
+
Future<String?> create() async {
|
|
49
|
+
final userId = _userId;
|
|
50
|
+
if (userId == null) return null;
|
|
51
|
+
try {
|
|
52
|
+
final conversation = await ref
|
|
53
|
+
.read(aiChatApiProvider)
|
|
54
|
+
.createConversation(userId);
|
|
55
|
+
state = AsyncData([conversation, ...?_current]);
|
|
56
|
+
return conversation.id;
|
|
57
|
+
} catch (e) {
|
|
58
|
+
_logger.e('Failed to create conversation: $e');
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/// Deletes a conversation (and its messages) and removes it from the list.
|
|
64
|
+
Future<void> delete(String conversationId) async {
|
|
65
|
+
final userId = _userId;
|
|
66
|
+
if (userId == null) return;
|
|
67
|
+
// Optimistic removal so the UI feels instant.
|
|
68
|
+
state = AsyncData(
|
|
69
|
+
(_current ?? const []).where((c) => c.id != conversationId).toList(),
|
|
70
|
+
);
|
|
71
|
+
if (ref.read(selectedConversationIdProvider) == conversationId) {
|
|
72
|
+
ref.read(selectedConversationIdProvider.notifier).state = null;
|
|
73
|
+
}
|
|
74
|
+
try {
|
|
75
|
+
await ref
|
|
76
|
+
.read(aiChatApiProvider)
|
|
77
|
+
.deleteConversation(userId, conversationId);
|
|
78
|
+
} catch (e) {
|
|
79
|
+
_logger.e('Failed to delete conversation: $e');
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/// Updates the denormalized last-message fields and moves the conversation to
|
|
84
|
+
/// the top of the list. Called by the chat notifier after each message.
|
|
85
|
+
void touch(
|
|
86
|
+
String conversationId, {
|
|
87
|
+
required String role,
|
|
88
|
+
required String content,
|
|
89
|
+
required DateTime at,
|
|
90
|
+
}) {
|
|
91
|
+
final current = _current;
|
|
92
|
+
if (current == null) return;
|
|
93
|
+
final index = current.indexWhere((c) => c.id == conversationId);
|
|
94
|
+
if (index == -1) return;
|
|
95
|
+
final updated = current[index].copyWith(
|
|
96
|
+
lastMessageRole: role,
|
|
97
|
+
lastMessageContent: content,
|
|
98
|
+
updatedAt: at,
|
|
99
|
+
);
|
|
100
|
+
final rest = [...current]..removeAt(index);
|
|
101
|
+
state = AsyncData([updated, ...rest]);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -8,19 +8,19 @@ import 'package:kasy_kit/features/settings/ui/widgets/avatar_utils.dart';
|
|
|
8
8
|
import 'package:kasy_kit/i18n/translations.g.dart';
|
|
9
9
|
|
|
10
10
|
/// Diameter used beside chat bubbles.
|
|
11
|
-
const double
|
|
11
|
+
const double kAiChatAvatarDiameter = 32;
|
|
12
12
|
|
|
13
13
|
/// Diameter shown in the empty state hero.
|
|
14
|
-
const double
|
|
14
|
+
const double kAiChatEmptyAvatarDiameter = 64;
|
|
15
15
|
|
|
16
16
|
/// Bundled launcher icon ([flutter_launcher_icons] → `assets/images/icon.png`).
|
|
17
|
-
const String
|
|
17
|
+
const String kAiChatAppIconAsset = 'assets/images/icon.png';
|
|
18
18
|
|
|
19
19
|
/// Project / assistant avatar — app icon when bundled, [KasyAvatar] fallback otherwise.
|
|
20
|
-
class
|
|
21
|
-
const
|
|
20
|
+
class AiChatAssistantAvatar extends StatelessWidget {
|
|
21
|
+
const AiChatAssistantAvatar({
|
|
22
22
|
super.key,
|
|
23
|
-
this.diameter =
|
|
23
|
+
this.diameter = kAiChatAvatarDiameter,
|
|
24
24
|
this.showShadow = false,
|
|
25
25
|
});
|
|
26
26
|
|
|
@@ -31,7 +31,7 @@ class LlmChatAssistantAvatar extends StatelessWidget {
|
|
|
31
31
|
Widget build(BuildContext context) {
|
|
32
32
|
return KasyAvatar(
|
|
33
33
|
diameter: diameter,
|
|
34
|
-
image: const AssetImage(
|
|
34
|
+
image: const AssetImage(kAiChatAppIconAsset),
|
|
35
35
|
icon: KasyIcons.packageOutline,
|
|
36
36
|
fallbackSurface: KasyAvatarFallbackSurface.soft,
|
|
37
37
|
showShadow: showShadow,
|
|
@@ -41,10 +41,10 @@ class LlmChatAssistantAvatar extends StatelessWidget {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
/// User avatar resolved from profile photo, initials, or [KasyAvatar] fallback.
|
|
44
|
-
class
|
|
45
|
-
const
|
|
44
|
+
class AiChatUserAvatar extends ConsumerWidget {
|
|
45
|
+
const AiChatUserAvatar({
|
|
46
46
|
super.key,
|
|
47
|
-
this.diameter =
|
|
47
|
+
this.diameter = kAiChatAvatarDiameter,
|
|
48
48
|
this.showShadow = false,
|
|
49
49
|
});
|
|
50
50
|
|
|
@@ -61,7 +61,7 @@ class LlmChatUserAvatar extends ConsumerWidget {
|
|
|
61
61
|
:final email,
|
|
62
62
|
:final avatarPath,
|
|
63
63
|
) =>
|
|
64
|
-
|
|
64
|
+
_AiChatUserAvatarContent(
|
|
65
65
|
diameter: diameter,
|
|
66
66
|
userId: id,
|
|
67
67
|
avatarPath: avatarPath,
|
|
@@ -91,8 +91,8 @@ class LlmChatUserAvatar extends ConsumerWidget {
|
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
class
|
|
95
|
-
const
|
|
94
|
+
class _AiChatUserAvatarContent extends StatelessWidget {
|
|
95
|
+
const _AiChatUserAvatarContent({
|
|
96
96
|
required this.diameter,
|
|
97
97
|
required this.userId,
|
|
98
98
|
required this.avatarPath,
|
|
@@ -6,11 +6,11 @@ import 'package:kasy_kit/core/theme/theme.dart';
|
|
|
6
6
|
import 'package:kasy_kit/i18n/translations.g.dart';
|
|
7
7
|
|
|
8
8
|
/// Maximum characters allowed in a single chat message.
|
|
9
|
-
const int
|
|
9
|
+
const int kAiChatMaxMessageLength = 2000;
|
|
10
10
|
|
|
11
11
|
/// Unified chat bar: primary [KasyTextField] shell (surface, border, shadow) + send orb inside.
|
|
12
|
-
class
|
|
13
|
-
const
|
|
12
|
+
class AiChatComposer extends StatefulWidget {
|
|
13
|
+
const AiChatComposer({
|
|
14
14
|
super.key,
|
|
15
15
|
required this.controller,
|
|
16
16
|
required this.onSend,
|
|
@@ -24,10 +24,10 @@ class LlmChatComposer extends StatefulWidget {
|
|
|
24
24
|
static const int _maxLines = 5;
|
|
25
25
|
|
|
26
26
|
@override
|
|
27
|
-
State<
|
|
27
|
+
State<AiChatComposer> createState() => _AiChatComposerState();
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
class
|
|
30
|
+
class _AiChatComposerState extends State<AiChatComposer> {
|
|
31
31
|
bool _canSend = false;
|
|
32
32
|
|
|
33
33
|
@override
|
|
@@ -45,7 +45,7 @@ class _LlmChatComposerState extends State<LlmChatComposer> {
|
|
|
45
45
|
|
|
46
46
|
bool _hasSendableText(String value) {
|
|
47
47
|
final String trimmed = value.trim();
|
|
48
|
-
return trimmed.isNotEmpty && trimmed.length <=
|
|
48
|
+
return trimmed.isNotEmpty && trimmed.length <= kAiChatMaxMessageLength;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
void _onTextChanged() {
|
|
@@ -90,13 +90,13 @@ class _LlmChatComposerState extends State<LlmChatComposer> {
|
|
|
90
90
|
controller: widget.controller,
|
|
91
91
|
enabled: enabled,
|
|
92
92
|
variant: KasyTextFieldVariant.embedded,
|
|
93
|
-
hint: t.
|
|
93
|
+
hint: t.ai_chat.hint,
|
|
94
94
|
minLines: 1,
|
|
95
|
-
maxLines:
|
|
95
|
+
maxLines: AiChatComposer._maxLines,
|
|
96
96
|
textCapitalization: TextCapitalization.sentences,
|
|
97
97
|
textInputAction: TextInputAction.newline,
|
|
98
98
|
inputFormatters: [
|
|
99
|
-
LengthLimitingTextInputFormatter(
|
|
99
|
+
LengthLimitingTextInputFormatter(kAiChatMaxMessageLength),
|
|
100
100
|
],
|
|
101
101
|
onSubmitted: (_) => _handleSend(),
|
|
102
102
|
),
|
|
@@ -112,7 +112,7 @@ class _LlmChatComposerState extends State<LlmChatComposer> {
|
|
|
112
112
|
size: KasyButtonSize.small,
|
|
113
113
|
iconOnlyLayoutExtent: 36,
|
|
114
114
|
iconGlyphSize: 18,
|
|
115
|
-
semanticLabel: t.
|
|
115
|
+
semanticLabel: t.ai_chat.hint,
|
|
116
116
|
),
|
|
117
117
|
),
|
|
118
118
|
],
|