提交 c2c63647 authored 作者: songchuancai's avatar songchuancai

增加会话名称自动生成

上级 0f950dc7
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({ final OpenAIService? openAIService;
super.key,
this.customTitle, const HomePage({
this.customDescription, super.key,
this.customImageUrl, this.customTitle,
this.hideNavigation = false, this.customDescription,
}); this.customImageUrl,
this.hideNavigation = false,
@override this.openAIService,
State<HomePage> createState() => _HomePageState(); });
}
@override
class _HomePageState extends State<HomePage> { State<HomePage> createState() => _HomePageState();
final speechToText = SpeechToText(); }
final flutterTts = FlutterTts(); class _HomePageState extends State<HomePage> {
final speechToText = SpeechToText();
String lastWords = '';
final flutterTts = FlutterTts();
final OpenAIService openAIService = OpenAIService();
String lastWords = '';
String? generatedContent;
late OpenAIService openAIService;
String? generatedImageUrl;
String? generatedContent;
int start = 200;
String? generatedImageUrl;
int delay = 200;
int start = 200;
final TextEditingController _messageController = TextEditingController();
int delay = 200;
String currentStreamedContent = '';
final TextEditingController _messageController = TextEditingController();
List<ChatMessage> messages = [];
String currentStreamedContent = '';
bool _isLoading = false;
List<ChatMessage> messages = [];
bool _isVoiceMode = true;
bool _isLoading = false;
bool _isListeningPressed = false;
bool _isVoiceMode = true;
String _currentVoiceText = '';
bool _isListeningPressed = false;
late StorageService _storageService;
String _currentVoiceText = '';
late SharedPreferences _prefs;
late StorageService _storageService;
late Conversation _currentConversation;
late SharedPreferences _prefs;
List<Conversation> _conversations = [];
late Conversation _currentConversation;
int _currentIndex = 0;
List<Conversation> _conversations = [];
bool _hasSpeechPermission = false;
int _currentIndex = 0;
@override
void initState() { bool _hasSpeechPermission = false;
super.initState();
@override
_initializeStorage(); void initState() {
super.initState();
initSpeechToText();
_initializeStorage();
_initTts();
} initSpeechToText();
Future<void> _initializeStorage() async { _initTts();
_prefs = await SharedPreferences.getInstance();
openAIService = widget.openAIService ?? OpenAIService();
_storageService = StorageService(_prefs); }
_conversations = await _storageService.getConversations(); Future<void> _initializeStorage() async {
_prefs = await SharedPreferences.getInstance();
// 每次进入应用创建一个新的会话
_createNewConversation(); _storageService = StorageService(_prefs);
// if (_conversations.isEmpty) {
// _createNewConversation(); _conversations = await _storageService.getConversations();
// } else {
// _currentConversation = _conversations.first; // 每次进入应用创建一个新的会话
// setState(() { _createNewConversation();
// messages = _currentConversation.messages;
// }); // if (_conversations.isEmpty) {
// }
} // _createNewConversation();
Future<void> _createNewConversation() async { // } else {
final newConversation = Conversation(
id: const Uuid().v4(), // _currentConversation = _conversations.first;
title: '新会话 ${_conversations.length + 1}',
createdAt: DateTime.now(), // setState(() {
messages: [],
); // messages = _currentConversation.messages;
await _storageService.addConversation(newConversation); // });
setState(() { // }
_conversations.insert(0, newConversation); }
_currentConversation = newConversation; Future<void> _createNewConversation() async {
final newConversation = Conversation(
messages = []; id: const Uuid().v4(),
}); title: '新会话 ${_conversations.length + 1}',
} createdAt: DateTime.now(),
messages: [],
Future<void> _updateCurrentConversation() async { );
_currentConversation = Conversation(
id: _currentConversation.id, await _storageService.addConversation(newConversation);
title: _currentConversation.title,
createdAt: _currentConversation.createdAt, setState(() {
messages: messages, _conversations.insert(0, newConversation);
);
_currentConversation = newConversation;
await _storageService.updateConversation(_currentConversation);
} messages = [];
});
Future<void> _initTts() async { }
if (!kIsWeb) {
await flutterTts.setSharedInstance(true); Future<void> _updateCurrentConversation() async {
} _currentConversation = Conversation(
id: _currentConversation.id,
setState(() {}); title: _currentConversation.title,
} createdAt: _currentConversation.createdAt,
messages: messages,
Future<void> initSpeechToText() async { );
try {
_hasSpeechPermission = await speechToText.initialize( await _storageService.updateConversation(_currentConversation);
onStatus: (status) { }
print('语音状态: $status');
if (status == 'notListening') { Future<void> _initTts() async {
setState(() => _isListeningPressed = false); if (!kIsWeb) {
} await flutterTts.setSharedInstance(true);
}, }
onError: (error) {
print('语音错误: $error'); setState(() {});
setState(() => _isListeningPressed = false); }
_showErrorDialog('语音初始化失败,请检查麦克风权限');
}, Future<void> initSpeechToText() async {
); try {
} catch (e) { _hasSpeechPermission = await speechToText.initialize(
print('语音初始化错误: $e'); onStatus: (status) {
_hasSpeechPermission = false; print('语音状态: $status');
}
setState(() {}); if (status == 'notListening') {
} setState(() => _isListeningPressed = false);
}
Future<void> startListening() async { },
if (!_hasSpeechPermission) { onError: (error) {
_showErrorDialog('请先授予麦克风权限'); print('语音错误: $error');
setState(() => _isListeningPressed = false);
return; setState(() => _isListeningPressed = false);
}
_showErrorDialog('语音初始化失败,请检查麦克风权限');
try { },
await speechToText.listen( );
onResult: onSpeechResult, } catch (e) {
localeId: 'zh_CN', print('语音初始化错误: $e');
listenMode: ListenMode.confirmation,
cancelOnError: true, _hasSpeechPermission = false;
partialResults: true, }
);
} catch (e) { setState(() {});
print('开始录音失败: $e'); }
setState(() => _isListeningPressed = false);
_showErrorDialog('启动语音识别失败,请重试'); Future<void> startListening() async {
} if (!_hasSpeechPermission) {
} _showErrorDialog('请先授予麦克风权限');
Future<void> stopListening() async { setState(() => _isListeningPressed = false);
await speechToText.stop();
return;
setState(() {}); }
}
try {
Future<void> onSpeechResult(SpeechRecognitionResult result) async { await speechToText.listen(
setState(() { onResult: onSpeechResult,
lastWords = result.recognizedWords; localeId: 'zh_CN',
listenMode: ListenMode.confirmation,
_currentVoiceText = result.recognizedWords; cancelOnError: true,
}); partialResults: true,
} );
} catch (e) {
Future<void> systemSpeak(String content) async { print('开始录音失败: $e');
try {
if (kIsWeb) { setState(() => _isListeningPressed = false);
// 设置语速
_showErrorDialog('启动语音识别失败,请重试');
await flutterTts.setSpeechRate(1); }
}
// 音调
Future<void> stopListening() async {
await flutterTts.setPitch(0.8); await speechToText.stop();
await flutterTts.speak(content); setState(() {});
} else { }
await flutterTts.setSharedInstance(true);
Future<void> onSpeechResult(SpeechRecognitionResult result) async {
await flutterTts.speak(content); setState(() {
} lastWords = result.recognizedWords;
} catch (e) {
print('TTS Error: $e'); _currentVoiceText = result.recognizedWords;
} });
} }
Future<void> _sendMessage() async { Future<void> systemSpeak(String content) async {
if (!mounted) return; // 添加这行 try {
if (kIsWeb) {
if (_messageController.text.isEmpty) return; // 设置语速
String userMessage = _messageController.text; await flutterTts.setSpeechRate(1);
_messageController.clear(); // 音调
setState(() { await flutterTts.setPitch(0.8);
messages.add(ChatMessage(
text: userMessage, await flutterTts.speak(content);
isUserMessage: true, } else {
)); await flutterTts.setSharedInstance(true);
currentStreamedContent = ''; await flutterTts.speak(content);
}
_isLoading = true; } catch (e) {
}); print('TTS Error: $e');
}
try { }
String fullResponse = '';
Future<void> _sendMessage() async {
bool isFirstChunk = true; if (!mounted) return; // 添加这行
// 创建一个缓冲区来存储收到的文本 if (_messageController.text.isEmpty) return;
StringBuffer buffer = StringBuffer(); String userMessage = _messageController.text;
await for (final chunk in openAIService.chatGPTAPI(userMessage)) { _messageController.clear();
buffer.write(chunk);
setState(() {
// 逐字显示文本 messages.add(ChatMessage(
text: userMessage,
for (int i = fullResponse.length; i < buffer.length; i++) { isUserMessage: true,
setState(() { ));
fullResponse += buffer.toString()[i];
currentStreamedContent = '';
if (isFirstChunk && i == 0) {
messages.add(ChatMessage( _isLoading = true;
text: fullResponse, });
isUserMessage: false,
)); 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(userMessage)) {
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, 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; );
}); }
} });
}
// 添加短暂延迟以创建打字效果
@override
void dispose() { await Future.delayed(const Duration(milliseconds: 50));
super.dispose(); }
}
speechToText.stop();
// await systemSpeak(fullResponse);
flutterTts.stop();
await _updateCurrentConversation();
_messageController.dispose(); } catch (e) {
} setState(() {
messages.add(ChatMessage(
Widget _buildBottomInput() { text: '抱歉,出现了一些错误:$e',
return Container( isUserMessage: false,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), ));
decoration: BoxDecoration( });
color: Colors.white, } finally {
boxShadow: [ setState(() {
BoxShadow( _isLoading = false;
color: Colors.grey.withOpacity(0.1), });
spreadRadius: 1, }
blurRadius: 3, }
offset: const Offset(0, -1),
), @override
], void dispose() {
), super.dispose();
child: Row(
children: [ speechToText.stop();
Expanded(
child: _isVoiceMode flutterTts.stop();
? GestureDetector(
onTap: () async { _messageController.dispose();
if (!_isListeningPressed) { }
// 开始录音
setState(() { Widget _buildBottomInput() {
_isListeningPressed = true; return Container(
_currentVoiceText = ''; padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
}); decoration: BoxDecoration(
await startListening(); color: Colors.white,
} else { boxShadow: [
// 停止录音 BoxShadow(
setState(() => _isListeningPressed = false); color: Colors.grey.withOpacity(0.1),
await stopListening(); spreadRadius: 1,
blurRadius: 3,
// 确保有语音文本才处理 offset: const Offset(0, -1),
if (_currentVoiceText.isNotEmpty) { ),
setState(() { ],
messages.add(ChatMessage( ),
text: _currentVoiceText, child: Row(
isUserMessage: true, children: [
)); Expanded(
_isLoading = true; child: _isVoiceMode
}); ? GestureDetector(
await _processAIResponse(_currentVoiceText); onTap: () async {
} if (!_isListeningPressed) {
setState(() => _currentVoiceText = ''); // 开始录音
}
}, setState(() {
child: Container( _isListeningPressed = true;
padding: const EdgeInsets.symmetric(
horizontal: 20, _currentVoiceText = '';
vertical: 10, });
),
decoration: BoxDecoration( await startListening();
color: Colors.grey[100], } else {
borderRadius: BorderRadius.circular(25), // 停止录音
),
child: Row( setState(() => _isListeningPressed = false);
mainAxisSize: MainAxisSize.min,
children: [ await stopListening();
Icon(
_isListeningPressed ? Icons.stop : Icons.mic, // 确保有语音文本才处理
color: _isListeningPressed
? Pallete.firstSuggestionBoxColor if (_currentVoiceText.isNotEmpty) {
: Colors.grey[600], setState(() {
), messages.add(ChatMessage(
const SizedBox(width: 8), text: _currentVoiceText,
Text( isUserMessage: true,
_isListeningPressed ));
? (_currentVoiceText.isEmpty ? '点击停止' : _currentVoiceText)
: '点击说话', _isLoading = true;
textAlign: TextAlign.center, });
style: TextStyle(
color: _isListeningPressed await _processAIResponse(_currentVoiceText);
? Pallete.firstSuggestionBoxColor }
: Colors.grey[600],
), setState(() => _currentVoiceText = '');
), }
], },
), child: Container(
), padding: const EdgeInsets.symmetric(
) horizontal: 20,
: TextField( vertical: 10,
controller: _messageController, ),
decoration: InputDecoration( decoration: BoxDecoration(
hintText: '输入消息...', color: Colors.grey[100],
border: OutlineInputBorder( borderRadius: BorderRadius.circular(25),
borderRadius: BorderRadius.circular(25), ),
borderSide: BorderSide.none, child: Row(
), mainAxisSize: MainAxisSize.min,
filled: true, children: [
fillColor: Colors.grey[100], Icon(
contentPadding: const EdgeInsets.symmetric( _isListeningPressed ? Icons.stop : Icons.mic,
horizontal: 20, color: _isListeningPressed
vertical: 10, ? Pallete.firstSuggestionBoxColor
), : Colors.grey[600],
), ),
), const SizedBox(width: 8),
), Text(
const SizedBox(width: 8), _isListeningPressed
IconButton( ? (_currentVoiceText.isEmpty
icon: Icon( ? '点击停止'
_isVoiceMode ? Icons.keyboard : Icons.mic, : _currentVoiceText)
color: Pallete.firstSuggestionBoxColor, : '点击说话',
), textAlign: TextAlign.center,
onPressed: () { style: TextStyle(
setState(() => _isVoiceMode = !_isVoiceMode); color: _isListeningPressed
}, ? Pallete.firstSuggestionBoxColor
), : Colors.grey[600],
if (!_isVoiceMode) ),
IconButton( ),
icon: const Icon( ],
Icons.send, ),
color: Pallete.firstSuggestionBoxColor, ),
), )
onPressed: _sendMessage, : TextField(
), controller: _messageController,
], decoration: InputDecoration(
), hintText: '输入消息...',
); border: OutlineInputBorder(
} borderRadius: BorderRadius.circular(25),
borderSide: BorderSide.none,
AppBar _buildAppBar() { ),
return AppBar( filled: true,
title: Row( fillColor: Colors.grey[100],
mainAxisAlignment: MainAxisAlignment.center, contentPadding: const EdgeInsets.symmetric(
children: [ horizontal: 20,
TextButton( vertical: 10,
onPressed: () { ),
setState(() => _currentIndex = 0); ),
}, ),
child: Text( ),
'对话', const SizedBox(width: 8),
style: TextStyle( IconButton(
fontSize: _currentIndex == 0 ? 20 : 16, icon: Icon(
fontWeight: _isVoiceMode ? Icons.keyboard : Icons.mic,
_currentIndex == 0 ? FontWeight.bold : FontWeight.normal, color: Pallete.firstSuggestionBoxColor,
color: _currentIndex == 0 ),
? Pallete.firstSuggestionBoxColor onPressed: () {
: Colors.grey, setState(() => _isVoiceMode = !_isVoiceMode);
), },
), ),
), if (!_isVoiceMode)
const SizedBox(width: 20), IconButton(
TextButton( icon: const Icon(
onPressed: () { Icons.send,
setState(() => _currentIndex = 1); color: Pallete.firstSuggestionBoxColor,
}, ),
child: Text( onPressed: _sendMessage,
'应用', ),
style: TextStyle( ],
fontSize: _currentIndex == 1 ? 20 : 16, ),
fontWeight: );
_currentIndex == 1 ? FontWeight.bold : FontWeight.normal, }
color: _currentIndex == 1
? Pallete.firstSuggestionBoxColor AppBar _buildAppBar() {
: Colors.grey, return AppBar(
), title: Row(
), mainAxisAlignment: MainAxisAlignment.center,
), children: [
], TextButton(
), onPressed: () {
leading: Builder( setState(() => _currentIndex = 0);
builder: (context) => IconButton( },
icon: const Icon(Icons.menu), child: Text(
onPressed: () => Scaffold.of(context).openDrawer(), '对话',
), style: TextStyle(
), fontSize: _currentIndex == 0 ? 20 : 16,
); fontWeight:
} _currentIndex == 0 ? FontWeight.bold : FontWeight.normal,
color: _currentIndex == 0
Widget _buildDrawer(BuildContext context) { ? Pallete.firstSuggestionBoxColor
return Drawer( : Colors.grey,
child: Column( ),
children: [ ),
DrawerHeader( ),
decoration: BoxDecoration( const SizedBox(width: 20),
color: Pallete.firstSuggestionBoxColor, TextButton(
), onPressed: () {
child: const Center( setState(() => _currentIndex = 1);
child: Text( },
'会话列表', child: Text(
style: TextStyle( '应用',
color: Colors.white, style: TextStyle(
fontSize: 24, fontSize: _currentIndex == 1 ? 20 : 16,
), fontWeight:
), _currentIndex == 1 ? FontWeight.bold : FontWeight.normal,
), color: _currentIndex == 1
), ? Pallete.firstSuggestionBoxColor
ListTile( : Colors.grey,
leading: const Icon(Icons.add), ),
title: const Text('新建会话'), ),
onTap: () { ),
_createNewConversation(); ],
),
Navigator.pop(context); leading: Builder(
}, builder: (context) => IconButton(
), icon: const Icon(Icons.menu),
Expanded( onPressed: () => Scaffold.of(context).openDrawer(),
child: ListView.builder( ),
itemCount: _conversations.length, ),
itemBuilder: (context, index) { );
final conversation = _conversations[index]; }
return ListTile( Widget _buildDrawer(BuildContext context) {
leading: const Icon(Icons.chat), return Drawer(
title: Text(conversation.title), child: Column(
subtitle: Text( children: [
conversation.messages.isEmpty DrawerHeader(
? '暂无消息' decoration: BoxDecoration(
: conversation.messages.last.text, color: Pallete.firstSuggestionBoxColor,
maxLines: 1, ),
overflow: TextOverflow.ellipsis, child: const Center(
), child: Text(
selected: _currentConversation.id == conversation.id, '会话列表',
onTap: () { style: TextStyle(
setState(() { color: Colors.white,
_currentConversation = conversation; fontSize: 24,
),
messages = conversation.messages; ),
}); ),
),
Navigator.pop(context); ListTile(
}, leading: const Icon(Icons.add),
trailing: IconButton( title: const Text('新建会话'),
icon: const Icon(Icons.delete), onTap: () {
onPressed: () async { _createNewConversation();
await _storageService.deleteConversation(conversation.id);
Navigator.pop(context);
setState(() { },
_conversations.removeAt(index); ),
Expanded(
if (_currentConversation.id == conversation.id) { child: ListView.builder(
if (_conversations.isEmpty) { itemCount: _conversations.length,
_createNewConversation(); itemBuilder: (context, index) {
} else { final conversation = _conversations[index];
_currentConversation = _conversations.first;
return ListTile(
messages = _currentConversation.messages; leading: const Icon(Icons.chat),
} title: Text(conversation.title),
} subtitle: Text(
}); conversation.messages.isEmpty
}, ? '暂无消息'
), : conversation.messages.last.text,
); maxLines: 1,
}, overflow: TextOverflow.ellipsis,
), ),
), selected: _currentConversation.id == conversation.id,
const Spacer(), onTap: () {
const Divider(height: 1), setState(() {
FutureBuilder<User?>( _currentConversation = conversation;
future: StorageService.getUser(),
builder: (context, snapshot) { messages = conversation.messages;
if (!snapshot.hasData) return const SizedBox(); });
return Container( Navigator.pop(context);
padding: const EdgeInsets.all(16), },
decoration: BoxDecoration( trailing: IconButton(
color: Colors.grey[50], icon: const Icon(Icons.delete),
), onPressed: () async {
child: Column( await _storageService.deleteConversation(conversation.id);
children: [
Row( setState(() {
children: [ _conversations.removeAt(index);
Container(
width: 50, if (_currentConversation.id == conversation.id) {
height: 50, if (_conversations.isEmpty) {
decoration: BoxDecoration( _createNewConversation();
color: Pallete.firstSuggestionBoxColor, } else {
shape: BoxShape.circle, _currentConversation = _conversations.first;
),
child: Center( messages = _currentConversation.messages;
child: Text( }
snapshot.data!.username[0].toUpperCase(), }
style: const TextStyle( });
color: Colors.white, },
fontSize: 24, ),
fontWeight: FontWeight.bold, );
), },
), ),
), ),
), const Spacer(),
const SizedBox(width: 16), const Divider(height: 1),
Expanded( FutureBuilder<User?>(
child: Column( future: StorageService.getUser(),
crossAxisAlignment: CrossAxisAlignment.start, builder: (context, snapshot) {
children: [ if (!snapshot.hasData) return const SizedBox();
Text(
snapshot.data!.username, return Container(
style: const TextStyle( padding: const EdgeInsets.all(16),
fontSize: 18, decoration: BoxDecoration(
fontWeight: FontWeight.bold, color: Colors.grey[50],
), ),
), child: Column(
const SizedBox(height: 4), children: [
Text( Row(
'在线', children: [
style: TextStyle( Container(
fontSize: 14, width: 50,
color: Colors.green[600], height: 50,
), decoration: BoxDecoration(
), color: Pallete.firstSuggestionBoxColor,
], shape: BoxShape.circle,
), ),
), child: Center(
], child: Text(
), snapshot.data!.username[0].toUpperCase(),
const SizedBox(height: 16), style: const TextStyle(
InkWell( color: Colors.white,
onTap: () async { fontSize: 24,
final confirmed = await showDialog<bool>( fontWeight: FontWeight.bold,
context: context, ),
builder: (context) => AlertDialog( ),
title: const Text('确认退出'), ),
content: const Text('您确定要退出登录吗?'), ),
actions: [ const SizedBox(width: 16),
TextButton( Expanded(
onPressed: () => Navigator.pop(context, false), child: Column(
child: const Text('取消'), crossAxisAlignment: CrossAxisAlignment.start,
), children: [
TextButton( Text(
onPressed: () => Navigator.pop(context, true), snapshot.data!.username,
child: const Text( style: const TextStyle(
'退出', fontSize: 18,
style: TextStyle(color: Colors.red), fontWeight: FontWeight.bold,
), ),
), ),
], const SizedBox(height: 4),
), Text(
); '在线',
style: TextStyle(
if (confirmed == true && context.mounted) { fontSize: 14,
await StorageService.clearUser(); color: Colors.green[600],
),
Navigator.of(context).pushReplacementNamed('/login'); ),
} ],
}, ),
child: Container( ),
padding: const EdgeInsets.symmetric( ],
vertical: 12, ),
horizontal: 16, const SizedBox(height: 16),
), InkWell(
decoration: BoxDecoration( onTap: () async {
color: Colors.red[50], final confirmed = await showDialog<bool>(
borderRadius: BorderRadius.circular(8), context: context,
), builder: (context) => AlertDialog(
child: Row( title: const Text('确认退出'),
mainAxisAlignment: MainAxisAlignment.center, content: const Text('您确定要退出登录吗?'),
children: [ actions: [
Icon( TextButton(
Icons.logout, onPressed: () => Navigator.pop(context, false),
color: Colors.red[700], child: const Text('取消'),
size: 20, ),
), TextButton(
const SizedBox(width: 8), onPressed: () => Navigator.pop(context, true),
Text( child: const Text(
'出登录', '退出',
style: TextStyle( style: TextStyle(color: Colors.red),
color: Colors.red[700], ),
fontSize: 16, ),
fontWeight: FontWeight.w500, ],
), ),
), );
],
), if (confirmed == true && context.mounted) {
), await StorageService.clearUser();
),
], Navigator.of(context).pushReplacementNamed('/login');
), }
); },
}, child: Container(
), padding: const EdgeInsets.symmetric(
], vertical: 12,
), horizontal: 16,
); ),
} decoration: BoxDecoration(
color: Colors.red[50],
void _showErrorDialog(String message) { borderRadius: BorderRadius.circular(8),
showDialog( ),
context: context, child: Row(
builder: (context) => AlertDialog( mainAxisAlignment: MainAxisAlignment.center,
title: const Text('提示'), children: [
content: Text(message), Icon(
actions: [ Icons.logout,
TextButton( color: Colors.red[700],
onPressed: () => Navigator.pop(context), size: 20,
child: const Text('确定'), ),
), const SizedBox(width: 8),
], Text(
), '出登录',
); style: TextStyle(
} color: Colors.red[700],
fontSize: 16,
@override fontWeight: FontWeight.w500,
Widget build(BuildContext context) { ),
return Scaffold( ),
appBar: widget.hideNavigation ? null : _buildAppBar(), ],
drawer: widget.hideNavigation ? null : _buildDrawer(context), ),
body: IndexedStack( ),
index: _currentIndex, ),
children: [ ],
Column( ),
children: [ );
Expanded( },
child: ListView.builder( ),
padding: const EdgeInsets.all(16), ],
itemCount: messages.isEmpty ? 1 : messages.length, ),
itemBuilder: (context, index) { );
if (messages.isEmpty) { }
return Center(
child: Column( void _showErrorDialog(String message) {
mainAxisAlignment: MainAxisAlignment.center, showDialog(
children: [ context: context,
ZoomIn( builder: (context) => AlertDialog(
child: Stack( title: const Text('提示'),
children: [ content: Text(message),
Center( actions: [
child: Container( TextButton(
height: 120, onPressed: () => Navigator.pop(context),
width: 120, child: const Text('确定'),
margin: const EdgeInsets.only(top: 4), ),
decoration: const BoxDecoration( ],
color: Pallete.assistantCircleColor, ),
shape: BoxShape.circle, );
), }
),
), @override
Container( Widget build(BuildContext context) {
height: 123, return Scaffold(
decoration: BoxDecoration( appBar: widget.hideNavigation ? null : _buildAppBar(),
shape: BoxShape.circle, drawer: widget.hideNavigation ? null : _buildDrawer(context),
image: DecorationImage( body: IndexedStack(
image: widget.customImageUrl != null index: _currentIndex,
? NetworkImage( children: [
widget.customImageUrl!) Column(
: const AssetImage( children: [
'assets/images/virtualAssistant.png', Expanded(
) as ImageProvider, child: ListView.builder(
onError: (exception, stackTrace) { padding: const EdgeInsets.all(16),
print( itemCount: messages.isEmpty ? 1 : messages.length,
'Error loading image: $exception'); itemBuilder: (context, index) {
}, if (messages.isEmpty) {
), return Center(
), child: Column(
), mainAxisAlignment: MainAxisAlignment.center,
], children: [
), ZoomIn(
), child: Stack(
const SizedBox(height: 20), children: [
Text( Center(
widget.customDescription ?? child: Container(
'你好!我是你的快际新云AI助手,请问有什么可以帮你的吗?', height: 120,
style: const TextStyle( width: 120,
fontSize: 20, margin: const EdgeInsets.only(top: 4),
color: Pallete.mainFontColor, decoration: const BoxDecoration(
), color: Pallete.assistantCircleColor,
textAlign: TextAlign.center, shape: BoxShape.circle,
), ),
], ),
), ),
); Container(
} height: 123,
decoration: BoxDecoration(
final message = messages[index]; shape: BoxShape.circle,
image: DecorationImage(
return Column( image: widget.customImageUrl != null
children: [ ? NetworkImage(
ChatBubble( widget.customImageUrl!)
message: message, : const AssetImage(
isTyping: _isLoading && 'assets/images/virtualAssistant.png',
index == messages.length - 1 && ) as ImageProvider,
!message.isUserMessage, onError: (exception, stackTrace) {
), print(
if (_isLoading && 'Error loading image: $exception');
index == messages.length - 1 && },
message.isUserMessage) ),
Padding( ),
padding: const EdgeInsets.all(8.0), ),
child: Align( ],
alignment: Alignment.centerLeft, ),
child: Row( ),
mainAxisSize: MainAxisSize.min, const SizedBox(height: 20),
children: [ Text(
SizedBox( widget.customDescription ??
width: 20, '你好!我是你的快际新云AI助手,请问有什么可以帮你的吗?',
height: 20, style: const TextStyle(
child: CircularProgressIndicator( fontSize: 20,
strokeWidth: 2, color: Pallete.mainFontColor,
valueColor: AlwaysStoppedAnimation<Color>( ),
Pallete.firstSuggestionBoxColor, textAlign: TextAlign.center,
), ),
), ],
), ),
const SizedBox(width: 8), );
const Text( }
'正在思考中...',
style: TextStyle( final message = messages[index];
color: Pallete.mainFontColor,
fontSize: 14, return Column(
), children: [
), ChatBubble(
], message: message,
), isTyping: _isLoading &&
), index == messages.length - 1 &&
), !message.isUserMessage,
], ),
); if (_isLoading &&
}, index == messages.length - 1 &&
), message.isUserMessage)
), Padding(
_buildBottomInput(), padding: const EdgeInsets.all(8.0),
], child: Align(
), alignment: Alignment.centerLeft,
if (!widget.hideNavigation) AppsPage(), child: Row(
], mainAxisSize: MainAxisSize.min,
), children: [
); SizedBox(
} width: 20,
} height: 20,
child: CircularProgressIndicator(
\ No newline at end of file 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(),
],
),
);
}
}
...@@ -6,6 +6,7 @@ import 'package:shared_preferences/shared_preferences.dart'; ...@@ -6,6 +6,7 @@ import 'package:shared_preferences/shared_preferences.dart';
import '../models/app_item.dart'; import '../models/app_item.dart';
import '../services/chat_service.dart';
import '../services/storage_service.dart'; import '../services/storage_service.dart';
import '../home_page.dart'; import '../home_page.dart';
...@@ -22,6 +23,8 @@ class ChatPage extends ConsumerStatefulWidget { ...@@ -22,6 +23,8 @@ class ChatPage extends ConsumerStatefulWidget {
} }
class _ChatPageState extends ConsumerState<ChatPage> { class _ChatPageState extends ConsumerState<ChatPage> {
final OpenAIService openAIService = OpenAIService();
@override @override
void initState() { void initState() {
super.initState(); super.initState();
...@@ -46,6 +49,12 @@ class _ChatPageState extends ConsumerState<ChatPage> { ...@@ -46,6 +49,12 @@ class _ChatPageState extends ConsumerState<ChatPage> {
} }
@override @override
void dispose() {
openAIService.clearConversation();
super.dispose();
}
@override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
...@@ -78,6 +87,7 @@ class _ChatPageState extends ConsumerState<ChatPage> { ...@@ -78,6 +87,7 @@ class _ChatPageState extends ConsumerState<ChatPage> {
customDescription: widget.app.description, customDescription: widget.app.description,
customImageUrl: widget.app.iconUrl, customImageUrl: widget.app.iconUrl,
hideNavigation: true, hideNavigation: true,
openAIService: openAIService,
), ),
); );
} }
......
...@@ -8,6 +8,32 @@ class OpenAIService { ...@@ -8,6 +8,32 @@ class OpenAIService {
// final String apiKey = 'sk-OVjS7VE9mT68Uvg7kSFoMnbU6EU836FO'; // final String apiKey = 'sk-OVjS7VE9mT68Uvg7kSFoMnbU6EU836FO';
// final String appKey = 'app-FRP2s2wSx01rsE67'; // final String appKey = 'app-FRP2s2wSx01rsE67';
String? conversationId; String? conversationId;
bool _isFirstMessage = true;
Future<void> _generateConversationName() async {
if (conversationId == null) return;
var prefs = await SharedPreferences.getInstance();
var storageService = StorageService(prefs);
String appKey = storageService.getWorkbenchToken() ?? '';
try {
final response = await http.post(
Uri.parse('$baseUrl/api/conversations/$conversationId/name'),
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer $appKey',
},
body: jsonEncode({"auto_generate": true}),
);
if (response.statusCode != 200) {
print('生成会话名称失败: ${response.statusCode}');
}
} catch (e) {
print('生成会话名称异常: $e');
}
}
Stream<String> chatGPTAPI(String message) async* { Stream<String> chatGPTAPI(String message) async* {
final client = http.Client(); final client = http.Client();
...@@ -85,11 +111,16 @@ class OpenAIService { ...@@ -85,11 +111,16 @@ class OpenAIService {
} catch (e) { } catch (e) {
throw Exception(e.toString()); throw Exception(e.toString());
} finally { } finally {
if (_isFirstMessage && conversationId != null) {
await _generateConversationName();
_isFirstMessage = false;
}
client.close(); client.close();
} }
} }
void clearConversation() { void clearConversation() {
conversationId = null; conversationId = null;
_isFirstMessage = true;
} }
} }
...@@ -9,12 +9,12 @@ import audioplayers_darwin ...@@ -9,12 +9,12 @@ import audioplayers_darwin
import flutter_tts import flutter_tts
import path_provider_foundation import path_provider_foundation
import shared_preferences_foundation import shared_preferences_foundation
import speech_to_text_macos import speech_to_text
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin")) AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin"))
FlutterTtsPlugin.register(with: registry.registrar(forPlugin: "FlutterTtsPlugin")) FlutterTtsPlugin.register(with: registry.registrar(forPlugin: "FlutterTtsPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
SpeechToTextMacosPlugin.register(with: registry.registrar(forPlugin: "SpeechToTextMacosPlugin")) SpeechToTextPlugin.register(with: registry.registrar(forPlugin: "SpeechToTextPlugin"))
} }
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论