From 6db0f61321b9c3d19d42a7d600dc0e751a34b252 Mon Sep 17 00:00:00 2001 From: Udhay-Adithya Date: Sun, 7 Sep 2025 23:48:55 +0530 Subject: [PATCH] feat: add terminal state provider --- lib/models/models.dart | 1 + lib/providers/providers.dart | 1 + lib/providers/terminal_providers.dart | 179 ++++++++++++++++++++++++++ 3 files changed, 181 insertions(+) create mode 100644 lib/providers/terminal_providers.dart diff --git a/lib/models/models.dart b/lib/models/models.dart index 5d17479a..6a724dd5 100644 --- a/lib/models/models.dart +++ b/lib/models/models.dart @@ -2,3 +2,4 @@ export 'history_meta_model.dart'; export 'history_request_model.dart'; export 'request_model.dart'; export 'settings_model.dart'; +export 'terminal_models.dart'; diff --git a/lib/providers/providers.dart b/lib/providers/providers.dart index 1a906ce5..cf1b9426 100644 --- a/lib/providers/providers.dart +++ b/lib/providers/providers.dart @@ -2,5 +2,6 @@ export 'ai_providers.dart'; export 'collection_providers.dart'; export 'environment_providers.dart'; export 'history_providers.dart'; +export 'terminal_providers.dart'; export 'settings_providers.dart'; export 'ui_providers.dart'; diff --git a/lib/providers/terminal_providers.dart b/lib/providers/terminal_providers.dart new file mode 100644 index 00000000..3ec406ab --- /dev/null +++ b/lib/providers/terminal_providers.dart @@ -0,0 +1,179 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:uuid/uuid.dart'; +import 'package:apidash_core/apidash_core.dart'; +import '../models/terminal_models.dart'; + +final terminalStateProvider = + StateNotifierProvider((ref) { + return TerminalController(); +}); + +class TerminalState { + TerminalState({required this.entries}) + : index = { + for (var i = 0; i < entries.length; i++) entries[i].id: i, + }; + + final List entries; // newest first + final Map index; + + TerminalState copyWith({List? entries}) { + return TerminalState(entries: entries ?? this.entries); + } +} + +class TerminalController extends StateNotifier { + TerminalController() : super(TerminalState(entries: [])); + + static const _uuid = Uuid(); + + String _newId() => _uuid.v4(); + + void clear() { + state = TerminalState(entries: []); + } + + String append(TerminalEntry entry) { + final list = [entry, ...state.entries]; + state = TerminalState(entries: list); + return entry.id; + } + + void _updateById(String id, TerminalEntry Function(TerminalEntry) updater) { + final idx = state.index[id]; + if (idx == null) return; + final current = state.entries[idx]; + final updated = updater(current); + final list = [...state.entries]; + list[idx] = updated; + state = TerminalState(entries: list); + } + + // Convenience builders + String startNetwork({ + required APIType apiType, + required HTTPVerb method, + required String url, + String? requestId, + Map? requestHeaders, + String? requestBodyPreview, + bool isStreaming = false, + }) { + final id = _newId(); + final entry = TerminalEntry( + id: id, + ts: DateTime.now(), + source: TerminalSource.network, + level: TerminalLevel.info, + requestId: requestId, + network: NetworkLogData( + phase: NetworkPhase.started, + apiType: apiType, + method: method, + url: url, + requestHeaders: requestHeaders, + requestBodyPreview: requestBodyPreview, + isStreaming: isStreaming, + sentAt: DateTime.now(), + ), + ); + append(entry); + return id; + } + + void addNetworkChunk(String logId, BodyChunk chunk) { + _updateById(logId, (e) { + final n = e.network; + if (n == null) return e; + n.chunks.add(chunk); + return e.copyWith( + network: n.copyWith(phase: NetworkPhase.progress), + ); + }); + } + + void completeNetwork( + String logId, { + int? statusCode, + Map? responseHeaders, + String? responseBodyPreview, + Duration? duration, + }) { + _updateById(logId, (e) { + final n = e.network; + if (n == null) return e; + return e.copyWith( + level: (statusCode != null && statusCode >= 400) + ? TerminalLevel.error + : TerminalLevel.info, + network: n.copyWith( + phase: NetworkPhase.completed, + responseStatus: statusCode, + responseHeaders: responseHeaders, + responseBodyPreview: responseBodyPreview, + duration: duration, + completedAt: DateTime.now(), + ), + ); + }); + } + + void failNetwork(String logId, String message) { + _updateById(logId, (e) { + final n = e.network; + if (n == null) return e; + return e.copyWith( + level: TerminalLevel.error, + network: n.copyWith( + phase: NetworkPhase.failed, + errorMessage: message, + completedAt: DateTime.now(), + ), + ); + }); + } + + void logJs({ + required String level, + required List args, + String? stack, + String? context, + String? contextRequestId, + }) { + append(TerminalEntry( + id: _newId(), + ts: DateTime.now(), + source: TerminalSource.js, + level: switch (level) { + 'warn' => TerminalLevel.warn, + 'error' || 'fatal' => TerminalLevel.error, + _ => TerminalLevel.info, + }, + requestId: contextRequestId, + js: JsLogData( + level: level, + args: args, + stack: stack, + context: context, + contextRequestId: contextRequestId, + ), + )); + } + + void logSystem({ + required String category, + required String message, + String? stack, + TerminalLevel level = TerminalLevel.info, + List tags = const [], + }) { + append(TerminalEntry( + id: _newId(), + ts: DateTime.now(), + source: TerminalSource.system, + level: level, + tags: tags, + system: SystemLogData(category: category, message: message, stack: stack), + )); + } +}