提交 13b88ee1 authored 作者: songchuancai's avatar songchuancai

增加历史会话功能(目前历史会话保存到本地)

上级 25369ae8
import 'package:allen/feature_box.dart';
import 'package:allen/openai_service.dart'; import 'package:allen/openai_service.dart';
import 'package:allen/pallete.dart'; import 'package:allen/pallete.dart';
import 'package:animate_do/animate_do.dart'; import 'package:animate_do/animate_do.dart';
...@@ -6,18 +5,12 @@ import 'package:flutter/material.dart'; ...@@ -6,18 +5,12 @@ 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 'dart:io' show Platform;
import 'package:flutter/foundation.dart' show kIsWeb; import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:uuid/uuid.dart';
class ChatMessage { import 'models/conversation.dart';
final String text; import 'services/storage_service.dart';
final bool isUserMessage; import 'package:shared_preferences/shared_preferences.dart';
import 'models/chat_message.dart';
ChatMessage({
required this.text,
required this.isUserMessage,
});
}
class HomePage extends StatefulWidget { class HomePage extends StatefulWidget {
const HomePage({super.key}); const HomePage({super.key});
...@@ -37,19 +30,63 @@ class _HomePageState extends State<HomePage> { ...@@ -37,19 +30,63 @@ class _HomePageState extends State<HomePage> {
int delay = 200; int delay = 200;
final TextEditingController _messageController = TextEditingController(); final TextEditingController _messageController = TextEditingController();
String currentStreamedContent = ''; String currentStreamedContent = '';
final 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 Conversation _currentConversation;
List<Conversation> _conversations = [];
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_initializeStorage();
initSpeechToText(); initSpeechToText();
_initTts(); _initTts();
} }
Future<void> _initializeStorage() async {
final prefs = await SharedPreferences.getInstance();
_storageService = StorageService(prefs);
_conversations = await _storageService.getConversations();
if (_conversations.isEmpty) {
_createNewConversation();
} else {
_currentConversation = _conversations.first;
setState(() {
messages = _currentConversation.messages;
});
}
}
Future<void> _createNewConversation() async {
final newConversation = Conversation(
id: const Uuid().v4(),
title: '新会话 ${_conversations.length + 1}',
createdAt: DateTime.now(),
messages: [],
);
await _storageService.addConversation(newConversation);
setState(() {
_conversations.insert(0, newConversation);
_currentConversation = newConversation;
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 { Future<void> _initTts() async {
if (!kIsWeb) { if (!kIsWeb) {
await flutterTts.setSharedInstance(true); await flutterTts.setSharedInstance(true);
...@@ -128,6 +165,7 @@ class _HomePageState extends State<HomePage> { ...@@ -128,6 +165,7 @@ class _HomePageState extends State<HomePage> {
}); });
} }
await systemSpeak(fullResponse); await systemSpeak(fullResponse);
await _updateCurrentConversation();
} catch (e) { } catch (e) {
setState(() { setState(() {
messages.add(ChatMessage( messages.add(ChatMessage(
...@@ -164,6 +202,7 @@ class _HomePageState extends State<HomePage> { ...@@ -164,6 +202,7 @@ class _HomePageState extends State<HomePage> {
}); });
} }
await systemSpeak(fullResponse); await systemSpeak(fullResponse);
await _updateCurrentConversation();
} catch (e) { } catch (e) {
setState(() { setState(() {
messages.add(ChatMessage( messages.add(ChatMessage(
...@@ -296,16 +335,101 @@ class _HomePageState extends State<HomePage> { ...@@ -296,16 +335,101 @@ class _HomePageState extends State<HomePage> {
); );
} }
@override AppBar _buildAppBar() {
Widget build(BuildContext context) { return AppBar(
return Scaffold(
appBar: AppBar(
title: BounceInDown( title: BounceInDown(
child: const Text('快际新云'), child: const Text('快际新云'),
), ),
leading: const Icon(Icons.menu), leading: Builder(
builder: (context) => IconButton(
icon: const Icon(Icons.menu),
onPressed: () => Scaffold.of(context).openDrawer(),
),
),
centerTitle: true, centerTitle: true,
);
}
Widget _buildDrawer() {
return Drawer(
child: Column(
children: [
DrawerHeader(
decoration: BoxDecoration(
color: Pallete.firstSuggestionBoxColor,
), ),
child: const Center(
child: Text(
'会话列表',
style: TextStyle(
color: Colors.white,
fontSize: 24,
),
),
),
),
ListTile(
leading: const Icon(Icons.add),
title: const Text('新建会话'),
onTap: () {
_createNewConversation();
Navigator.pop(context);
},
),
Expanded(
child: ListView.builder(
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
? '暂无消息'
: conversation.messages.last.text,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
selected: _currentConversation.id == conversation.id,
onTap: () {
setState(() {
_currentConversation = conversation;
messages = conversation.messages;
});
Navigator.pop(context);
},
trailing: IconButton(
icon: const Icon(Icons.delete),
onPressed: () async {
await _storageService.deleteConversation(conversation.id);
setState(() {
_conversations.removeAt(index);
if (_currentConversation.id == conversation.id) {
if (_conversations.isEmpty) {
_createNewConversation();
} else {
_currentConversation = _conversations.first;
messages = _currentConversation.messages;
}
}
});
},
),
);
},
),
),
],
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: _buildAppBar(),
drawer: _buildDrawer(),
body: Column( body: Column(
children: [ children: [
Expanded( Expanded(
......
class ChatMessage {
final String text;
final bool isUserMessage;
final DateTime timestamp;
ChatMessage({
required this.text,
required this.isUserMessage,
DateTime? timestamp,
}) : timestamp = timestamp ?? DateTime.now();
Map<String, dynamic> toJson() => {
'text': text,
'isUserMessage': isUserMessage,
'timestamp': timestamp.toIso8601String(),
};
factory ChatMessage.fromJson(Map<String, dynamic> json) => ChatMessage(
text: json['text'],
isUserMessage: json['isUserMessage'],
timestamp: DateTime.parse(json['timestamp']),
);
}
\ No newline at end of file
import 'chat_message.dart';
class Conversation {
final String id;
final String title;
final DateTime createdAt;
final List<ChatMessage> messages;
Conversation({
required this.id,
required this.title,
required this.createdAt,
required this.messages,
});
Map<String, dynamic> toJson() => {
'id': id,
'title': title,
'createdAt': createdAt.toIso8601String(),
'messages': messages.map((msg) => msg.toJson()).toList(),
};
factory Conversation.fromJson(Map<String, dynamic> json) => Conversation(
id: json['id'],
title: json['title'],
createdAt: DateTime.parse(json['createdAt']),
messages: (json['messages'] as List)
.map((msg) => ChatMessage.fromJson(msg))
.toList(),
);
}
\ No newline at end of file
import 'dart:convert';
import 'package:shared_preferences/shared_preferences.dart';
import '../models/conversation.dart';
class StorageService {
static const String _conversationsKey = 'conversations';
final SharedPreferences _prefs;
StorageService(this._prefs);
Future<List<Conversation>> getConversations() async {
final String? data = _prefs.getString(_conversationsKey);
if (data == null) return [];
List<dynamic> jsonList = json.decode(data);
return jsonList.map((json) => Conversation.fromJson(json)).toList();
}
Future<void> saveConversations(List<Conversation> conversations) async {
final String data = json.encode(
conversations.map((conv) => conv.toJson()).toList(),
);
await _prefs.setString(_conversationsKey, data);
}
Future<void> addConversation(Conversation conversation) async {
final conversations = await getConversations();
conversations.insert(0, conversation);
await saveConversations(conversations);
}
Future<void> updateConversation(Conversation conversation) async {
final conversations = await getConversations();
final index =
conversations.indexWhere((conv) => conv.id == conversation.id);
if (index != -1) {
conversations[index] = conversation;
await saveConversations(conversations);
}
}
Future<void> deleteConversation(String id) async {
final conversations = await getConversations();
conversations.removeWhere((conv) => conv.id == id);
await saveConversations(conversations);
}
}
...@@ -6,9 +6,11 @@ import FlutterMacOS ...@@ -6,9 +6,11 @@ import FlutterMacOS
import Foundation import Foundation
import flutter_tts import flutter_tts
import shared_preferences_foundation
import speech_to_text_macos import speech_to_text_macos
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FlutterTtsPlugin.register(with: registry.registrar(forPlugin: "FlutterTtsPlugin")) FlutterTtsPlugin.register(with: registry.registrar(forPlugin: "FlutterTtsPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
SpeechToTextMacosPlugin.register(with: registry.registrar(forPlugin: "SpeechToTextMacosPlugin")) SpeechToTextMacosPlugin.register(with: registry.registrar(forPlugin: "SpeechToTextMacosPlugin"))
} }
...@@ -49,6 +49,14 @@ packages: ...@@ -49,6 +49,14 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "1.18.0" version: "1.18.0"
crypto:
dependency: transitive
description:
name: crypto
sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855"
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.0.6"
cupertino_icons: cupertino_icons:
dependency: "direct main" dependency: "direct main"
description: description:
...@@ -65,6 +73,30 @@ packages: ...@@ -65,6 +73,30 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "1.3.1" version: "1.3.1"
ffi:
dependency: transitive
description:
name: ffi
sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.3"
file:
dependency: transitive
description:
name: file
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
url: "https://pub.flutter-io.cn"
source: hosted
version: "7.0.1"
fixnum:
dependency: transitive
description:
name: fixnum
sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.1.1"
flutter: flutter:
dependency: "direct main" dependency: "direct main"
description: flutter description: flutter
...@@ -192,6 +224,30 @@ packages: ...@@ -192,6 +224,30 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "1.9.0" version: "1.9.0"
path_provider_linux:
dependency: transitive
description:
name: path_provider_linux
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.2.1"
path_provider_platform_interface:
dependency: transitive
description:
name: path_provider_platform_interface
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.2"
path_provider_windows:
dependency: transitive
description:
name: path_provider_windows
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.3.0"
pedantic: pedantic:
dependency: transitive dependency: transitive
description: description:
...@@ -200,6 +256,14 @@ packages: ...@@ -200,6 +256,14 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "1.11.1" version: "1.11.1"
platform:
dependency: transitive
description:
name: platform
sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.1.6"
plugin_platform_interface: plugin_platform_interface:
dependency: transitive dependency: transitive
description: description:
...@@ -208,6 +272,62 @@ packages: ...@@ -208,6 +272,62 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "2.1.8" version: "2.1.8"
shared_preferences:
dependency: "direct main"
description:
name: shared_preferences
sha256: "746e5369a43170c25816cc472ee016d3a66bc13fcf430c0bc41ad7b4b2922051"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.3.2"
shared_preferences_android:
dependency: transitive
description:
name: shared_preferences_android
sha256: "3b9febd815c9ca29c9e3520d50ec32f49157711e143b7a4ca039eb87e8ade5ab"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.3.3"
shared_preferences_foundation:
dependency: transitive
description:
name: shared_preferences_foundation
sha256: "07e050c7cd39bad516f8d64c455f04508d09df104be326d8c02551590a0d513d"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.5.3"
shared_preferences_linux:
dependency: transitive
description:
name: shared_preferences_linux
sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.4.1"
shared_preferences_platform_interface:
dependency: transitive
description:
name: shared_preferences_platform_interface
sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.4.1"
shared_preferences_web:
dependency: transitive
description:
name: shared_preferences_web
sha256: d2ca4132d3946fec2184261726b355836a82c33d7d5b67af32692aff18a4684e
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.4.2"
shared_preferences_windows:
dependency: transitive
description:
name: shared_preferences_windows
sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.4.1"
sky_engine: sky_engine:
dependency: transitive dependency: transitive
description: flutter description: flutter
...@@ -245,6 +365,14 @@ packages: ...@@ -245,6 +365,14 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "2.3.0" version: "2.3.0"
sprintf:
dependency: transitive
description:
name: sprintf
sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23"
url: "https://pub.flutter-io.cn"
source: hosted
version: "7.0.0"
stack_trace: stack_trace:
dependency: transitive dependency: transitive
description: description:
...@@ -293,6 +421,14 @@ packages: ...@@ -293,6 +421,14 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "1.4.0" version: "1.4.0"
uuid:
dependency: "direct main"
description:
name: uuid
sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff
url: "https://pub.flutter-io.cn"
source: hosted
version: "4.5.1"
vector_math: vector_math:
dependency: transitive dependency: transitive
description: description:
...@@ -309,6 +445,22 @@ packages: ...@@ -309,6 +445,22 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "14.2.5" version: "14.2.5"
web:
dependency: transitive
description:
name: web
sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.1.0"
xdg_directories:
dependency: transitive
description:
name: xdg_directories
sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.1.0"
sdks: sdks:
dart: ">=3.5.0 <4.0.0" dart: ">=3.5.0 <4.0.0"
flutter: ">=3.18.0-18.0.pre.54" flutter: ">=3.24.0"
...@@ -39,6 +39,8 @@ dependencies: ...@@ -39,6 +39,8 @@ dependencies:
http: ^0.13.5 http: ^0.13.5
flutter_tts: ^3.6.3 flutter_tts: ^3.6.3
animate_do: ^3.0.2 animate_do: ^3.0.2
shared_preferences: ^2.2.2
uuid: ^4.3.3
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论