Save verified Mithqal AI project progress

This commit is contained in:
Hamza Ayed
2026-10-01 01:02:03 +03:00
parent f0a444ca46
commit e1f981d29e
111 changed files with 1589 additions and 295 deletions
@@ -14,9 +14,42 @@ class ChatCubit extends Cubit<ChatState> {
Future<void> _loadModelName() async {
try {
final modelName = await _api.getModelName();
if (!isClosed) emit(state.copyWith(modelName: modelName));
final models = await _api.getAvailableModels();
if (!isClosed) {
emit(
state.copyWith(
modelName: modelName,
availableModels: models,
selectedModel: state.selectedModel ?? modelName,
),
);
}
} catch (_) {
if (!isClosed) emit(state.copyWith(modelName: 'تعذر الاتصال بالخادم'));
if (!isClosed) {
emit(
state.copyWith(
modelName: 'تعذر الاتصال بالخادم',
availableModels: const [],
),
);
}
}
}
void selectModel(String model) {
if (state.availableModels.contains(model)) {
emit(state.copyWith(selectedModel: model, clearError: true));
}
}
void toggleWorkspaceMode() {
if (!state.isSending) {
emit(
state.copyWith(
isWorkspaceMode: !state.isWorkspaceMode,
clearError: true,
),
);
}
}
@@ -76,20 +109,79 @@ class ChatCubit extends Cubit<ChatState> {
}
Future<void> send(String text) async {
await _sendWithHistory(text, state.messages);
}
Future<void> regenerateLastAnswer() async {
if (state.isSending) return;
final messages = state.messages;
if (messages.length < 2 ||
messages.last.fromUser ||
!messages[messages.length - 2].fromUser) {
return;
}
final history = messages.sublist(0, messages.length - 1);
await _sendWithHistory(
history.last.text,
history,
appendUserMessage: false,
);
}
Future<void> editLastQuestion(String text) async {
if (state.isSending || text.trim().isEmpty) return;
final messages = state.messages;
final questionIndex = messages.lastIndexWhere(
(message) => message.fromUser,
);
if (questionIndex < 0 || questionIndex != messages.length - 2) return;
final history = [
...messages.take(questionIndex),
ChatMessage(text.trim(), fromUser: true),
];
await _sendWithHistory(text.trim(), history, appendUserMessage: false);
}
Future<void> _sendWithHistory(
String text,
List<ChatMessage> history, {
bool appendUserMessage = true,
}) async {
final prompt = text.trim();
if (prompt.isEmpty || state.isSending || state.isLoadingHistory) return;
final messages = [...state.messages, ChatMessage(prompt, fromUser: true)];
final messages =
appendUserMessage
? [...history, ChatMessage(prompt, fromUser: true)]
: List<ChatMessage>.from(history);
final originalMessages = state.messages;
var answer = '';
emit(state.copyWith(messages: messages, isSending: true, clearError: true));
try {
await _saveCurrent(messages);
if (appendUserMessage) await _saveCurrent(messages);
if (isClosed) return;
emit(
state.copyWith(
messages: [...messages, const ChatMessage('', fromUser: false)],
),
);
await for (final delta in _api.completeStream(messages)) {
if (state.isWorkspaceMode) {
answer = await _api.askWorkspaceAgent(
prompt,
model: state.selectedModel,
);
if (isClosed) return;
if (answer.trim().isEmpty) {
throw Exception('لم يصل نص في إجابة الوكيل.');
}
final updated = [...messages, ChatMessage(answer, fromUser: false)];
emit(state.copyWith(messages: updated, isSending: false));
await _saveCurrent(updated);
return;
}
await for (final delta in _api.completeStream(
messages,
model: state.selectedModel,
)) {
if (isClosed) return;
answer += delta;
emit(
@@ -106,7 +198,9 @@ class ChatCubit extends Cubit<ChatState> {
} catch (error) {
if (isClosed) return;
final partial =
answer.isEmpty
!appendUserMessage
? originalMessages
: answer.isEmpty
? messages
: [...messages, ChatMessage(answer, fromUser: false)];
emit(
@@ -116,7 +210,7 @@ class ChatCubit extends Cubit<ChatState> {
error: error.toString(),
),
);
if (answer.isNotEmpty) {
if (appendUserMessage && answer.isNotEmpty) {
try {
await _saveCurrent(partial);
} catch (_) {
@@ -181,7 +275,14 @@ class ChatCubit extends Cubit<ChatState> {
final url = value.trim();
if (url.isEmpty) return;
_api.setBaseUrl(url);
emit(state.copyWith(apiBaseUrl: _api.baseUrl, clearError: true));
emit(
state.copyWith(
apiBaseUrl: _api.baseUrl,
availableModels: const [],
clearSelectedModel: true,
clearError: true,
),
);
_loadModelName();
}
}
@@ -59,8 +59,11 @@ class ChatState {
this.activeConversationId,
this.isLoadingHistory = true,
this.isSending = false,
this.isWorkspaceMode = false,
this.error,
this.modelName = 'جارٍ اكتشاف النموذج…',
this.availableModels = const [],
this.selectedModel,
required this.apiBaseUrl,
});
@@ -69,8 +72,11 @@ class ChatState {
final String? activeConversationId;
final bool isLoadingHistory;
final bool isSending;
final bool isWorkspaceMode;
final String? error;
final String modelName;
final List<String> availableModels;
final String? selectedModel;
final String apiBaseUrl;
ChatState copyWith({
@@ -80,9 +86,13 @@ class ChatState {
bool clearActiveConversation = false,
bool? isLoadingHistory,
bool? isSending,
bool? isWorkspaceMode,
String? error,
bool clearError = false,
String? modelName,
List<String>? availableModels,
String? selectedModel,
bool clearSelectedModel = false,
String? apiBaseUrl,
}) => ChatState(
messages: messages ?? this.messages,
@@ -93,8 +103,12 @@ class ChatState {
: (activeConversationId ?? this.activeConversationId),
isLoadingHistory: isLoadingHistory ?? this.isLoadingHistory,
isSending: isSending ?? this.isSending,
isWorkspaceMode: isWorkspaceMode ?? this.isWorkspaceMode,
error: clearError ? null : (error ?? this.error),
modelName: modelName ?? this.modelName,
availableModels: availableModels ?? this.availableModels,
selectedModel:
clearSelectedModel ? null : (selectedModel ?? this.selectedModel),
apiBaseUrl: apiBaseUrl ?? this.apiBaseUrl,
);
}