refactor: 重构聊天数据层至core并简化首页UI

This commit is contained in:
zl-q
2026-03-29 21:46:26 +08:00
parent 4db9a13bfe
commit f126d7a547
18 changed files with 568 additions and 328 deletions
@@ -1,60 +1,87 @@
import 'package:dio/dio.dart';
import 'dart:typed_data';
import 'package:flutter_test/flutter_test.dart';
import 'package:social_app/data/network/i_api_client.dart';
import 'package:social_app/core/chat/chat_api.dart';
import 'package:social_app/core/chat/chat_history_repository.dart';
import 'package:social_app/data/cache/cache_store.dart';
import 'package:social_app/features/chat/data/repositories/chat_history_repository.dart';
class _FakeApiClient implements IApiClient {
final Map<String, dynamic> _getResponses = <String, dynamic>{};
final Map<String, int> getCalls = <String, int>{};
class _FakeChatApi implements ChatApi {
final Map<String, dynamic> _historyResponses = <String, dynamic>{};
final Map<String, int> historyCalls = <String, int>{};
void setGet(String path, dynamic data) => _getResponses[path] = data;
void setHistory(String key, dynamic data) => _historyResponses[key] = data;
@override
Future<Response<T>> get<T>(String path, {Options? options}) async {
getCalls[path] = (getCalls[path] ?? 0) + 1;
if (!_getResponses.containsKey(path)) {
throw StateError('missing GET mock for $path');
Future<Map<String, dynamic>> fetchHistory({
String? threadId,
DateTime? beforeDate,
}) async {
final key = _historyKey(threadId: threadId, beforeDate: beforeDate);
historyCalls[key] = (historyCalls[key] ?? 0) + 1;
if (!_historyResponses.containsKey(key)) {
throw StateError('missing history mock for $key');
}
return Response<T>(
requestOptions: RequestOptions(path: path),
data: _getResponses[path] as T,
);
return _historyResponses[key] as Map<String, dynamic>;
}
@override
Future<Response<T>> delete<T>(String path, {data, Options? options}) {
Future<void> cancelRun({required String threadId, required String runId}) {
throw UnimplementedError();
}
@override
Future<Stream<String>> getSseLines(
String path, {
Map<String, String>? headers,
Future<Map<String, dynamic>> createRun(Map<String, dynamic> runInput) {
throw UnimplementedError();
}
@override
Future<Uint8List> fetchAttachmentPreview(String previewPath) {
throw UnimplementedError();
}
@override
Future<Stream<String>> streamRunEvents(
String threadId, {
String? lastEventId,
}) {
throw UnimplementedError();
}
@override
Future<Response<T>> patch<T>(String path, {data, Options? options}) {
Future<String> transcribeAudio(String filePath) {
throw UnimplementedError();
}
@override
Future<Response<T>> post<T>(String path, {data, Options? options}) {
Future<Map<String, dynamic>> uploadAttachment({
required String threadId,
required String filename,
required String mimeType,
required Uint8List bytes,
}) {
throw UnimplementedError();
}
@override
Future<Response<T>> put<T>(String path, {data, Options? options}) {
throw UnimplementedError();
String _historyKey({String? threadId, DateTime? beforeDate}) {
final threadPart = (threadId == null || threadId.isEmpty)
? 'default'
: threadId;
if (beforeDate == null) {
return 'first:$threadPart';
}
final day = DateTime(
beforeDate.year,
beforeDate.month,
beforeDate.day,
).toIso8601String().substring(0, 10);
return 'before:$threadPart:$day';
}
}
void main() {
test('loads first-page history from cache on second read', () async {
final client = _FakeApiClient();
client.setGet('/api/v1/agent/history', {
final chatApi = _FakeChatApi();
chatApi.setHistory('first:default', {
'scope': 'history_day',
'threadId': 't1',
'day': '2026-03-29',
@@ -72,7 +99,7 @@ void main() {
});
final repository = ChatHistoryRepository(
apiClient: client,
chatApi: chatApi,
store: HybridCacheStore(
memory: MemoryCacheStore(),
persistent: PersistentCacheStore(),
@@ -84,6 +111,6 @@ void main() {
expect(first.threadId, 't1');
expect(second.messages.length, 1);
expect(client.getCalls['/api/v1/agent/history'], 1);
expect(chatApi.historyCalls['first:default'], 1);
});
}