feat: register local agent project workspaces

This commit is contained in:
Hamza Ayed
2026-10-04 01:57:44 +03:00
parent d29275043a
commit 1feeb372a4
12 changed files with 806 additions and 45 deletions
@@ -478,6 +478,26 @@ class ApiRepository {
return (data['files'] as List<dynamic>).cast<String>();
}
Future<List<String>> registerWorkspaceProject(String workspacePath) async {
final response = await http.post(
Uri.parse('$_baseUrl/v1/agent/projects'),
headers: await _userHeaders(),
body: jsonEncode({'workspace_path': workspacePath}),
);
_checkStatus(response);
final data = jsonDecode(response.body) as Map<String, dynamic>;
return (data['files'] as List<dynamic>).cast<String>();
}
Future<void> unregisterWorkspaceProject(String workspacePath) async {
final response = await http.delete(
Uri.parse('$_baseUrl/v1/agent/projects'),
headers: await _userHeaders(),
body: jsonEncode({'workspace_path': workspacePath}),
);
_checkStatus(response);
}
Future<Map<String, dynamic>> indexWorkspaceKnowledge(
String workspacePath,
List<String> files,
@@ -271,7 +271,7 @@ class ChatCubit extends Cubit<ChatState> {
),
);
try {
final files = await _api.listWorkspaceFiles(path);
final files = await _api.registerWorkspaceProject(path);
if (isClosed || state.selectedWorkspacePath != path) return const [];
final savedPaths = [
...state.savedWorkspacePaths.where((savedPath) => savedPath != path),
@@ -300,9 +300,26 @@ class ChatCubit extends Cubit<ChatState> {
}
Future<bool> removeSavedWorkspace(String path) async {
try {
await _api.unregisterWorkspaceProject(path);
} catch (error) {
if (!isClosed) emit(state.copyWith(error: _readableError(error)));
return false;
}
final savedPaths =
state.savedWorkspacePaths.where((savedPath) => savedPath != path).toList();
emit(state.copyWith(savedWorkspacePaths: List.unmodifiable(savedPaths)));
state.savedWorkspacePaths
.where((savedPath) => savedPath != path)
.toList();
final isSelected = state.selectedWorkspacePath == path;
emit(
state.copyWith(
savedWorkspacePaths: List.unmodifiable(savedPaths),
isWorkspaceMode: isSelected ? false : null,
clearSelectedWorkspacePath: isSelected,
availableWorkspaceFiles: isSelected ? const [] : null,
selectedWorkspaceFiles: isSelected ? const [] : null,
),
);
return _persistSettings({'saved_workspace_paths': savedPaths});
}
@@ -180,7 +180,7 @@ class _ChatPageState extends State<ChatPage> {
(dialogContext) => AlertDialog(
title: const Text('إزالة المشروع من القائمة؟'),
content: Text(
'سيُزال ${_workspaceName(path)} من قائمة المشاريع المحفوظة فقط. لن تُحذف ملفاته.',
'سيُزال ${_workspaceName(path)} من قائمة المشاريع، ويُلغى أي تسجيل وصول خاص به. لن تُحذف ملفاته من جهازك.',
),
actions: [
TextButton(
@@ -698,8 +698,8 @@ class _ChatPageState extends State<ChatPage> {
content: Text(
api.accountSessionPersists
? credentials.$1
? 'تم إنشاء الحساب وتخزين الجلسة بأمان.'
: 'تم تسجيل الدخول وتخزين الجلسة بأمان.'
? 'تم إنشاء الحساب وتخزين الجلسة بأمان.'
: 'تم تسجيل الدخول وتخزين الجلسة بأمان.'
: credentials.$1
? 'تم إنشاء الحساب. جلسة الويب مؤقتة وستنتهي عند إعادة تحميل الصفحة.'
: 'تم تسجيل الدخول. جلسة الويب مؤقتة وستنتهي عند إعادة تحميل الصفحة.',
@@ -743,10 +743,11 @@ class _ChatPageState extends State<ChatPage> {
child: const Text('إلغاء'),
),
FilledButton(
onPressed: () => Navigator.pop(
dialogContext,
emailController.text.trim(),
),
onPressed:
() => Navigator.pop(
dialogContext,
emailController.text.trim(),
),
child: const Text('إرسال الرمز'),
),
],
@@ -818,7 +819,9 @@ class _ChatPageState extends State<ChatPage> {
passwordController.text.length < 12) {
ScaffoldMessenger.of(dialogContext).showSnackBar(
const SnackBar(
content: Text('أدخل رمزًا صالحًا وكلمة مرور من 12 محرفًا على الأقل.'),
content: Text(
'أدخل رمزًا صالحًا وكلمة مرور من 12 محرفًا على الأقل.',
),
),
);
return;
@@ -841,7 +844,9 @@ class _ChatPageState extends State<ChatPage> {
await api.completePasswordReset(token: reset.$1, newPassword: reset.$2);
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('تم تغيير كلمة المرور. سجّل الدخول بها الآن.')),
const SnackBar(
content: Text('تم تغيير كلمة المرور. سجّل الدخول بها الآن.'),
),
);
} catch (error) {
if (!mounted) return;
@@ -1121,14 +1126,18 @@ class _ChatPageState extends State<ChatPage> {
return Padding(
padding: const EdgeInsets.only(bottom: 3),
child: Material(
color: selected
? const Color(0xFFDCEAE3)
: Colors.transparent,
color:
selected
? const Color(0xFFDCEAE3)
: Colors.transparent,
borderRadius: BorderRadius.circular(10),
child: ListTile(
dense: true,
visualDensity: VisualDensity.compact,
contentPadding: const EdgeInsets.only(left: 3, right: 8),
contentPadding: const EdgeInsets.only(
left: 3,
right: 8,
),
leading: Icon(
Icons.folder_open_outlined,
size: 18,
@@ -1146,9 +1155,10 @@ class _ChatPageState extends State<ChatPage> {
icon: const Icon(Icons.close, size: 16),
onPressed: () => _removeSavedWorkspace(path),
),
onTap: chat.isSending
? null
: () => _openSavedWorkspace(path),
onTap:
chat.isSending
? null
: () => _openSavedWorkspace(path),
),
),
);