✨ Thinking billing check
This commit is contained in:
@@ -1323,5 +1323,6 @@
|
|||||||
"popularity": "Popularity",
|
"popularity": "Popularity",
|
||||||
"descendingOrder": "Descending Order",
|
"descendingOrder": "Descending Order",
|
||||||
"selectDate": "Select Date",
|
"selectDate": "Select Date",
|
||||||
"pinnedPosts": "Pinned Posts"
|
"pinnedPosts": "Pinned Posts",
|
||||||
|
"thoughtUnpaidHint": "Thinking unavaiable due to unpaid orders"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1090,5 +1090,6 @@
|
|||||||
"thoughtNewConversation": "开始新对话",
|
"thoughtNewConversation": "开始新对话",
|
||||||
"thoughtParseError": "解析 AI 响应失败",
|
"thoughtParseError": "解析 AI 响应失败",
|
||||||
"aiThought": "寻思",
|
"aiThought": "寻思",
|
||||||
"aiThoughtTitle": "让 SN 酱寻思寻思"
|
"aiThoughtTitle": "让 SN 酱寻思寻思",
|
||||||
|
"thoughtUnpaidHint": "寻思因为有未支付的订单而被禁用"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import "package:riverpod_annotation/riverpod_annotation.dart";
|
|||||||
import "package:hooks_riverpod/hooks_riverpod.dart";
|
import "package:hooks_riverpod/hooks_riverpod.dart";
|
||||||
import "package:island/models/thought.dart";
|
import "package:island/models/thought.dart";
|
||||||
import "package:island/pods/network.dart";
|
import "package:island/pods/network.dart";
|
||||||
|
import "package:island/widgets/alert.dart";
|
||||||
import "package:island/widgets/app_scaffold.dart";
|
import "package:island/widgets/app_scaffold.dart";
|
||||||
import "package:island/widgets/response.dart";
|
import "package:island/widgets/response.dart";
|
||||||
import "package:island/widgets/thought/thought_sequence_list.dart";
|
import "package:island/widgets/thought/thought_sequence_list.dart";
|
||||||
@@ -14,6 +15,13 @@ import "package:material_symbols_icons/material_symbols_icons.dart";
|
|||||||
|
|
||||||
part 'think.g.dart';
|
part 'think.g.dart';
|
||||||
|
|
||||||
|
@riverpod
|
||||||
|
Future<bool> thoughtAvailableStaus(Ref ref) async {
|
||||||
|
final apiClient = ref.watch(apiClientProvider);
|
||||||
|
final response = await apiClient.get('/insight/billing/status');
|
||||||
|
return response.data['status'] == 'ok';
|
||||||
|
}
|
||||||
|
|
||||||
@riverpod
|
@riverpod
|
||||||
Future<List<SnThinkingThought>> thoughtSequence(
|
Future<List<SnThinkingThought>> thoughtSequence(
|
||||||
Ref ref,
|
Ref ref,
|
||||||
@@ -47,6 +55,8 @@ class ThoughtScreen extends HookConsumerWidget {
|
|||||||
? initialThoughts.first.sequence!.topic
|
? initialThoughts.first.sequence!.topic
|
||||||
: 'aiThought'.tr();
|
: 'aiThought'.tr();
|
||||||
|
|
||||||
|
final statusAsync = ref.watch(thoughtAvailableStausProvider);
|
||||||
|
|
||||||
return AppScaffold(
|
return AppScaffold(
|
||||||
isNoBackground: false,
|
isNoBackground: false,
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
@@ -71,23 +81,91 @@ class ThoughtScreen extends HookConsumerWidget {
|
|||||||
const Gap(8),
|
const Gap(8),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: thoughts.when(
|
body: statusAsync.maybeWhen(
|
||||||
data:
|
data: (status) {
|
||||||
(thoughtList) => ThoughtChatInterface(
|
final retry = useMemoized(
|
||||||
initialThoughts: thoughtList,
|
() => () async {
|
||||||
initialTopic: initialTopic,
|
showLoadingModal(context);
|
||||||
),
|
try {
|
||||||
loading: () => const Center(child: CircularProgressIndicator()),
|
await ref
|
||||||
error:
|
.read(apiClientProvider)
|
||||||
(error, _) => ResponseErrorWidget(
|
.post('/insight/billing/retry');
|
||||||
error: error,
|
showSnackBar('Retried billing process');
|
||||||
onRetry:
|
ref.invalidate(thoughtAvailableStausProvider);
|
||||||
() =>
|
} catch (e) {
|
||||||
selectedSequenceId.value != null
|
showSnackBar('Failed to retry billing');
|
||||||
? ref.invalidate(
|
}
|
||||||
thoughtSequenceProvider(selectedSequenceId.value!),
|
hideLoadingModal(context);
|
||||||
)
|
},
|
||||||
: null,
|
[context, ref],
|
||||||
|
);
|
||||||
|
|
||||||
|
final thoughtsBody = thoughts.when(
|
||||||
|
data:
|
||||||
|
(thoughtList) => ThoughtChatInterface(
|
||||||
|
initialThoughts: thoughtList,
|
||||||
|
initialTopic: initialTopic,
|
||||||
|
isDisabled: !status,
|
||||||
|
),
|
||||||
|
loading: () => const Center(child: CircularProgressIndicator()),
|
||||||
|
error:
|
||||||
|
(error, _) => ResponseErrorWidget(
|
||||||
|
error: error,
|
||||||
|
onRetry:
|
||||||
|
() =>
|
||||||
|
selectedSequenceId.value != null
|
||||||
|
? ref.invalidate(
|
||||||
|
thoughtSequenceProvider(
|
||||||
|
selectedSequenceId.value!,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return status
|
||||||
|
? thoughtsBody
|
||||||
|
: Column(
|
||||||
|
children: [
|
||||||
|
MaterialBanner(
|
||||||
|
leading: const Icon(Symbols.error),
|
||||||
|
content: const Text(
|
||||||
|
'You have unpaid orders. Please settle your payment to continue using the service.',
|
||||||
|
style: TextStyle(fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
retry();
|
||||||
|
},
|
||||||
|
child: Text('retry'.tr()),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Expanded(child: thoughtsBody),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
orElse:
|
||||||
|
() => thoughts.when(
|
||||||
|
data:
|
||||||
|
(thoughtList) => ThoughtChatInterface(
|
||||||
|
initialThoughts: thoughtList,
|
||||||
|
initialTopic: initialTopic,
|
||||||
|
),
|
||||||
|
loading: () => const Center(child: CircularProgressIndicator()),
|
||||||
|
error:
|
||||||
|
(error, _) => ResponseErrorWidget(
|
||||||
|
error: error,
|
||||||
|
onRetry:
|
||||||
|
() =>
|
||||||
|
selectedSequenceId.value != null
|
||||||
|
? ref.invalidate(
|
||||||
|
thoughtSequenceProvider(
|
||||||
|
selectedSequenceId.value!,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -6,6 +6,25 @@ part of 'think.dart';
|
|||||||
// RiverpodGenerator
|
// RiverpodGenerator
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
|
String _$thoughtAvailableStausHash() =>
|
||||||
|
r'720e04e56bff8c4d4ca6854ce997da4e7926c84c';
|
||||||
|
|
||||||
|
/// See also [thoughtAvailableStaus].
|
||||||
|
@ProviderFor(thoughtAvailableStaus)
|
||||||
|
final thoughtAvailableStausProvider = AutoDisposeFutureProvider<bool>.internal(
|
||||||
|
thoughtAvailableStaus,
|
||||||
|
name: r'thoughtAvailableStausProvider',
|
||||||
|
debugGetCreateSourceHash:
|
||||||
|
const bool.fromEnvironment('dart.vm.product')
|
||||||
|
? null
|
||||||
|
: _$thoughtAvailableStausHash,
|
||||||
|
dependencies: null,
|
||||||
|
allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||||
|
// ignore: unused_element
|
||||||
|
typedef ThoughtAvailableStausRef = AutoDisposeFutureProviderRef<bool>;
|
||||||
String _$thoughtSequenceHash() => r'2a93c0a04f9a720ba474c02a36502940fb7f3ed7';
|
String _$thoughtSequenceHash() => r'2a93c0a04f9a720ba474c02a36502940fb7f3ed7';
|
||||||
|
|
||||||
/// Copied from Dart SDK
|
/// Copied from Dart SDK
|
||||||
|
|||||||
@@ -1,8 +1,13 @@
|
|||||||
import "package:easy_localization/easy_localization.dart";
|
import "package:easy_localization/easy_localization.dart";
|
||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
|
import "package:flutter_hooks/flutter_hooks.dart";
|
||||||
import "package:hooks_riverpod/hooks_riverpod.dart";
|
import "package:hooks_riverpod/hooks_riverpod.dart";
|
||||||
|
import "package:island/pods/network.dart";
|
||||||
|
import "package:island/screens/thought/think.dart";
|
||||||
|
import "package:island/widgets/alert.dart";
|
||||||
import "package:island/widgets/content/sheet.dart";
|
import "package:island/widgets/content/sheet.dart";
|
||||||
import "package:island/widgets/thought/thought_shared.dart";
|
import "package:island/widgets/thought/thought_shared.dart";
|
||||||
|
import "package:material_symbols_icons/material_symbols_icons.dart";
|
||||||
|
|
||||||
class ThoughtSheet extends HookConsumerWidget {
|
class ThoughtSheet extends HookConsumerWidget {
|
||||||
final List<Map<String, dynamic>> attachedMessages;
|
final List<Map<String, dynamic>> attachedMessages;
|
||||||
@@ -39,11 +44,62 @@ class ThoughtSheet extends HookConsumerWidget {
|
|||||||
attachedPosts: attachedPosts,
|
attachedPosts: attachedPosts,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
final statusAsync = ref.watch(thoughtAvailableStausProvider);
|
||||||
|
|
||||||
return SheetScaffold(
|
return SheetScaffold(
|
||||||
titleText: chatState.currentTopic.value ?? 'aiThought'.tr(),
|
titleText: chatState.currentTopic.value ?? 'aiThought'.tr(),
|
||||||
child: ThoughtChatInterface(
|
child: statusAsync.maybeWhen(
|
||||||
attachedMessages: attachedMessages,
|
data: (status) {
|
||||||
attachedPosts: attachedPosts,
|
final retry = useMemoized(
|
||||||
|
() => () async {
|
||||||
|
showLoadingModal(context);
|
||||||
|
try {
|
||||||
|
await ref
|
||||||
|
.read(apiClientProvider)
|
||||||
|
.post('/insight/billing/retry');
|
||||||
|
showSnackBar('Retried billing process');
|
||||||
|
ref.invalidate(thoughtAvailableStausProvider);
|
||||||
|
} catch (e) {
|
||||||
|
showSnackBar('Failed to retry billing');
|
||||||
|
}
|
||||||
|
hideLoadingModal(context);
|
||||||
|
},
|
||||||
|
[context, ref],
|
||||||
|
);
|
||||||
|
|
||||||
|
final chatInterface = ThoughtChatInterface(
|
||||||
|
attachedMessages: attachedMessages,
|
||||||
|
attachedPosts: attachedPosts,
|
||||||
|
isDisabled: !status,
|
||||||
|
);
|
||||||
|
return status
|
||||||
|
? chatInterface
|
||||||
|
: Column(
|
||||||
|
children: [
|
||||||
|
MaterialBanner(
|
||||||
|
leading: const Icon(Symbols.error),
|
||||||
|
content: const Text(
|
||||||
|
'You have unpaid orders. Please settle your payment to continue using the service.',
|
||||||
|
style: TextStyle(fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
retry();
|
||||||
|
},
|
||||||
|
child: Text('retry'.tr()),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Expanded(child: chatInterface),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
orElse:
|
||||||
|
() => ThoughtChatInterface(
|
||||||
|
attachedMessages: attachedMessages,
|
||||||
|
attachedPosts: attachedPosts,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -359,6 +359,7 @@ class ThoughtChatInterface extends HookConsumerWidget {
|
|||||||
final String? initialTopic;
|
final String? initialTopic;
|
||||||
final List<Map<String, dynamic>> attachedMessages;
|
final List<Map<String, dynamic>> attachedMessages;
|
||||||
final List<String> attachedPosts;
|
final List<String> attachedPosts;
|
||||||
|
final bool isDisabled;
|
||||||
|
|
||||||
const ThoughtChatInterface({
|
const ThoughtChatInterface({
|
||||||
super.key,
|
super.key,
|
||||||
@@ -366,6 +367,7 @@ class ThoughtChatInterface extends HookConsumerWidget {
|
|||||||
this.initialTopic,
|
this.initialTopic,
|
||||||
this.attachedMessages = const [],
|
this.attachedMessages = const [],
|
||||||
this.attachedPosts = const [],
|
this.attachedPosts = const [],
|
||||||
|
this.isDisabled = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -466,6 +468,7 @@ class ThoughtChatInterface extends HookConsumerWidget {
|
|||||||
onSend: chatState.sendMessage,
|
onSend: chatState.sendMessage,
|
||||||
attachedMessages: attachedMessages,
|
attachedMessages: attachedMessages,
|
||||||
attachedPosts: attachedPosts,
|
attachedPosts: attachedPosts,
|
||||||
|
isDisabled: isDisabled,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -509,6 +512,7 @@ class ThoughtInput extends HookWidget {
|
|||||||
final VoidCallback onSend;
|
final VoidCallback onSend;
|
||||||
final List<Map<String, dynamic>>? attachedMessages;
|
final List<Map<String, dynamic>>? attachedMessages;
|
||||||
final List<String>? attachedPosts;
|
final List<String>? attachedPosts;
|
||||||
|
final bool isDisabled;
|
||||||
|
|
||||||
const ThoughtInput({
|
const ThoughtInput({
|
||||||
super.key,
|
super.key,
|
||||||
@@ -517,6 +521,7 @@ class ThoughtInput extends HookWidget {
|
|||||||
required this.onSend,
|
required this.onSend,
|
||||||
this.attachedMessages,
|
this.attachedMessages,
|
||||||
this.attachedPosts,
|
this.attachedPosts,
|
||||||
|
this.isDisabled = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -607,11 +612,13 @@ class ThoughtInput extends HookWidget {
|
|||||||
child: TextField(
|
child: TextField(
|
||||||
controller: messageController,
|
controller: messageController,
|
||||||
keyboardType: TextInputType.multiline,
|
keyboardType: TextInputType.multiline,
|
||||||
enabled: !isStreaming,
|
enabled: !isStreaming && !isDisabled,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
hintText:
|
hintText:
|
||||||
(isStreaming
|
(isStreaming
|
||||||
? 'thoughtStreamingHint'
|
? 'thoughtStreamingHint'
|
||||||
|
: isDisabled
|
||||||
|
? 'thoughtUnpaidHint'.tr()
|
||||||
: 'thoughtInputHint')
|
: 'thoughtInputHint')
|
||||||
.tr(),
|
.tr(),
|
||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
@@ -624,13 +631,16 @@ class ThoughtInput extends HookWidget {
|
|||||||
maxLines: 5,
|
maxLines: 5,
|
||||||
minLines: 1,
|
minLines: 1,
|
||||||
textInputAction: TextInputAction.send,
|
textInputAction: TextInputAction.send,
|
||||||
onSubmitted: (_) => onSend(),
|
onSubmitted:
|
||||||
|
(!isStreaming && !isDisabled)
|
||||||
|
? (_) => onSend()
|
||||||
|
: null,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: Icon(isStreaming ? Symbols.stop : Icons.send),
|
icon: Icon(isStreaming ? Symbols.stop : Icons.send),
|
||||||
color: Theme.of(context).colorScheme.primary,
|
color: Theme.of(context).colorScheme.primary,
|
||||||
onPressed: onSend,
|
onPressed: (!isStreaming && !isDisabled) ? onSend : null,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user