提交 28ae4749 authored 作者: songchuancai's avatar songchuancai

增加应用列表

上级 e92fe5f9
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 'package:flutter_markdown/flutter_markdown.dart'; import 'package:flutter_markdown/flutter_markdown.dart';
import 'pages/apps_page.dart'; import 'pages/apps_page.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 @override
void initState() { void initState() {
super.initState(); super.initState();
_initializeStorage(); _initializeStorage();
initSpeechToText(); initSpeechToText();
_initTts(); _initTts();
} }
Future<void> _initializeStorage() async { Future<void> _initializeStorage() async {
_prefs = await SharedPreferences.getInstance(); _prefs = await SharedPreferences.getInstance();
_storageService = StorageService(_prefs); _storageService = StorageService(_prefs);
_conversations = await _storageService.getConversations(); _conversations = await _storageService.getConversations();
if (_conversations.isEmpty) { if (_conversations.isEmpty) {
_createNewConversation(); _createNewConversation();
} else { } else {
_currentConversation = _conversations.first; _currentConversation = _conversations.first;
setState(() { setState(() {
messages = _currentConversation.messages; messages = _currentConversation.messages;
}); });
} }
} }
Future<void> _createNewConversation() async { Future<void> _createNewConversation() async {
final newConversation = Conversation( final newConversation = Conversation(
id: const Uuid().v4(), id: const Uuid().v4(),
title: '新会话 ${_conversations.length + 1}', title: '新会话 ${_conversations.length + 1}',
createdAt: DateTime.now(), createdAt: DateTime.now(),
messages: [], messages: [],
); );
await _storageService.addConversation(newConversation); await _storageService.addConversation(newConversation);
setState(() { setState(() {
_conversations.insert(0, newConversation); _conversations.insert(0, newConversation);
_currentConversation = newConversation; _currentConversation = newConversation;
messages = []; messages = [];
}); });
}
Future<void> _updateCurrentConversation() async {
_currentConversation = Conversation(
id: _currentConversation.id,
title: _currentConversation.title,
createdAt: _currentConversation.createdAt,
messages: messages,
);
await _storageService.updateConversation(_currentConversation);
} }
Future<void> _initTts() async {
if (!kIsWeb) {
await flutterTts.setSharedInstance(true);
}
setState(() {});
}
Future<void> initSpeechToText() async {
await speechToText.initialize();
setState(() {});
}
Future<void> startListening() async {
await speechToText.listen(onResult: onSpeechResult);
setState(() {});
}
Future<void> _updateCurrentConversation() async { Future<void> stopListening() async {
await speechToText.stop();
_currentConversation = Conversation(
id: _currentConversation.id,
title: _currentConversation.title,
createdAt: _currentConversation.createdAt,
messages: messages,
);
await _storageService.updateConversation(_currentConversation);
}
Future<void> _initTts() async {
if (!kIsWeb) {
await flutterTts.setSharedInstance(true);
}
setState(() {});
}
Future<void> initSpeechToText() async {
await speechToText.initialize();
setState(() {}); setState(() {});
} }
Future<void> startListening() async {
await speechToText.listen(onResult: onSpeechResult);
setState(() {});
}
Future<void> stopListening() async {
await speechToText.stop();
setState(() {});
}
Future<void> onSpeechResult(SpeechRecognitionResult result) async { Future<void> onSpeechResult(SpeechRecognitionResult result) async {
setState(() { setState(() {
lastWords = result.recognizedWords; lastWords = result.recognizedWords;
_currentVoiceText = result.recognizedWords; _currentVoiceText = result.recognizedWords;
}); });
} }
Future<void> systemSpeak(String content) async { Future<void> systemSpeak(String content) async {
try { try {
if (kIsWeb) { if (kIsWeb) {
// 设置语速 // 设置语速
await flutterTts.setSpeechRate(3); await flutterTts.setSpeechRate(3);
// 音调 // 音调
await flutterTts.setPitch(0.8); await flutterTts.setPitch(0.8);
await flutterTts.speak(content); await flutterTts.speak(content);
} else { } else {
await flutterTts.setSharedInstance(true); await flutterTts.setSharedInstance(true);
await flutterTts.speak(content); await flutterTts.speak(content);
} }
} catch (e) { } catch (e) {
print('TTS Error: $e'); print('TTS Error: $e');
} }
} }
Future<void> _sendMessage() async { Future<void> _sendMessage() async {
if (!mounted) return; // 添加这行
if (_messageController.text.isEmpty) return; if (_messageController.text.isEmpty) return;
String userMessage = _messageController.text; String userMessage = _messageController.text;
_messageController.clear(); _messageController.clear();
setState(() { setState(() {
messages.add(ChatMessage( messages.add(ChatMessage(
text: userMessage, text: userMessage,
isUserMessage: true, isUserMessage: true,
)); ));
currentStreamedContent = ''; currentStreamedContent = '';
_isLoading = true; _isLoading = true;
}); });
try { try {
String fullResponse = ''; String fullResponse = '';
bool isFirstChunk = true; bool isFirstChunk = true;
// 创建一个缓冲区来存储收到的文本 // 创建一个缓冲区来存储收到的文本
StringBuffer buffer = StringBuffer(); StringBuffer buffer = StringBuffer();
await for (final chunk in openAIService.chatGPTAPI(userMessage)) { await for (final chunk in openAIService.chatGPTAPI(userMessage)) {
buffer.write(chunk); buffer.write(chunk);
// 逐字显示文本 // 逐字显示文本
for (int i = fullResponse.length; i < buffer.length; i++) { for (int i = fullResponse.length; i < buffer.length; i++) {
setState(() { setState(() {
fullResponse += buffer.toString()[i]; fullResponse += buffer.toString()[i];
if (isFirstChunk && i == 0) { if (isFirstChunk && i == 0) {
messages.add(ChatMessage( messages.add(ChatMessage(
text: fullResponse, text: fullResponse,
isUserMessage: false, isUserMessage: false,
)); ));
isFirstChunk = false; isFirstChunk = false;
} else { } else {
messages.last = ChatMessage( messages.last = ChatMessage(
text: fullResponse, text: fullResponse,
isUserMessage: false, isUserMessage: false,
); );
} }
}); });
// 添加短暂延迟以创建打字效果 // 添加短暂延迟以创建打字效果
await Future.delayed(const Duration(milliseconds: 50)); await Future.delayed(const Duration(milliseconds: 50));
} }
} }
await systemSpeak(fullResponse); await systemSpeak(fullResponse);
await _updateCurrentConversation(); await _updateCurrentConversation();
} catch (e) { } catch (e) {
setState(() { setState(() {
messages.add(ChatMessage( messages.add(ChatMessage(
text: '抱歉,出现了一些错误:$e', text: '抱歉,出现了一些错误:$e',
isUserMessage: false, isUserMessage: false,
)); ));
}); });
} finally {
} finally {
setState(() { setState(() {
_isLoading = false; _isLoading = false;
}); });
} }
} }
Future<void> _processAIResponse(String userInput) async { Future<void> _processAIResponse(String userInput) async {
try { try {
String fullResponse = ''; String fullResponse = '';
bool isFirstChunk = true; bool isFirstChunk = true;
// 创建一个缓冲区来储收到的文本 // 创建一个缓冲区来储收到的文本
StringBuffer buffer = StringBuffer(); StringBuffer buffer = StringBuffer();
await for (final chunk in openAIService.chatGPTAPI(userInput)) { await for (final chunk in openAIService.chatGPTAPI(userInput)) {
buffer.write(chunk); buffer.write(chunk);
// 逐字显示文本 // 逐字显示文本
for (int i = fullResponse.length; i < buffer.length; i++) { for (int i = fullResponse.length; i < buffer.length; i++) {
setState(() { setState(() {
fullResponse += buffer.toString()[i]; fullResponse += buffer.toString()[i];
if (isFirstChunk && i == 0) { if (isFirstChunk && i == 0) {
messages.add(ChatMessage( messages.add(ChatMessage(
text: fullResponse, text: fullResponse,
isUserMessage: false, isUserMessage: false,
)); ));
isFirstChunk = false; isFirstChunk = false;
} else { } else {
messages.last = ChatMessage( messages.last = ChatMessage(
text: fullResponse, text: fullResponse,
isUserMessage: false, isUserMessage: false,
); );
} }
}); });
// 添加短暂延迟以创建打字效果 // 添加短暂延迟以创建打字效果
await Future.delayed(const Duration(milliseconds: 50)); await Future.delayed(const Duration(milliseconds: 50));
} }
} }
await systemSpeak(fullResponse); await systemSpeak(fullResponse);
await _updateCurrentConversation(); await _updateCurrentConversation();
} catch (e) { } catch (e) {
setState(() { setState(() {
messages.add(ChatMessage( messages.add(ChatMessage(
text: '抱歉,出现了一些错误:$e', text: '抱歉,出现了一些错误:$e',
isUserMessage: false, isUserMessage: false,
)); ));
}); });
} finally { } finally {
setState(() { setState(() {
_isLoading = false; _isLoading = false;
}); });
} }
} }
@override @override
void dispose() { void dispose() {
super.dispose(); super.dispose();
speechToText.stop(); speechToText.stop();
flutterTts.stop(); flutterTts.stop();
_messageController.dispose(); _messageController.dispose();
} }
Widget _buildBottomInput() { Widget _buildBottomInput() {
return Container( return Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: Colors.white,
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.grey.withOpacity(0.1), color: Colors.grey.withOpacity(0.1),
spreadRadius: 1, spreadRadius: 1,
blurRadius: 3, blurRadius: 3,
offset: const Offset(0, -1), offset: const Offset(0, -1),
), ),
], ],
), ),
child: Row( child: Row(
children: [ children: [
Expanded( Expanded(
child: _isVoiceMode child: _isVoiceMode
? GestureDetector( ? GestureDetector(
onLongPressStart: (_) async { onLongPressStart: (_) async {
setState(() { setState(() {
_isListeningPressed = true;
_currentVoiceText = '';
});
await startListening();
_isListeningPressed = true; },
onLongPressEnd: (_) async {
_currentVoiceText = '';
});
await startListening();
},
onLongPressEnd: (_) async {
setState(() => _isListeningPressed = false); setState(() => _isListeningPressed = false);
await stopListening(); await stopListening();
final finalVoiceText = _currentVoiceText; final finalVoiceText = _currentVoiceText;
if (finalVoiceText.isNotEmpty) { if (finalVoiceText.isNotEmpty) {
setState(() { setState(() {
messages.add(ChatMessage( messages.add(ChatMessage(
text: finalVoiceText, text: finalVoiceText,
isUserMessage: true, isUserMessage: true,
)); ));
_isLoading = true; _isLoading = true;
}); });
await _processAIResponse(finalVoiceText); await _processAIResponse(finalVoiceText);
} }
setState(() { setState(() {
_currentVoiceText = ''; _currentVoiceText = '';
}); });
}, },
child: Container( child: Container(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 20, horizontal: 20,
vertical: 10, vertical: 10,
), ),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.grey[100], color: Colors.grey[100],
borderRadius: BorderRadius.circular(25), borderRadius: BorderRadius.circular(25),
), ),
child: Text( child: Text(
_isListeningPressed _isListeningPressed
? (_currentVoiceText.isEmpty ? (_currentVoiceText.isEmpty
? '正在聆听...' ? '正在聆听...'
: _currentVoiceText) : _currentVoiceText)
: '按住说话', : '按住说话',
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
color: _isListeningPressed color: _isListeningPressed
? Pallete.firstSuggestionBoxColor ? Pallete.firstSuggestionBoxColor
: Colors.grey[600], : Colors.grey[600],
), ),
), ),
), ),
) )
: TextField( : TextField(
controller: _messageController, controller: _messageController,
decoration: InputDecoration( decoration: InputDecoration(
hintText: '输入消息...', hintText: '输入消息...',
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(25), borderRadius: BorderRadius.circular(25),
borderSide: BorderSide.none, borderSide: BorderSide.none,
), ),
filled: true, filled: true,
fillColor: Colors.grey[100], fillColor: Colors.grey[100],
contentPadding: const EdgeInsets.symmetric( contentPadding: const EdgeInsets.symmetric(
horizontal: 20, horizontal: 20,
vertical: 10, vertical: 10,
), ),
), ),
), ),
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
IconButton( IconButton(
icon: Icon( icon: Icon(
_isVoiceMode ? Icons.keyboard : Icons.mic, _isVoiceMode ? Icons.keyboard : Icons.mic,
color: Pallete.firstSuggestionBoxColor, color: Pallete.firstSuggestionBoxColor,
), ),
onPressed: () { onPressed: () {
setState(() => _isVoiceMode = !_isVoiceMode); setState(() => _isVoiceMode = !_isVoiceMode);
}, },
), ),
if (!_isVoiceMode) if (!_isVoiceMode)
IconButton( IconButton(
icon: const Icon( icon: const Icon(
Icons.send, Icons.send,
color: Pallete.firstSuggestionBoxColor, color: Pallete.firstSuggestionBoxColor,
), ),
onPressed: _sendMessage, onPressed: _sendMessage,
), ),
], ],
), ),
); );
}
AppBar _buildAppBar() {
}
AppBar _buildAppBar() {
return AppBar( return AppBar(
title: Row( title: Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
TextButton( TextButton(
onPressed: () { onPressed: () {
setState(() => _currentIndex = 0); setState(() => _currentIndex = 0);
}, },
child: Text( child: Text(
'对话', '对话',
style: TextStyle( style: TextStyle(
fontSize: _currentIndex == 0 ? 20 : 16, fontSize: _currentIndex == 0 ? 20 : 16,
fontWeight:
_currentIndex == 0 ? FontWeight.bold : FontWeight.normal,
fontWeight: _currentIndex == 0 ? FontWeight.bold : FontWeight.normal,
color: _currentIndex == 0 color: _currentIndex == 0
? Pallete.firstSuggestionBoxColor ? Pallete.firstSuggestionBoxColor
: Colors.grey, : Colors.grey,
), ),
), ),
), ),
const SizedBox(width: 20), const SizedBox(width: 20),
TextButton( TextButton(
onPressed: () { onPressed: () {
setState(() => _currentIndex = 1); setState(() => _currentIndex = 1);
}, },
child: Text( child: Text(
'应用', '应用',
style: TextStyle( style: TextStyle(
fontSize: _currentIndex == 1 ? 20 : 16, fontSize: _currentIndex == 1 ? 20 : 16,
fontWeight:
_currentIndex == 1 ? FontWeight.bold : FontWeight.normal,
fontWeight: _currentIndex == 1 ? FontWeight.bold : FontWeight.normal,
color: _currentIndex == 1 color: _currentIndex == 1
? Pallete.firstSuggestionBoxColor ? Pallete.firstSuggestionBoxColor
: Colors.grey, : Colors.grey,
), ),
), ),
), ),
], ],
), ),
leading: Builder( leading: Builder(
builder: (context) => IconButton( builder: (context) => IconButton(
icon: const Icon(Icons.menu), icon: const Icon(Icons.menu),
onPressed: () => Scaffold.of(context).openDrawer(), onPressed: () => Scaffold.of(context).openDrawer(),
), ),
), ),
); );
} }
Widget _buildDrawer(BuildContext context) { Widget _buildDrawer(BuildContext context) {
return Drawer( return Drawer(
child: Column( child: Column(
children: [ children: [
DrawerHeader( DrawerHeader(
decoration: BoxDecoration( decoration: BoxDecoration(
color: Pallete.firstSuggestionBoxColor, color: Pallete.firstSuggestionBoxColor,
), ),
child: const Center( child: const Center(
child: Text( child: Text(
'会话列表', '会话列表',
style: TextStyle( style: TextStyle(
color: Colors.white, color: Colors.white,
fontSize: 24, fontSize: 24,
), ),
), ),
), ),
), ),
ListTile( ListTile(
leading: const Icon(Icons.add), leading: const Icon(Icons.add),
title: const Text('新建会话'), title: const Text('新建会话'),
onTap: () { onTap: () {
_createNewConversation(); _createNewConversation();
Navigator.pop(context); Navigator.pop(context);
}, },
), ),
Expanded( Expanded(
child: ListView.builder( child: ListView.builder(
itemCount: _conversations.length, itemCount: _conversations.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final conversation = _conversations[index]; final conversation = _conversations[index];
return ListTile( return ListTile(
leading: const Icon(Icons.chat), leading: const Icon(Icons.chat),
title: Text(conversation.title), title: Text(conversation.title),
subtitle: Text( subtitle: Text(
conversation.messages.isEmpty conversation.messages.isEmpty
? '暂无消息' ? '暂无消息'
: conversation.messages.last.text, : conversation.messages.last.text,
maxLines: 1,
maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
selected: _currentConversation.id == conversation.id, selected: _currentConversation.id == conversation.id,
onTap: () { onTap: () {
setState(() { setState(() {
_currentConversation = conversation; _currentConversation = conversation;
messages = conversation.messages; messages = conversation.messages;
}); });
Navigator.pop(context); Navigator.pop(context);
}, },
trailing: IconButton( trailing: IconButton(
icon: const Icon(Icons.delete), icon: const Icon(Icons.delete),
onPressed: () async { onPressed: () async {
await _storageService.deleteConversation(conversation.id); await _storageService.deleteConversation(conversation.id);
setState(() { setState(() {
_conversations.removeAt(index); _conversations.removeAt(index);
if (_currentConversation.id == conversation.id) { if (_currentConversation.id == conversation.id) {
if (_conversations.isEmpty) { if (_conversations.isEmpty) {
_createNewConversation(); _createNewConversation();
} else { } else {
_currentConversation = _conversations.first; _currentConversation = _conversations.first;
messages = _currentConversation.messages; messages = _currentConversation.messages;
} }
} }
}); });
}, },
), ),
); );
}, },
), ),
), ),
const Spacer(), const Spacer(),
const Divider(height: 1), const Divider(height: 1),
FutureBuilder<User?>( FutureBuilder<User?>(
future: StorageService.getUser(), future: StorageService.getUser(),
builder: (context, snapshot) { builder: (context, snapshot) {
if (!snapshot.hasData) return const SizedBox(); if (!snapshot.hasData) return const SizedBox();
return Container( return Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.grey[50], color: Colors.grey[50],
), ),
child: Column( child: Column(
children: [ children: [
Row( Row(
children: [ children: [
Container( Container(
width: 50, width: 50,
height: 50, height: 50,
decoration: BoxDecoration( decoration: BoxDecoration(
color: Pallete.firstSuggestionBoxColor, color: Pallete.firstSuggestionBoxColor,
shape: BoxShape.circle, shape: BoxShape.circle,
), ),
child: Center( child: Center(
child: Text( child: Text(
snapshot.data!.username[0].toUpperCase(), snapshot.data!.username[0].toUpperCase(),
style: const TextStyle( style: const TextStyle(
color: Colors.white, color: Colors.white,
fontSize: 24, fontSize: 24,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
), ),
), ),
), ),
const SizedBox(width: 16), const SizedBox(width: 16),
Expanded( Expanded(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
snapshot.data!.username, snapshot.data!.username,
style: const TextStyle( style: const TextStyle(
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
Text( Text(
'在线', '在线',
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
color: Colors.green[600], color: Colors.green[600],
), ),
), ),
], ],
), ),
), ),
], ],
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
InkWell( InkWell(
onTap: () async { onTap: () async {
final confirmed = await showDialog<bool>( final confirmed = await showDialog<bool>(
context: context, context: context,
builder: (context) => AlertDialog( builder: (context) => AlertDialog(
title: const Text('确认退出'), title: const Text('确认退出'),
content: const Text('您确定要退出登录吗?'), content: const Text('您确定要退出登录吗?'),
actions: [ actions: [
TextButton( TextButton(
onPressed: () => Navigator.pop(context, false), onPressed: () => Navigator.pop(context, false),
child: const Text('取消'), child: const Text('取消'),
), ),
TextButton( TextButton(
onPressed: () => Navigator.pop(context, true), onPressed: () => Navigator.pop(context, true),
child: const Text( child: const Text(
'退出', '退出',
style: TextStyle(color: Colors.red), style: TextStyle(color: Colors.red),
), ),
), ),
], ],
), ),
); );
if (confirmed == true && context.mounted) { if (confirmed == true && context.mounted) {
await StorageService.clearUser(); await StorageService.clearUser();
Navigator.of(context).pushReplacementNamed('/login'); Navigator.of(context).pushReplacementNamed('/login');
} }
}, },
child: Container( child: Container(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
vertical: 12, vertical: 12,
horizontal: 16, horizontal: 16,
), ),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.red[50], color: Colors.red[50],
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Icon( Icon(
Icons.logout, Icons.logout,
color: Colors.red[700], color: Colors.red[700],
size: 20, size: 20,
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
Text( Text(
'退出登录', '退出登录',
style: TextStyle( style: TextStyle(
color: Colors.red[700], color: Colors.red[700],
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
), ),
), ),
], ],
), ),
), ),
), ),
], ],
), ),
); );
}, },
), ),
], ],
), ),
); );
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: widget.hideNavigation ? null : _buildAppBar(), appBar: widget.hideNavigation ? null : _buildAppBar(),
drawer: widget.hideNavigation ? null : _buildDrawer(context), drawer: widget.hideNavigation ? null : _buildDrawer(context),
body: IndexedStack( body: IndexedStack(
index: _currentIndex, index: _currentIndex,
children: [ children: [
Column( Column(
children: [ children: [
Expanded( Expanded(
child: ListView.builder( child: ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: messages.isEmpty ? 1 : messages.length,
padding: const EdgeInsets.all(16),
itemCount: messages.isEmpty ? 1 : messages.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
if (messages.isEmpty) { if (messages.isEmpty) {
return Center( return Center(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
ZoomIn( ZoomIn(
child: Stack( child: Stack(
children: [ children: [
Center( Center(
child: Container( child: Container(
height: 120, height: 120,
width: 120, width: 120,
margin: const EdgeInsets.only(top: 4), margin: const EdgeInsets.only(top: 4),
decoration: const BoxDecoration( decoration: const BoxDecoration(
color: Pallete.assistantCircleColor, color: Pallete.assistantCircleColor,
shape: BoxShape.circle, shape: BoxShape.circle,
), ),
), ),
), ),
Container( Container(
height: 123, height: 123,
decoration: BoxDecoration( decoration: BoxDecoration(
shape: BoxShape.circle, shape: BoxShape.circle,
image: DecorationImage( image: DecorationImage(
image: widget.customImageUrl != null image: widget.customImageUrl != null
? AssetImage(widget.customImageUrl!) ? AssetImage(widget.customImageUrl!)
: const AssetImage( : const AssetImage(
'assets/images/virtualAssistant.png', 'assets/images/virtualAssistant.png',
), ),
), ),
), ),
), ),
], ],
), ),
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
Text( Text(
widget.customDescription ?? widget.customDescription ??
'你好!我是你的快际新云AI助手,请问有什么可以帮你的吗?', '你好!我是你的快际新云AI助手,请问有什么可以帮你的吗?',
style: const TextStyle( style: const TextStyle(
fontSize: 20, fontSize: 20,
color: Pallete.mainFontColor, color: Pallete.mainFontColor,
), ),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
], ],
), ),
); );
} }
final message = messages[index]; final message = messages[index];
return Column( return Column(
children: [ children: [
Align( Align(
alignment: message.isUserMessage alignment: message.isUserMessage
? Alignment.centerRight ? Alignment.centerRight
: Alignment.centerLeft, : Alignment.centerLeft,
child: Container( child: Container(
margin: const EdgeInsets.symmetric(vertical: 4), margin: const EdgeInsets.symmetric(vertical: 4),
padding: const EdgeInsets.all(12), padding: const EdgeInsets.all(12),
constraints: BoxConstraints( constraints: BoxConstraints(
maxWidth: MediaQuery.of(context).size.width * 0.7, maxWidth: MediaQuery.of(context).size.width * 0.7,
), ),
decoration: BoxDecoration( decoration: BoxDecoration(
color: message.isUserMessage color: message.isUserMessage
? Pallete.firstSuggestionBoxColor ? Pallete.firstSuggestionBoxColor
: Pallete.assistantCircleColor, : Pallete.assistantCircleColor,
borderRadius: BorderRadius.circular(15).copyWith( borderRadius: BorderRadius.circular(15).copyWith(
bottomRight: bottomRight:
message.isUserMessage ? Radius.zero : null, message.isUserMessage ? Radius.zero : null,
bottomLeft: bottomLeft:
!message.isUserMessage ? Radius.zero : null, !message.isUserMessage ? Radius.zero : null,
), ),
), ),
child: message.isUserMessage child: message.isUserMessage
? Text( ? Text(
message.text, message.text,
style: const TextStyle( style: const TextStyle(
color: Colors.white, color: Colors.white,
fontSize: 16, fontSize: 16,
), ),
) )
: MarkdownBody( : MarkdownBody(
data: message.text, data: message.text,
selectable: true, selectable: true,
styleSheet: MarkdownStyleSheet( styleSheet: MarkdownStyleSheet(
p: const TextStyle( p: const TextStyle(
color: Colors.black, color: Colors.black,
fontSize: 16, fontSize: 16,
), ),
code: const TextStyle( code: const TextStyle(
color: Colors.white, color: Colors.white,
fontFamily: 'monospace', fontFamily: 'monospace',
fontSize: 14, fontSize: 14,
height: 1.5, height: 1.5,
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
), ),
codeblockPadding: codeblockPadding:
const EdgeInsets.all(16), const EdgeInsets.all(16),
codeblockDecoration: BoxDecoration( codeblockDecoration: BoxDecoration(
color: const Color(0xFF1E1E1E), color: const Color(0xFF1E1E1E),
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
blockquote: const TextStyle( blockquote: const TextStyle(
color: Colors.black87, color: Colors.black87,
fontSize: 16, fontSize: 16,
height: 1.5, height: 1.5,
), ),
blockquoteDecoration: BoxDecoration( blockquoteDecoration: BoxDecoration(
border: Border( border: Border(
left: BorderSide( left: BorderSide(
color: Colors.grey[300]!, color: Colors.grey[300]!,
width: 4, width: 4,
), ),
), ),
), ),
listBullet: const TextStyle( listBullet: const TextStyle(
color: Colors.black87), color: Colors.black87),
), ),
), ),
), ),
), ),
if (_isLoading && if (_isLoading &&
index == messages.length - 1 && index == messages.length - 1 &&
message.isUserMessage) message.isUserMessage)
Padding( Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: Align( child: Align(
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
SizedBox( SizedBox(
width: 20, width: 20,
height: 20, height: 20,
child: CircularProgressIndicator( child: CircularProgressIndicator(
strokeWidth: 2, strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>( valueColor: AlwaysStoppedAnimation<Color>(
Pallete.firstSuggestionBoxColor, Pallete.firstSuggestionBoxColor,
), ),
), ),
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
const Text( const Text(
'正在思考中...', '正在思考中...',
style: TextStyle( style: TextStyle(
color: Pallete.mainFontColor, color: Pallete.mainFontColor,
fontSize: 14, fontSize: 14,
), ),
), ),
], ],
), ),
), ),
), ),
], ],
); );
}, },
), ),
), ),
_buildBottomInput(), _buildBottomInput(),
], ],
), ),
if (!widget.hideNavigation) AppsPage(), if (!widget.hideNavigation) AppsPage(),
], ],
), ),
); );
} }
} }
\ No newline at end of file
class AppItem { class AppItem {
final String id;
final String name; final String name;
final String description; final String description;
final String imageUrl; final String iconUrl;
final String? appCode;
final String openingStatement;
final List<String> suggestedQuestions;
final bool isDefault;
AppItem({ AppItem({
required this.id,
required this.name, required this.name,
required this.description, required this.description,
required this.imageUrl, required this.iconUrl,
this.appCode,
required this.openingStatement,
required this.suggestedQuestions,
required this.isDefault,
}); });
factory AppItem.fromJson(Map<String, dynamic> json) {
return AppItem(
name: json['name'] ?? '',
description: json['description'] ?? '',
iconUrl: json['icon_url'] ?? '',
appCode: json['app_code'],
openingStatement: json['opening_statement'] ?? '',
suggestedQuestions: List<String>.from(json['suggested_questions'] ?? []),
isDefault: json['is_default'] ?? false,
);
}
} }
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../services/apps_service.dart';
import '../models/app_item.dart'; import '../models/app_item.dart';
import '../pallete.dart'; import '../pallete.dart';
import '../services/storage_service.dart';
import '../models/user.dart';
class AppsPage extends StatelessWidget { class AppsPage extends StatefulWidget {
AppsPage({super.key}); const AppsPage({super.key});
final List<AppItem> apps = [ @override
AppItem( State<AppsPage> createState() => _AppsPageState();
id: '1', }
name: '文章助手',
description: '帮助您撰写高质量的文章,提供创意和灵感', class _AppsPageState extends State<AppsPage> {
imageUrl: 'assets/images/article.png', late final AppsService _appsService;
), List<AppItem> apps = [];
AppItem( bool isLoading = true;
id: '2', String? error;
name: '代码专家',
description: '解答编程问题,优化代码结构,提供最佳实践', @override
imageUrl: 'assets/images/code.png', void initState() {
), super.initState();
AppItem( _initializeService();
id: '3', }
name: '翻译助手',
description: '精准翻译多国语言,支持专业术语翻译', Future<void> _initializeService() async {
imageUrl: 'assets/images/translate.png', final User? user = await StorageService.getUser();
), _appsService = AppsService(token: user?.token);
AppItem( fetchApps();
id: '4', }
name: '数学导师',
description: '解决数学问题,讲解数学概念和公式', Future<void> fetchApps() async {
imageUrl: 'assets/images/math.png', try {
), final appsList = await _appsService.getApps();
AppItem( setState(() {
id: '5', apps = appsList;
name: '生活顾问', isLoading = false;
description: '提供日常生活建议,解答各类生活问题', });
imageUrl: 'assets/images/life.png', } catch (e) {
), setState(() {
]; error = e.toString();
isLoading = false;
});
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (isLoading) {
return const Center(child: CircularProgressIndicator());
}
if (error != null) {
return Center(child: Text(error!));
}
return ListView.builder( return ListView.builder(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
itemCount: apps.length, itemCount: apps.length,
...@@ -57,10 +72,19 @@ class AppsPage extends StatelessWidget { ...@@ -57,10 +72,19 @@ class AppsPage extends StatelessWidget {
color: Pallete.firstSuggestionBoxColor.withOpacity(0.1), color: Pallete.firstSuggestionBoxColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
), ),
child: Icon( child: ClipRRect(
borderRadius: BorderRadius.circular(12),
child: Image.network(
app.iconUrl,
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) {
return Icon(
Icons.apps, Icons.apps,
size: 30, size: 30,
color: Pallete.firstSuggestionBoxColor, color: Pallete.firstSuggestionBoxColor,
);
},
),
), ),
), ),
title: Text( title: Text(
......
...@@ -40,16 +40,9 @@ class ChatPage extends StatelessWidget { ...@@ -40,16 +40,9 @@ class ChatPage extends StatelessWidget {
body: HomePage( body: HomePage(
customTitle: app.name, customTitle: app.name,
customDescription: app.description, customDescription: app.description,
customImageUrl: app.imageUrl, customImageUrl: app.iconUrl,
hideNavigation: true, hideNavigation: true,
), ),
); );
} }
} }
\ No newline at end of file
const openAIAPIKey = 'sk-zV0MPjPserDD4gdQ8lE8aUGwxayOwuayogGidos4VO8uxdDL'; const openAIAPIKey = 'sk-zV0MPjPserDD4gdQ8lE8aUGwxayOwuayogGidos4VO8uxdDL';
const baseUrl = 'https://knowledge-web.apps.iytcloud.com/console/api';
import 'dart:convert'; import 'dart:convert';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import '../secrets.dart';
class ApiService { class ApiService {
static const String baseUrl =
'https://knowledge-web.apps.iytcloud.com/console/api';
static Future<Map<String, dynamic>> login( static Future<Map<String, dynamic>> login(
String email, String password) async { String email, String password) async {
final response = await http.post( final response = await http.post(
......
import 'dart:convert';
import 'package:http/http.dart' as http;
import '../models/app_item.dart';
import '../secrets.dart';
class AppsService {
final String? token;
AppsService({this.token});
Future<List<AppItem>> getApps({int page = 1, int limit = 12}) async {
final response = await http.get(
Uri.parse('$baseUrl/apps/?page=$page&limit=$limit'),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
if (token != null) 'Authorization': 'Bearer $token',
},
);
if (response.statusCode == 200) {
final Map<String, dynamic> responseData = json.decode(response.body);
final List<dynamic> appsData = responseData['data'];
return appsData.map((json) => AppItem.fromJson(json)).toList();
} else {
throw Exception('获取应用列表失败: ${response.statusCode}');
}
}
}
import 'dart:convert'; import 'dart:convert';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import '../secrets.dart';
class OpenAIService { class OpenAIService {
final String baseUrl =
'https://knowledge-web.apps.iytcloud.com/console/api/openapi/chat';
final String apiKey = 'sk-OVjS7VE9mT68Uvg7kSFoMnbU6EU836FO'; final String apiKey = 'sk-OVjS7VE9mT68Uvg7kSFoMnbU6EU836FO';
final String appKey = 'app-FRP2s2wSx01rsE67'; final String appKey = 'app-FRP2s2wSx01rsE67';
String? conversationId; String? conversationId;
...@@ -13,7 +12,7 @@ class OpenAIService { ...@@ -13,7 +12,7 @@ class OpenAIService {
var buffer = StringBuffer(); var buffer = StringBuffer();
try { try {
final request = http.Request('POST', Uri.parse(baseUrl)); final request = http.Request('POST', Uri.parse('$baseUrl/openapi/chat'));
request.headers.addAll({ request.headers.addAll({
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'Authorization': 'Bearer $apiKey', 'Authorization': 'Bearer $apiKey',
......
...@@ -70,11 +70,6 @@ flutter: ...@@ -70,11 +70,6 @@ flutter:
assets: assets:
- assets/images/ - assets/images/
- assets/sounds/ - assets/sounds/
- assets/images/article.png
- assets/images/code.png
- assets/images/translate.png
- assets/images/math.png
- assets/images/life.png
# An image asset can refer to one or more resolution-specific "variants", see # An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware # https://flutter.dev/assets-and-images/#resolution-aware
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论