fix: keep agent streams alive during long tasks
This commit is contained in:
@@ -508,7 +508,7 @@ class ApiRepository {
|
||||
headers: await _userHeaders(),
|
||||
body: jsonEncode({'workspace_path': workspacePath, 'files': files}),
|
||||
)
|
||||
.timeout(const Duration(minutes: 2));
|
||||
.timeout(const Duration(minutes: 5));
|
||||
_checkStatus(response);
|
||||
return jsonDecode(response.body) as Map<String, dynamic>;
|
||||
}
|
||||
|
||||
+32
-11
@@ -337,25 +337,46 @@ class ChatCubit extends Cubit<ChatState> {
|
||||
if (path == null || files.isEmpty) {
|
||||
throw StateError('اختر مساحة عمل وحدد ملفًا واحدًا على الأقل للفهرسة.');
|
||||
}
|
||||
emit(state.copyWith(agentProgress: 'يفهرس الملفات المحددة محليًا…'));
|
||||
emit(
|
||||
state.copyWith(agentProgress: 'يجهز فهرسة ${files.length} ملفًا محليًا…'),
|
||||
);
|
||||
var indexedCount = 0;
|
||||
var chunks = 0;
|
||||
String? currentFile;
|
||||
try {
|
||||
final result = await _api.indexWorkspaceKnowledge(path, files);
|
||||
final indexed = result['indexed'] as List<dynamic>? ?? const [];
|
||||
final chunks = indexed.fold<int>(
|
||||
0,
|
||||
(total, item) =>
|
||||
total + ((item as Map<String, dynamic>)['chunks'] as int? ?? 0),
|
||||
);
|
||||
for (var index = 0; index < files.length; index++) {
|
||||
currentFile = files[index];
|
||||
final fileName = currentFile.split(RegExp(r'[/\\]')).last;
|
||||
emit(
|
||||
state.copyWith(
|
||||
agentProgress:
|
||||
'يفهرس الملف ${index + 1}/${files.length}: $fileName…',
|
||||
clearError: true,
|
||||
),
|
||||
);
|
||||
final result = await _api.indexWorkspaceKnowledge(path, [currentFile]);
|
||||
final indexed = result['indexed'] as List<dynamic>? ?? const [];
|
||||
if (indexed.length != 1) {
|
||||
throw StateError('لم يؤكد الخادم فهرسة الملف $fileName.');
|
||||
}
|
||||
indexedCount++;
|
||||
chunks +=
|
||||
(indexed.single as Map<String, dynamic>)['chunks'] as int? ?? 0;
|
||||
}
|
||||
if (!isClosed) {
|
||||
emit(state.copyWith(clearAgentProgress: true, clearError: true));
|
||||
}
|
||||
return 'فُهرست ${indexed.length} ملفًا محليًا في SQLite ($chunks مقطعًا).';
|
||||
return 'فُهرست $indexedCount ملفًا محليًا في SQLite ($chunks مقطعًا).';
|
||||
} catch (error) {
|
||||
final message = _readableError(error);
|
||||
final detail =
|
||||
indexedCount == 0
|
||||
? 'تعذرت فهرسة ${currentFile ?? 'الملف المحدد'}: $message'
|
||||
: 'فُهرست $indexedCount من ${files.length} ملفات. تعذرت فهرسة ${currentFile ?? 'الملف التالي'}: $message';
|
||||
if (!isClosed) {
|
||||
emit(state.copyWith(clearAgentProgress: true, error: message));
|
||||
emit(state.copyWith(clearAgentProgress: true, error: detail));
|
||||
}
|
||||
throw Exception(message);
|
||||
throw Exception(detail);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,8 @@ class _VersionTestApi extends ApiRepository {
|
||||
SavedConversation? lastSaved;
|
||||
String? indexedWorkspace;
|
||||
List<String> indexedFiles = const [];
|
||||
final indexedRequests = <List<String>>[];
|
||||
final availableFiles = <String>['README.md'];
|
||||
List<String> deletedFiles = const [];
|
||||
String? registeredProject;
|
||||
String? unregisteredProject;
|
||||
@@ -60,9 +62,8 @@ class _VersionTestApi extends ApiRepository {
|
||||
Future<List<SavedConversation>> listConversations() async => [];
|
||||
|
||||
@override
|
||||
Future<List<String>> listWorkspaceFiles(String workspacePath) async => [
|
||||
'README.md',
|
||||
];
|
||||
Future<List<String>> listWorkspaceFiles(String workspacePath) async =>
|
||||
List.of(availableFiles);
|
||||
|
||||
@override
|
||||
Future<List<String>> registerWorkspaceProject(String workspacePath) async {
|
||||
@@ -82,6 +83,8 @@ class _VersionTestApi extends ApiRepository {
|
||||
) async {
|
||||
indexedWorkspace = workspacePath;
|
||||
indexedFiles = files;
|
||||
indexedRequests.add(List.of(files));
|
||||
await Future<void>.delayed(const Duration(milliseconds: 1));
|
||||
return {
|
||||
'indexed': [
|
||||
for (final file in files) {'path': file, 'chunks': 2},
|
||||
@@ -175,21 +178,39 @@ void main() {
|
||||
|
||||
test('selected workspace files can be indexed as local knowledge', () async {
|
||||
final api = _VersionTestApi();
|
||||
api.availableFiles.add('lib/main.dart');
|
||||
final cubit = ChatCubit(api, settingsStore: LocalSettingsStore.inMemory());
|
||||
await Future<void>.delayed(Duration.zero);
|
||||
|
||||
await cubit.selectWorkspace(r'C:\Projects\sample');
|
||||
cubit.selectWorkspaceFiles(['README.md']);
|
||||
final progressUpdates = <String?>[];
|
||||
final subscription = cubit.stream.listen(
|
||||
(state) => progressUpdates.add(state.agentProgress),
|
||||
);
|
||||
cubit.selectWorkspaceFiles(['README.md', 'lib/main.dart']);
|
||||
final message = await cubit.indexSelectedWorkspaceKnowledge();
|
||||
|
||||
expect(api.indexedWorkspace, r'C:\Projects\sample');
|
||||
expect(api.indexedFiles, ['README.md']);
|
||||
expect(message, contains('2 مقطعًا'));
|
||||
expect(api.indexedRequests, [
|
||||
['README.md'],
|
||||
['lib/main.dart'],
|
||||
]);
|
||||
expect(message, contains('فُهرست 2 ملفًا'));
|
||||
expect(message, contains('4 مقطعًا'));
|
||||
expect(
|
||||
progressUpdates,
|
||||
contains('يفهرس الملف 1/2: README.md…'),
|
||||
);
|
||||
expect(
|
||||
progressUpdates,
|
||||
contains('يفهرس الملف 2/2: main.dart…'),
|
||||
);
|
||||
expect(cubit.state.agentProgress, isNull);
|
||||
await subscription.cancel();
|
||||
|
||||
final removal = await cubit.removeSelectedWorkspaceKnowledge();
|
||||
expect(api.deletedFiles, ['README.md']);
|
||||
expect(removal, contains('أُزيل 1 ملف'));
|
||||
expect(api.deletedFiles, ['README.md', 'lib/main.dart']);
|
||||
expect(removal, contains('أُزيل 2 ملف'));
|
||||
await cubit.close();
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user