Add web research and source file reading
This commit is contained in:
@@ -205,6 +205,45 @@ class ApiRepository {
|
||||
}
|
||||
}
|
||||
|
||||
Future<String> searchWeb(String query, {String? model}) async {
|
||||
final client = http.Client();
|
||||
_activeRequestClient = client;
|
||||
try {
|
||||
final request = http.Request(
|
||||
'POST',
|
||||
Uri.parse('$_baseUrl/v1/web/search'),
|
||||
)
|
||||
..headers['Content-Type'] = 'application/json'
|
||||
..body = jsonEncode({
|
||||
'query': query,
|
||||
'max_results': 5,
|
||||
if (model != null) 'model': model,
|
||||
});
|
||||
final streamed = await client
|
||||
.send(request)
|
||||
.timeout(const Duration(minutes: 10));
|
||||
final response = await http.Response.fromStream(streamed);
|
||||
_checkStatus(response);
|
||||
final data = jsonDecode(response.body) as Map<String, dynamic>;
|
||||
final result = data['result'] as String? ?? '';
|
||||
final sources = (data['sources'] as List<dynamic>? ?? const [])
|
||||
.cast<Map<String, dynamic>>();
|
||||
if (sources.isEmpty) return result;
|
||||
final sourceList = sources.map((source) {
|
||||
final index = source['index'];
|
||||
final title = source['title'] as String? ?? 'مصدر';
|
||||
final url = source['url'] as String? ?? '';
|
||||
final status = source['status'] as String?;
|
||||
final note = status == 'snippet_only' ? ' (مقتطف فقط؛ تعذر فتح الصفحة)' : '';
|
||||
return '[$index] [$title]($url)$note';
|
||||
}).join('\n');
|
||||
return '$result\n\nالمصادر:\n$sourceList';
|
||||
} finally {
|
||||
if (identical(_activeRequestClient, client)) _activeRequestClient = null;
|
||||
client.close();
|
||||
}
|
||||
}
|
||||
|
||||
Stream<String> completeStream(
|
||||
List<ChatMessage> messages, {
|
||||
String? model,
|
||||
|
||||
+18
-5
@@ -88,6 +88,19 @@ class ChatCubit extends Cubit<ChatState> {
|
||||
emit(
|
||||
state.copyWith(
|
||||
isWorkspaceMode: !state.isWorkspaceMode,
|
||||
isWebSearchMode: false,
|
||||
clearError: true,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void toggleWebSearchMode() {
|
||||
if (!state.isSending) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
isWebSearchMode: !state.isWebSearchMode,
|
||||
isWorkspaceMode: false,
|
||||
clearError: true,
|
||||
),
|
||||
);
|
||||
@@ -254,11 +267,11 @@ class ChatCubit extends Cubit<ChatState> {
|
||||
),
|
||||
);
|
||||
_startElapsedTimer(generation);
|
||||
if (state.isWorkspaceMode) {
|
||||
answer = await _api.askWorkspaceAgent(
|
||||
prompt,
|
||||
model: state.selectedModel,
|
||||
);
|
||||
if (state.isWorkspaceMode || state.isWebSearchMode) {
|
||||
answer =
|
||||
state.isWorkspaceMode
|
||||
? await _api.askWorkspaceAgent(prompt, model: state.selectedModel)
|
||||
: await _api.searchWeb(prompt, model: state.selectedModel);
|
||||
if (isClosed || generation != _requestGeneration) return;
|
||||
if (answer.trim().isEmpty) {
|
||||
throw Exception('لم يصل نص في إجابة الوكيل.');
|
||||
|
||||
@@ -81,6 +81,7 @@ class ChatState {
|
||||
this.isLoadingHistory = true,
|
||||
this.isSending = false,
|
||||
this.isWorkspaceMode = false,
|
||||
this.isWebSearchMode = false,
|
||||
this.error,
|
||||
this.modelName = 'جارٍ اكتشاف النموذج…',
|
||||
this.modelStatus = ModelConnectionStatus.checking,
|
||||
@@ -98,6 +99,7 @@ class ChatState {
|
||||
final bool isLoadingHistory;
|
||||
final bool isSending;
|
||||
final bool isWorkspaceMode;
|
||||
final bool isWebSearchMode;
|
||||
final String? error;
|
||||
final String modelName;
|
||||
final ModelConnectionStatus modelStatus;
|
||||
@@ -116,6 +118,7 @@ class ChatState {
|
||||
bool? isLoadingHistory,
|
||||
bool? isSending,
|
||||
bool? isWorkspaceMode,
|
||||
bool? isWebSearchMode,
|
||||
String? error,
|
||||
bool clearError = false,
|
||||
String? modelName,
|
||||
@@ -138,6 +141,7 @@ class ChatState {
|
||||
isLoadingHistory: isLoadingHistory ?? this.isLoadingHistory,
|
||||
isSending: isSending ?? this.isSending,
|
||||
isWorkspaceMode: isWorkspaceMode ?? this.isWorkspaceMode,
|
||||
isWebSearchMode: isWebSearchMode ?? this.isWebSearchMode,
|
||||
error: clearError ? null : (error ?? this.error),
|
||||
modelName: modelName ?? this.modelName,
|
||||
modelStatus: modelStatus ?? this.modelStatus,
|
||||
|
||||
+18
-2
@@ -554,6 +554,18 @@ class _ChatPageState extends State<ChatPage> {
|
||||
: Icons.folder_outlined,
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () => context.read<ChatCubit>().toggleWebSearchMode(),
|
||||
tooltip:
|
||||
context.watch<ChatCubit>().state.isWebSearchMode
|
||||
? 'إيقاف البحث العميق على الإنترنت'
|
||||
: 'بحث عميق على الإنترنت من عدة مصادر',
|
||||
color:
|
||||
context.watch<ChatCubit>().state.isWebSearchMode
|
||||
? _green
|
||||
: _ink,
|
||||
icon: const Icon(Icons.travel_explore_rounded),
|
||||
),
|
||||
if (context.watch<ChatCubit>().state.availableModels.isNotEmpty)
|
||||
ConstrainedBox(
|
||||
constraints: BoxConstraints(maxWidth: compact ? 100 : 190),
|
||||
@@ -690,7 +702,9 @@ class _ChatPageState extends State<ChatPage> {
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
context.watch<ChatCubit>().state.isWorkspaceMode
|
||||
? 'وضع الوكيل يبحث في ملفات المشروع للقراءة فقط.'
|
||||
? 'وضع الملفات: اسأل عن ملفات الكود في مساحة العمل للقراءة فقط.'
|
||||
: context.watch<ChatCubit>().state.isWebSearchMode
|
||||
? 'بحث عميق: يجلب مصادر ويب متعددة ويلخصها مع الروابط.'
|
||||
: 'اكتب سؤالك، وسأرسله إلى النموذج المحلي على جهازك.',
|
||||
style: const TextStyle(color: Color(0xFF718078), fontSize: 14),
|
||||
),
|
||||
@@ -947,7 +961,9 @@ class _ChatPageState extends State<ChatPage> {
|
||||
decoration: InputDecoration(
|
||||
hintText:
|
||||
chat.isWorkspaceMode
|
||||
? 'اسأل عن ملفات مساحة العمل…'
|
||||
? 'اسأل عن ملف كود أو عن مساحة العمل…'
|
||||
: chat.isWebSearchMode
|
||||
? 'ما الموضوع الذي تريد البحث عنه على الإنترنت؟'
|
||||
: 'اكتب رسالتك هنا…',
|
||||
hintStyle: TextStyle(color: Color(0xFF9BA59E)),
|
||||
border: InputBorder.none,
|
||||
|
||||
Reference in New Issue
Block a user