提交 8631150a authored 作者: songchuancai's avatar songchuancai

优化语音功能

上级 104d31dd
...@@ -8,7 +8,7 @@ if (localPropertiesFile.exists()) { ...@@ -8,7 +8,7 @@ if (localPropertiesFile.exists()) {
def flutterRoot = localProperties.getProperty('flutter.sdk') def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) { if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") throw new FileNotFoundException ("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
} }
def flutterVersionCode = localProperties.getProperty('flutter.versionCode') def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
......
...@@ -5,6 +5,8 @@ ...@@ -5,6 +5,8 @@
<uses-permission android:name="android.permission.BLUETOOTH"/> <uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/> <uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<queries> <queries>
<intent> <intent>
<action android:name="android.speech.RecognitionService" /> <action android:name="android.speech.RecognitionService" />
......
import 'package:allen/pallete.dart'; import 'package:allen/pallete.dart';
import 'package:animate_do/animate_do.dart'; import 'package:animate_do/animate_do.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_tts/flutter_tts.dart'; import 'package:flutter_tts/flutter_tts.dart';
import 'package:speech_to_text/speech_recognition_result.dart'; import 'package:speech_to_text/speech_recognition_result.dart';
import 'package:speech_to_text/speech_to_text.dart'; import 'package:speech_to_text/speech_to_text.dart';
import 'package:flutter/foundation.dart' show kIsWeb; import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
import 'models/conversation.dart'; import 'models/conversation.dart';
import 'services/storage_service.dart'; import 'services/storage_service.dart';
import 'services/chat_service.dart'; import 'services/chat_service.dart';
import 'models/chat_message.dart'; import 'models/chat_message.dart';
import 'models/user.dart'; import 'models/user.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'pages/apps_page.dart'; import 'pages/apps_page.dart';
import 'pages/chat_bubble.dart'; import 'pages/chat_bubble.dart';
class HomePage extends StatefulWidget { class HomePage extends StatefulWidget {
final String? customTitle; final String? customTitle;
final String? customDescription; final String? customDescription;
final String? customImageUrl; final String? customImageUrl;
final bool hideNavigation; final bool hideNavigation;
const HomePage({ const HomePage({
super.key, super.key,
this.customTitle, this.customTitle,
this.customDescription, this.customDescription,
this.customImageUrl, this.customImageUrl,
this.hideNavigation = false, this.hideNavigation = false,
}); });
@override @override
State<HomePage> createState() => _HomePageState(); State<HomePage> createState() => _HomePageState();
} }
class _HomePageState extends State<HomePage> { class _HomePageState extends State<HomePage> {
final speechToText = SpeechToText(); final speechToText = SpeechToText();
final flutterTts = FlutterTts(); final flutterTts = FlutterTts();
String lastWords = ''; String lastWords = '';
final OpenAIService openAIService = OpenAIService(); final OpenAIService openAIService = OpenAIService();
String? generatedContent; String? generatedContent;
String? generatedImageUrl; String? generatedImageUrl;
int start = 200; int start = 200;
int delay = 200; int delay = 200;
final TextEditingController _messageController = TextEditingController(); final TextEditingController _messageController = TextEditingController();
String currentStreamedContent = ''; String currentStreamedContent = '';
List<ChatMessage> messages = []; List<ChatMessage> messages = [];
bool _isLoading = false; bool _isLoading = false;
bool _isVoiceMode = true; bool _isVoiceMode = true;
bool _isListeningPressed = false; bool _isListeningPressed = false;
String _currentVoiceText = ''; String _currentVoiceText = '';
late StorageService _storageService; late StorageService _storageService;
late SharedPreferences _prefs; late SharedPreferences _prefs;
late Conversation _currentConversation; late Conversation _currentConversation;
List<Conversation> _conversations = []; List<Conversation> _conversations = [];
int _currentIndex = 0; int _currentIndex = 0;
@override bool _hasSpeechPermission = false;
void initState() {
super.initState(); @override
void initState() {
_initializeStorage(); super.initState();
initSpeechToText(); _initializeStorage();
_initTts(); initSpeechToText();
}
_initTts();
Future<void> _initializeStorage() async { }
_prefs = await SharedPreferences.getInstance();
Future<void> _initializeStorage() async {
_storageService = StorageService(_prefs); _prefs = await SharedPreferences.getInstance();
_conversations = await _storageService.getConversations(); _storageService = StorageService(_prefs);
// 每次进入应用创建一个新的会话 _conversations = await _storageService.getConversations();
_createNewConversation();
// if (_conversations.isEmpty) { // 每次进入应用创建一个新的会话
// _createNewConversation(); _createNewConversation();
// } else { // if (_conversations.isEmpty) {
// _currentConversation = _conversations.first; // _createNewConversation();
// } else {
// setState(() { // _currentConversation = _conversations.first;
// messages = _currentConversation.messages;
// }); // setState(() {
// } // messages = _currentConversation.messages;
} // });
// }
Future<void> _createNewConversation() async { }
final newConversation = Conversation(
id: const Uuid().v4(), Future<void> _createNewConversation() async {
title: '新会话 ${_conversations.length + 1}', final newConversation = Conversation(
createdAt: DateTime.now(), id: const Uuid().v4(),
messages: [], title: '新会话 ${_conversations.length + 1}',
); createdAt: DateTime.now(),
messages: [],
await _storageService.addConversation(newConversation); );
setState(() { await _storageService.addConversation(newConversation);
_conversations.insert(0, newConversation);
setState(() {
_currentConversation = newConversation; _conversations.insert(0, newConversation);
messages = []; _currentConversation = newConversation;
});
} messages = [];
});
Future<void> _updateCurrentConversation() async { }
_currentConversation = Conversation(
id: _currentConversation.id, Future<void> _updateCurrentConversation() async {
title: _currentConversation.title, _currentConversation = Conversation(
createdAt: _currentConversation.createdAt, id: _currentConversation.id,
messages: messages, title: _currentConversation.title,
); createdAt: _currentConversation.createdAt,
messages: messages,
await _storageService.updateConversation(_currentConversation); );
}
await _storageService.updateConversation(_currentConversation);
Future<void> _initTts() async { }
if (!kIsWeb) {
await flutterTts.setSharedInstance(true); Future<void> _initTts() async {
} if (!kIsWeb) {
await flutterTts.setSharedInstance(true);
setState(() {}); }
}
setState(() {});
Future<void> initSpeechToText() async { }
await speechToText.initialize();
Future<void> initSpeechToText() async {
setState(() {}); try {
} _hasSpeechPermission = await speechToText.initialize(
onStatus: (status) {
Future<void> startListening() async { print('语音状态: $status');
await speechToText.listen(onResult: onSpeechResult); if (status == 'notListening') {
setState(() => _isListeningPressed = false);
setState(() {}); }
} },
onError: (error) {
Future<void> stopListening() async { print('语音错误: $error');
await speechToText.stop(); setState(() => _isListeningPressed = false);
_showErrorDialog('语音初始化失败,请检查麦克风权限');
setState(() {}); },
} );
} catch (e) {
Future<void> onSpeechResult(SpeechRecognitionResult result) async { print('语音初始化错误: $e');
setState(() { _hasSpeechPermission = false;
lastWords = result.recognizedWords; }
setState(() {});
_currentVoiceText = result.recognizedWords; }
});
} Future<void> startListening() async {
if (!_hasSpeechPermission) {
Future<void> systemSpeak(String content) async { _showErrorDialog('请先授予麦克风权限');
try { setState(() => _isListeningPressed = false);
if (kIsWeb) { return;
// 设置语速 }
await flutterTts.setSpeechRate(1); try {
await speechToText.listen(
// 音调 onResult: onSpeechResult,
localeId: 'zh_CN',
await flutterTts.setPitch(0.8); listenMode: ListenMode.confirmation,
cancelOnError: true,
await flutterTts.speak(content); partialResults: true,
} else { );
await flutterTts.setSharedInstance(true); } catch (e) {
print('开始录音失败: $e');
await flutterTts.speak(content); setState(() => _isListeningPressed = false);
} _showErrorDialog('启动语音识别失败,请重试');
} catch (e) { }
print('TTS Error: $e'); }
}
} Future<void> stopListening() async {
await speechToText.stop();
Future<void> _sendMessage() async {
if (!mounted) return; // 添加这行 setState(() {});
}
if (_messageController.text.isEmpty) return;
Future<void> onSpeechResult(SpeechRecognitionResult result) async {
String userMessage = _messageController.text; setState(() {
lastWords = result.recognizedWords;
_messageController.clear();
_currentVoiceText = result.recognizedWords;
setState(() { });
messages.add(ChatMessage( }
text: userMessage,
isUserMessage: true, Future<void> systemSpeak(String content) async {
)); try {
if (kIsWeb) {
currentStreamedContent = ''; // 设置语速
_isLoading = true; await flutterTts.setSpeechRate(1);
});
// 音调
try {
String fullResponse = ''; await flutterTts.setPitch(0.8);
bool isFirstChunk = true; await flutterTts.speak(content);
} else {
// 创建一个缓冲区来存储收到的文本 await flutterTts.setSharedInstance(true);
StringBuffer buffer = StringBuffer(); await flutterTts.speak(content);
}
await for (final chunk in openAIService.chatGPTAPI(userMessage)) { } catch (e) {
buffer.write(chunk); print('TTS Error: $e');
}
// 逐字显示文本 }
for (int i = fullResponse.length; i < buffer.length; i++) { Future<void> _sendMessage() async {
setState(() { if (!mounted) return; // 添加这行
fullResponse += buffer.toString()[i];
if (_messageController.text.isEmpty) return;
if (isFirstChunk && i == 0) {
messages.add(ChatMessage( String userMessage = _messageController.text;
text: fullResponse,
isUserMessage: false, _messageController.clear();
));
setState(() {
isFirstChunk = false; messages.add(ChatMessage(
} else { text: userMessage,
messages.last = ChatMessage( isUserMessage: true,
text: fullResponse, ));
isUserMessage: false,
); currentStreamedContent = '';
}
}); _isLoading = true;
});
// 添加短暂延迟以创建打字效果
try {
await Future.delayed(const Duration(milliseconds: 50)); String fullResponse = '';
}
} bool isFirstChunk = true;
//await systemSpeak(fullResponse); // 创建一个缓冲区来存储收到的文本
await _updateCurrentConversation(); StringBuffer buffer = StringBuffer();
} catch (e) {
setState(() { await for (final chunk in openAIService.chatGPTAPI(userMessage)) {
messages.add(ChatMessage( buffer.write(chunk);
text: '抱歉,出现了一些错误:$e',
isUserMessage: false, // 逐字显示文本
));
}); for (int i = fullResponse.length; i < buffer.length; i++) {
} finally { setState(() {
setState(() { fullResponse += buffer.toString()[i];
_isLoading = false;
}); if (isFirstChunk && i == 0) {
} messages.add(ChatMessage(
} text: fullResponse,
isUserMessage: false,
Future<void> _processAIResponse(String userInput) async { ));
try {
String fullResponse = ''; isFirstChunk = false;
} else {
bool isFirstChunk = true; messages.last = ChatMessage(
text: fullResponse,
// 创建一个缓冲区来储收到的文本 isUserMessage: false,
);
StringBuffer buffer = StringBuffer(); }
});
await for (final chunk in openAIService.chatGPTAPI(userInput)) {
buffer.write(chunk); // 添加短暂延迟以创建打字效果
// 逐字显示文本 await Future.delayed(const Duration(milliseconds: 50));
}
for (int i = fullResponse.length; i < buffer.length; i++) { }
setState(() {
fullResponse += buffer.toString()[i]; //await systemSpeak(fullResponse);
if (isFirstChunk && i == 0) { await _updateCurrentConversation();
messages.add(ChatMessage( } catch (e) {
text: fullResponse, setState(() {
isUserMessage: false, messages.add(ChatMessage(
)); text: '抱歉,出现了一些错误:$e',
isUserMessage: false,
isFirstChunk = false; ));
} else { });
messages.last = ChatMessage( } finally {
text: fullResponse, setState(() {
isUserMessage: false, _isLoading = false;
); });
} }
}); }
// 添加短暂延迟以创建打字效果 Future<void> _processAIResponse(String userInput) async {
try {
await Future.delayed(const Duration(milliseconds: 50)); String fullResponse = '';
}
} bool isFirstChunk = true;
// await systemSpeak(fullResponse); // 创建一个缓冲区来储收到的文本
await _updateCurrentConversation(); StringBuffer buffer = StringBuffer();
} catch (e) {
setState(() { await for (final chunk in openAIService.chatGPTAPI(userInput)) {
messages.add(ChatMessage( buffer.write(chunk);
text: '抱歉,出现了一些错误:$e',
isUserMessage: false, // 逐字显示文本
));
}); for (int i = fullResponse.length; i < buffer.length; i++) {
} finally { setState(() {
setState(() { fullResponse += buffer.toString()[i];
_isLoading = false;
}); if (isFirstChunk && i == 0) {
} messages.add(ChatMessage(
} text: fullResponse,
isUserMessage: false,
@override ));
void dispose() {
super.dispose(); isFirstChunk = false;
} else {
speechToText.stop(); messages.last = ChatMessage(
text: fullResponse,
flutterTts.stop(); isUserMessage: false,
);
_messageController.dispose(); }
} });
Widget _buildBottomInput() { // 添加短暂延迟以创建打字效果
return Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), await Future.delayed(const Duration(milliseconds: 50));
decoration: BoxDecoration( }
color: Colors.white, }
boxShadow: [
BoxShadow( // await systemSpeak(fullResponse);
color: Colors.grey.withOpacity(0.1),
spreadRadius: 1, await _updateCurrentConversation();
blurRadius: 3, } catch (e) {
offset: const Offset(0, -1), setState(() {
), messages.add(ChatMessage(
], text: '抱歉,出现了一些错误:$e',
), isUserMessage: false,
child: Row( ));
children: [ });
Expanded( } finally {
child: _isVoiceMode setState(() {
? GestureDetector( _isLoading = false;
onLongPressStart: (_) async { });
setState(() { }
_isListeningPressed = true; }
_currentVoiceText = ''; @override
}); void dispose() {
super.dispose();
await startListening();
}, speechToText.stop();
onLongPressEnd: (_) async {
setState(() => _isListeningPressed = false); flutterTts.stop();
await stopListening(); _messageController.dispose();
}
final finalVoiceText = _currentVoiceText;
Widget _buildBottomInput() {
if (finalVoiceText.isNotEmpty) { return Container(
setState(() { padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
messages.add(ChatMessage( decoration: BoxDecoration(
text: finalVoiceText, color: Colors.white,
isUserMessage: true, boxShadow: [
)); BoxShadow(
color: Colors.grey.withOpacity(0.1),
_isLoading = true; spreadRadius: 1,
}); blurRadius: 3,
offset: const Offset(0, -1),
await _processAIResponse(finalVoiceText); ),
} ],
),
setState(() { child: Row(
_currentVoiceText = ''; children: [
}); Expanded(
}, child: _isVoiceMode
child: Container( ? GestureDetector(
padding: const EdgeInsets.symmetric( onTap: () async {
horizontal: 20, if (!_isListeningPressed) {
vertical: 10, // 开始录音
), setState(() {
decoration: BoxDecoration( _isListeningPressed = true;
color: Colors.grey[100], _currentVoiceText = '';
borderRadius: BorderRadius.circular(25), });
), await startListening();
child: Text( } else {
_isListeningPressed // 停止录音
? (_currentVoiceText.isEmpty setState(() => _isListeningPressed = false);
? '正在聆听...' await stopListening();
: _currentVoiceText)
: '按住说话', // 确保有语音文本才处理
textAlign: TextAlign.center, if (_currentVoiceText.isNotEmpty) {
style: TextStyle( setState(() {
color: _isListeningPressed messages.add(ChatMessage(
? Pallete.firstSuggestionBoxColor text: _currentVoiceText,
: Colors.grey[600], isUserMessage: true,
), ));
), _isLoading = true;
), });
) await _processAIResponse(_currentVoiceText);
: TextField( }
controller: _messageController, setState(() => _currentVoiceText = '');
decoration: InputDecoration( }
hintText: '输入消息...', },
border: OutlineInputBorder( child: Container(
borderRadius: BorderRadius.circular(25), padding: const EdgeInsets.symmetric(
borderSide: BorderSide.none, horizontal: 20,
), vertical: 10,
filled: true, ),
fillColor: Colors.grey[100], decoration: BoxDecoration(
contentPadding: const EdgeInsets.symmetric( color: Colors.grey[100],
horizontal: 20, borderRadius: BorderRadius.circular(25),
vertical: 10, ),
), child: Row(
), mainAxisSize: MainAxisSize.min,
), children: [
), Icon(
const SizedBox(width: 8), _isListeningPressed ? Icons.stop : Icons.mic,
IconButton( color: _isListeningPressed
icon: Icon( ? Pallete.firstSuggestionBoxColor
_isVoiceMode ? Icons.keyboard : Icons.mic, : Colors.grey[600],
color: Pallete.firstSuggestionBoxColor, ),
), const SizedBox(width: 8),
onPressed: () { Text(
setState(() => _isVoiceMode = !_isVoiceMode); _isListeningPressed
}, ? (_currentVoiceText.isEmpty ? '点击停止' : _currentVoiceText)
), : '点击说话',
if (!_isVoiceMode) textAlign: TextAlign.center,
IconButton( style: TextStyle(
icon: const Icon( color: _isListeningPressed
Icons.send, ? Pallete.firstSuggestionBoxColor
color: Pallete.firstSuggestionBoxColor, : Colors.grey[600],
), ),
onPressed: _sendMessage, ),
), ],
], ),
), ),
); )
} : TextField(
controller: _messageController,
AppBar _buildAppBar() { decoration: InputDecoration(
return AppBar( hintText: '输入消息...',
title: Row( border: OutlineInputBorder(
mainAxisAlignment: MainAxisAlignment.center, borderRadius: BorderRadius.circular(25),
children: [ borderSide: BorderSide.none,
TextButton( ),
onPressed: () { filled: true,
setState(() => _currentIndex = 0); fillColor: Colors.grey[100],
}, contentPadding: const EdgeInsets.symmetric(
child: Text( horizontal: 20,
'对话', vertical: 10,
style: TextStyle( ),
fontSize: _currentIndex == 0 ? 20 : 16, ),
fontWeight: ),
_currentIndex == 0 ? FontWeight.bold : FontWeight.normal, ),
color: _currentIndex == 0 const SizedBox(width: 8),
? Pallete.firstSuggestionBoxColor IconButton(
: Colors.grey, icon: Icon(
), _isVoiceMode ? Icons.keyboard : Icons.mic,
), color: Pallete.firstSuggestionBoxColor,
), ),
const SizedBox(width: 20), onPressed: () {
TextButton( setState(() => _isVoiceMode = !_isVoiceMode);
onPressed: () { },
setState(() => _currentIndex = 1); ),
}, if (!_isVoiceMode)
child: Text( IconButton(
'应用', icon: const Icon(
style: TextStyle( Icons.send,
fontSize: _currentIndex == 1 ? 20 : 16, color: Pallete.firstSuggestionBoxColor,
fontWeight: ),
_currentIndex == 1 ? FontWeight.bold : FontWeight.normal, onPressed: _sendMessage,
color: _currentIndex == 1 ),
? Pallete.firstSuggestionBoxColor ],
: Colors.grey, ),
), );
), }
),
], AppBar _buildAppBar() {
), return AppBar(
leading: Builder( title: Row(
builder: (context) => IconButton( mainAxisAlignment: MainAxisAlignment.center,
icon: const Icon(Icons.menu), children: [
onPressed: () => Scaffold.of(context).openDrawer(), TextButton(
), onPressed: () {
), setState(() => _currentIndex = 0);
); },
} child: Text(
'对话',
Widget _buildDrawer(BuildContext context) { style: TextStyle(
return Drawer( fontSize: _currentIndex == 0 ? 20 : 16,
child: Column( fontWeight:
children: [ _currentIndex == 0 ? FontWeight.bold : FontWeight.normal,
DrawerHeader( color: _currentIndex == 0
decoration: BoxDecoration( ? Pallete.firstSuggestionBoxColor
color: Pallete.firstSuggestionBoxColor, : Colors.grey,
), ),
child: const Center( ),
child: Text( ),
'会话列表', const SizedBox(width: 20),
style: TextStyle( TextButton(
color: Colors.white, onPressed: () {
fontSize: 24, setState(() => _currentIndex = 1);
), },
), child: Text(
), '应用',
), style: TextStyle(
ListTile( fontSize: _currentIndex == 1 ? 20 : 16,
leading: const Icon(Icons.add), fontWeight:
title: const Text('新建会话'), _currentIndex == 1 ? FontWeight.bold : FontWeight.normal,
onTap: () { color: _currentIndex == 1
_createNewConversation(); ? Pallete.firstSuggestionBoxColor
: Colors.grey,
Navigator.pop(context); ),
}, ),
), ),
Expanded( ],
child: ListView.builder( ),
itemCount: _conversations.length, leading: Builder(
itemBuilder: (context, index) { builder: (context) => IconButton(
final conversation = _conversations[index]; icon: const Icon(Icons.menu),
onPressed: () => Scaffold.of(context).openDrawer(),
return ListTile( ),
leading: const Icon(Icons.chat), ),
title: Text(conversation.title), );
subtitle: Text( }
conversation.messages.isEmpty
? '暂无消息' Widget _buildDrawer(BuildContext context) {
: conversation.messages.last.text, return Drawer(
maxLines: 1, child: Column(
overflow: TextOverflow.ellipsis, children: [
), DrawerHeader(
selected: _currentConversation.id == conversation.id, decoration: BoxDecoration(
onTap: () { color: Pallete.firstSuggestionBoxColor,
setState(() { ),
_currentConversation = conversation; child: const Center(
child: Text(
messages = conversation.messages; '会话列表',
}); style: TextStyle(
color: Colors.white,
Navigator.pop(context); fontSize: 24,
}, ),
trailing: IconButton( ),
icon: const Icon(Icons.delete), ),
onPressed: () async { ),
await _storageService.deleteConversation(conversation.id); ListTile(
leading: const Icon(Icons.add),
setState(() { title: const Text('新建会话'),
_conversations.removeAt(index); onTap: () {
_createNewConversation();
if (_currentConversation.id == conversation.id) {
if (_conversations.isEmpty) { Navigator.pop(context);
_createNewConversation(); },
} else { ),
_currentConversation = _conversations.first; Expanded(
child: ListView.builder(
messages = _currentConversation.messages; itemCount: _conversations.length,
} itemBuilder: (context, index) {
} final conversation = _conversations[index];
});
}, return ListTile(
), leading: const Icon(Icons.chat),
); title: Text(conversation.title),
}, subtitle: Text(
), conversation.messages.isEmpty
), ? '暂无消息'
const Spacer(), : conversation.messages.last.text,
const Divider(height: 1), maxLines: 1,
FutureBuilder<User?>( overflow: TextOverflow.ellipsis,
future: StorageService.getUser(), ),
builder: (context, snapshot) { selected: _currentConversation.id == conversation.id,
if (!snapshot.hasData) return const SizedBox(); onTap: () {
setState(() {
return Container( _currentConversation = conversation;
padding: const EdgeInsets.all(16),
decoration: BoxDecoration( messages = conversation.messages;
color: Colors.grey[50], });
),
child: Column( Navigator.pop(context);
children: [ },
Row( trailing: IconButton(
children: [ icon: const Icon(Icons.delete),
Container( onPressed: () async {
width: 50, await _storageService.deleteConversation(conversation.id);
height: 50,
decoration: BoxDecoration( setState(() {
color: Pallete.firstSuggestionBoxColor, _conversations.removeAt(index);
shape: BoxShape.circle,
), if (_currentConversation.id == conversation.id) {
child: Center( if (_conversations.isEmpty) {
child: Text( _createNewConversation();
snapshot.data!.username[0].toUpperCase(), } else {
style: const TextStyle( _currentConversation = _conversations.first;
color: Colors.white,
fontSize: 24, messages = _currentConversation.messages;
fontWeight: FontWeight.bold, }
), }
), });
), },
), ),
const SizedBox(width: 16), );
Expanded( },
child: Column( ),
crossAxisAlignment: CrossAxisAlignment.start, ),
children: [ const Spacer(),
Text( const Divider(height: 1),
snapshot.data!.username, FutureBuilder<User?>(
style: const TextStyle( future: StorageService.getUser(),
fontSize: 18, builder: (context, snapshot) {
fontWeight: FontWeight.bold, if (!snapshot.hasData) return const SizedBox();
),
), return Container(
const SizedBox(height: 4), padding: const EdgeInsets.all(16),
Text( decoration: BoxDecoration(
'在线', color: Colors.grey[50],
style: TextStyle( ),
fontSize: 14, child: Column(
color: Colors.green[600], children: [
), Row(
), children: [
], Container(
), width: 50,
), height: 50,
], decoration: BoxDecoration(
), color: Pallete.firstSuggestionBoxColor,
const SizedBox(height: 16), shape: BoxShape.circle,
InkWell( ),
onTap: () async { child: Center(
final confirmed = await showDialog<bool>( child: Text(
context: context, snapshot.data!.username[0].toUpperCase(),
builder: (context) => AlertDialog( style: const TextStyle(
title: const Text('确认退出'), color: Colors.white,
content: const Text('您确定要退出登录吗?'), fontSize: 24,
actions: [ fontWeight: FontWeight.bold,
TextButton( ),
onPressed: () => Navigator.pop(context, false), ),
child: const Text('取消'), ),
), ),
TextButton( const SizedBox(width: 16),
onPressed: () => Navigator.pop(context, true), Expanded(
child: const Text( child: Column(
'退出', crossAxisAlignment: CrossAxisAlignment.start,
style: TextStyle(color: Colors.red), children: [
), Text(
), snapshot.data!.username,
], style: const TextStyle(
), fontSize: 18,
); fontWeight: FontWeight.bold,
),
if (confirmed == true && context.mounted) { ),
await StorageService.clearUser(); const SizedBox(height: 4),
Text(
Navigator.of(context).pushReplacementNamed('/login'); '在线',
} style: TextStyle(
}, fontSize: 14,
child: Container( color: Colors.green[600],
padding: const EdgeInsets.symmetric( ),
vertical: 12, ),
horizontal: 16, ],
), ),
decoration: BoxDecoration( ),
color: Colors.red[50], ],
borderRadius: BorderRadius.circular(8), ),
), const SizedBox(height: 16),
child: Row( InkWell(
mainAxisAlignment: MainAxisAlignment.center, onTap: () async {
children: [ final confirmed = await showDialog<bool>(
Icon( context: context,
Icons.logout, builder: (context) => AlertDialog(
color: Colors.red[700], title: const Text('确认退出'),
size: 20, content: const Text('您确定要退出登录吗?'),
), actions: [
const SizedBox(width: 8), TextButton(
Text( onPressed: () => Navigator.pop(context, false),
'退出登录', child: const Text('取消'),
style: TextStyle( ),
color: Colors.red[700], TextButton(
fontSize: 16, onPressed: () => Navigator.pop(context, true),
fontWeight: FontWeight.w500, child: const Text(
), '退出',
), style: TextStyle(color: Colors.red),
], ),
), ),
), ],
), ),
], );
),
); if (confirmed == true && context.mounted) {
}, await StorageService.clearUser();
),
], Navigator.of(context).pushReplacementNamed('/login');
), }
); },
} child: Container(
padding: const EdgeInsets.symmetric(
@override vertical: 12,
Widget build(BuildContext context) { horizontal: 16,
return Scaffold( ),
appBar: widget.hideNavigation ? null : _buildAppBar(), decoration: BoxDecoration(
drawer: widget.hideNavigation ? null : _buildDrawer(context), color: Colors.red[50],
body: IndexedStack( borderRadius: BorderRadius.circular(8),
index: _currentIndex, ),
children: [ child: Row(
Column( mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Expanded( Icon(
child: ListView.builder( Icons.logout,
padding: const EdgeInsets.all(16), color: Colors.red[700],
itemCount: messages.isEmpty ? 1 : messages.length, size: 20,
itemBuilder: (context, index) { ),
if (messages.isEmpty) { const SizedBox(width: 8),
return Center( Text(
child: Column( '出登录',
mainAxisAlignment: MainAxisAlignment.center, style: TextStyle(
children: [ color: Colors.red[700],
ZoomIn( fontSize: 16,
child: Stack( fontWeight: FontWeight.w500,
children: [ ),
Center( ),
child: Container( ],
height: 120, ),
width: 120, ),
margin: const EdgeInsets.only(top: 4), ),
decoration: const BoxDecoration( ],
color: Pallete.assistantCircleColor, ),
shape: BoxShape.circle, );
), },
), ),
), ],
Container( ),
height: 123, );
decoration: BoxDecoration( }
shape: BoxShape.circle,
image: DecorationImage( void _showErrorDialog(String message) {
image: widget.customImageUrl != null showDialog(
? NetworkImage( context: context,
widget.customImageUrl!) builder: (context) => AlertDialog(
: const AssetImage( title: const Text('提示'),
'assets/images/virtualAssistant.png', content: Text(message),
) as ImageProvider, actions: [
onError: (exception, stackTrace) { TextButton(
print( onPressed: () => Navigator.pop(context),
'Error loading image: $exception'); child: const Text('确定'),
}, ),
), ],
), ),
), );
], }
),
), @override
const SizedBox(height: 20), Widget build(BuildContext context) {
Text( return Scaffold(
widget.customDescription ?? appBar: widget.hideNavigation ? null : _buildAppBar(),
'你好!我是你的快际新云AI助手,请问有什么可以帮你的吗?', drawer: widget.hideNavigation ? null : _buildDrawer(context),
style: const TextStyle( body: IndexedStack(
fontSize: 20, index: _currentIndex,
color: Pallete.mainFontColor, children: [
), Column(
textAlign: TextAlign.center, children: [
), Expanded(
], child: ListView.builder(
), padding: const EdgeInsets.all(16),
); itemCount: messages.isEmpty ? 1 : messages.length,
} itemBuilder: (context, index) {
if (messages.isEmpty) {
final message = messages[index]; return Center(
child: Column(
return Column( mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
ChatBubble( ZoomIn(
message: message, child: Stack(
isTyping: _isLoading && children: [
index == messages.length - 1 && Center(
!message.isUserMessage, child: Container(
), height: 120,
if (_isLoading && width: 120,
index == messages.length - 1 && margin: const EdgeInsets.only(top: 4),
message.isUserMessage) decoration: const BoxDecoration(
Padding( color: Pallete.assistantCircleColor,
padding: const EdgeInsets.all(8.0), shape: BoxShape.circle,
child: Align( ),
alignment: Alignment.centerLeft, ),
child: Row( ),
mainAxisSize: MainAxisSize.min, Container(
children: [ height: 123,
SizedBox( decoration: BoxDecoration(
width: 20, shape: BoxShape.circle,
height: 20, image: DecorationImage(
child: CircularProgressIndicator( image: widget.customImageUrl != null
strokeWidth: 2, ? NetworkImage(
valueColor: AlwaysStoppedAnimation<Color>( widget.customImageUrl!)
Pallete.firstSuggestionBoxColor, : const AssetImage(
), 'assets/images/virtualAssistant.png',
), ) as ImageProvider,
), onError: (exception, stackTrace) {
const SizedBox(width: 8), print(
const Text( 'Error loading image: $exception');
'正在思考中...', },
style: TextStyle( ),
color: Pallete.mainFontColor, ),
fontSize: 14, ),
), ],
), ),
], ),
), const SizedBox(height: 20),
), Text(
), widget.customDescription ??
], '你好!我是你的快际新云AI助手,请问有什么可以帮你的吗?',
); style: const TextStyle(
}, fontSize: 20,
), color: Pallete.mainFontColor,
), ),
_buildBottomInput(), textAlign: TextAlign.center,
], ),
), ],
if (!widget.hideNavigation) AppsPage(), ),
], );
), }
);
} final message = messages[index];
}
return Column(
children: [
ChatBubble(
message: message,
isTyping: _isLoading &&
index == messages.length - 1 &&
!message.isUserMessage,
),
if (_isLoading &&
index == messages.length - 1 &&
message.isUserMessage)
Padding(
padding: const EdgeInsets.all(8.0),
child: Align(
alignment: Alignment.centerLeft,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(
Pallete.firstSuggestionBoxColor,
),
),
),
const SizedBox(width: 8),
const Text(
'正在思考中...',
style: TextStyle(
color: Pallete.mainFontColor,
fontSize: 14,
),
),
],
),
),
),
],
);
},
),
),
_buildBottomInput(),
],
),
if (!widget.hideNavigation) AppsPage(),
],
),
);
}
}
\ No newline at end of file
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论