diff --git a/assets/svgs/image_icon.svg b/assets/svgs/image_icon.svg
new file mode 100644
index 00000000..6016457c
--- /dev/null
+++ b/assets/svgs/image_icon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/svgs/send_icon.svg b/assets/svgs/send_icon.svg
new file mode 100644
index 00000000..add1dc20
--- /dev/null
+++ b/assets/svgs/send_icon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/lib/core/storage/impl/user_storage_impl.dart b/lib/core/storage/impl/user_storage_impl.dart
index d1ffcb44..f77b8314 100644
--- a/lib/core/storage/impl/user_storage_impl.dart
+++ b/lib/core/storage/impl/user_storage_impl.dart
@@ -32,6 +32,8 @@ class UserStorageImpl implements UserStorage {
@override
Future deleteUser() async {
await _prefs.remove(_userKey);
+ //返回登陆页面
+
}
@override
diff --git a/lib/features/ai/data/models/chat_message.dart b/lib/features/ai/data/models/chat_message.dart
new file mode 100644
index 00000000..160f89b9
--- /dev/null
+++ b/lib/features/ai/data/models/chat_message.dart
@@ -0,0 +1,17 @@
+import 'dart:io';
+
+class ChatMessage {
+ final String? text;
+ final String? thinkingText;
+ final File? image;
+ final String? imageBase64;
+ final bool isUserMessage;
+
+ ChatMessage({
+ this.text,
+ this.thinkingText,
+ this.image,
+ this.imageBase64,
+ required this.isUserMessage,
+ });
+}
diff --git a/lib/features/ai/data/models/session.dart b/lib/features/ai/data/models/session.dart
new file mode 100644
index 00000000..717f763b
--- /dev/null
+++ b/lib/features/ai/data/models/session.dart
@@ -0,0 +1,13 @@
+class Session {
+ final String sessId;
+ final String title;
+
+ Session({required this.sessId, required this.title});
+
+ factory Session.fromJson(Map json) {
+ return Session(
+ sessId: json['sessId'],
+ title: json['title'],
+ );
+ }
+}
diff --git a/lib/features/ai/presentation/bloc/ai_cubit.dart b/lib/features/ai/presentation/bloc/ai_cubit.dart
new file mode 100644
index 00000000..bff6cd00
--- /dev/null
+++ b/lib/features/ai/presentation/bloc/ai_cubit.dart
@@ -0,0 +1,178 @@
+import 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+import 'package:bloc/bloc.dart';
+import 'package:dio/dio.dart';
+import 'package:image_picker/image_picker.dart';
+
+import 'ai_state.dart';
+import '../../data/models/chat_message.dart';
+import '../../data/models/session.dart';
+
+class AiCubit extends Cubit {
+ AiCubit() : super(const AiState()) {
+ createNewSession();
+ fetchSessions();
+ }
+
+ final Dio _dio = Dio();
+ final ImagePicker _picker = ImagePicker();
+ static const String BASE_URL = 'http://1.95.137.212:30009';
+ final String _userId = '1';
+
+ Future fetchSessions() async {
+ try {
+ final response = await _dio.get('$BASE_URL/sessions', queryParameters: {'userId': _userId});
+ if (response.statusCode == 200 && response.data['code'] == 0) {
+ final sessionsData = response.data['sessions'] as List;
+ emit(state.copyWith(sessions: sessionsData.map((data) => Session.fromJson(data)).toList()));
+ }
+ } catch (e) {
+ emit(state.copyWith(error: "Failed to fetch sessions: $e"));
+ }
+ }
+
+ void createNewSession() {
+ emit(AiState(
+ sessionId: DateTime.now().millisecondsSinceEpoch.toString(),
+ messages: [ChatMessage(text: "Welcome to the AI chat!", isUserMessage: false)],
+ status: AiStatus.initial,
+ selectedImageFile: null, // Explicitly set to null
+ sessions: state.sessions, // Preserve existing sessions
+ ));
+ }
+
+ Future loadSessionHistory(String sessionId) async {
+ emit(state.copyWith(status: AiStatus.loading, messages: [], sessionId: sessionId));
+
+ try {
+ final response = await _dio.get('$BASE_URL/history', queryParameters: {'sessionId': sessionId});
+ if (response.statusCode == 200 && response.data['code'] == 0) {
+ final messagesData = response.data['messages'] as List;
+ final historyMessages = messagesData.map((data) {
+ return ChatMessage(
+ text: data['content'],
+ thinkingText: data['role'] == 'assistant' ? data['thinking'] : null,
+ imageBase64: data['role'] == 'user' ? data['image_base64'] : null,
+ isUserMessage: data['role'] == 'user',
+ );
+ }).toList();
+ emit(state.copyWith(status: AiStatus.success, messages: historyMessages.reversed.toList()));
+ }
+ } catch (e) {
+ emit(state.copyWith(status: AiStatus.failure, error: "Failed to load session history: $e"));
+ }
+ }
+
+ Future analyze({String? prompt, File? image}) async {
+ if (state.status == AiStatus.loading) return;
+
+ String? imageBase64;
+ if (image != null) {
+ final bytes = await image.readAsBytes();
+ imageBase64 = 'data:image/png;base64,${base64Encode(bytes)}';
+ }
+
+ final userMessage = ChatMessage(text: prompt, image: image, isUserMessage: true);
+ final aiPlaceholder = ChatMessage(text: '', thinkingText: '', isUserMessage: false);
+
+ // Create a new state to ensure selectedImageFile is set to null
+ final newState = AiState(
+ status: AiStatus.loading,
+ messages: [aiPlaceholder, userMessage, ...state.messages],
+ selectedImageFile: null, // Clear the selected image
+ sessions: state.sessions,
+ sessionId: state.sessionId,
+ error: state.error,
+ );
+ emit(newState);
+
+ try {
+ final response = await _dio.post(
+ '$BASE_URL/analyze',
+ data: {
+ 'userId': _userId,
+ 'sessionId': state.sessionId,
+ 'userPrompt': prompt,
+ 'imageBase64': imageBase64,
+ },
+ options: Options(responseType: ResponseType.stream),
+ );
+
+ String currentPhase = 'thinking';
+ StringBuffer thinkingBuffer = StringBuffer();
+ StringBuffer answerBuffer = StringBuffer();
+
+ final streamSubscription = response.data.stream.listen(
+ (chunk) {
+ final decodedChunk = utf8.decode(chunk);
+ final events = decodedChunk.split('\n\n').where((s) => s.isNotEmpty);
+
+ for (final event in events) {
+ if (event.startsWith('data: ')) {
+ final dataString = event.substring(6);
+ if (dataString == '[DONE]') continue;
+ try {
+ final data = jsonDecode(dataString);
+ final type = data['type'];
+ final content = data['content'] as String;
+
+ if (type == 'phase') {
+ currentPhase = content;
+ } else if (type == 'thinking' && currentPhase == 'thinking') {
+ thinkingBuffer.write(content);
+ } else if (type == 'answer' && currentPhase == 'answer') {
+ answerBuffer.write(content);
+ }
+
+ final updatedMessages = List.from(state.messages);
+ updatedMessages[0] = ChatMessage(
+ thinkingText: thinkingBuffer.toString(),
+ text: answerBuffer.toString(),
+ isUserMessage: false,
+ );
+ emit(state.copyWith(messages: updatedMessages));
+
+ } catch (e) {
+ print("Error parsing stream data: $e");
+ }
+ }
+ }
+ },
+ onDone: () {
+ emit(state.copyWith(status: AiStatus.success));
+ fetchSessions();
+ },
+ onError: (error) {
+ final updatedMessages = List.from(state.messages);
+ updatedMessages[0] = ChatMessage(text: "Error: $error", isUserMessage: false);
+ emit(state.copyWith(status: AiStatus.failure, messages: updatedMessages));
+ },
+ );
+ } catch (e) {
+ final updatedMessages = List.from(state.messages);
+ updatedMessages[0] = ChatMessage(text: "Failed to connect to the server: $e", isUserMessage: false);
+ emit(state.copyWith(status: AiStatus.failure, messages: updatedMessages));
+ }
+ }
+
+ void selectImage(ImageSource source) async {
+ final pickedFile = await _picker.pickImage(source: source);
+ if (pickedFile != null) {
+ emit(state.copyWith(selectedImageFile: File(pickedFile.path)));
+ }
+ }
+
+ void deselectImage() {
+ // Manually create a new state to ensure selectedImageFile is set to null,
+ // bypassing the limitation of the existing copyWith method.
+ emit(AiState(
+ status: state.status,
+ messages: state.messages,
+ sessions: state.sessions,
+ sessionId: state.sessionId,
+ error: state.error,
+ selectedImageFile: null, // This will now correctly clear the file.
+ ));
+ }
+}
diff --git a/lib/features/ai/presentation/bloc/ai_state.dart b/lib/features/ai/presentation/bloc/ai_state.dart
new file mode 100644
index 00000000..8ae597e7
--- /dev/null
+++ b/lib/features/ai/presentation/bloc/ai_state.dart
@@ -0,0 +1,47 @@
+import 'dart:io';
+
+import 'package:equatable/equatable.dart';
+
+import '../../data/models/chat_message.dart';
+import '../../data/models/session.dart';
+
+enum AiStatus { initial, loading, success, failure }
+
+class AiState extends Equatable {
+ const AiState({
+ this.status = AiStatus.initial,
+ this.messages = const [],
+ this.sessions = const [],
+ this.selectedImageFile,
+ this.sessionId,
+ this.error = '',
+ });
+
+ final AiStatus status;
+ final List messages;
+ final List sessions;
+ final File? selectedImageFile;
+ final String? sessionId;
+ final String error;
+
+ AiState copyWith({
+ AiStatus? status,
+ List? messages,
+ List? sessions,
+ File? selectedImageFile,
+ String? sessionId,
+ String? error,
+ }) {
+ return AiState(
+ status: status ?? this.status,
+ messages: messages ?? this.messages,
+ sessions: sessions ?? this.sessions,
+ selectedImageFile: selectedImageFile ?? this.selectedImageFile,
+ sessionId: sessionId ?? this.sessionId,
+ error: error ?? this.error,
+ );
+ }
+
+ @override
+ List