refactor: remove unused Dashbot context and request provider; update chat viewmodel to use the existing request model

This commit is contained in:
Udhay-Adithya
2025-09-02 12:44:10 +05:30
parent 5b6c1d5139
commit aef4ee5039
13 changed files with 97 additions and 191 deletions

View File

@@ -14,7 +14,6 @@ class DashbotDefaultPage extends StatelessWidget {
children: [
kVSpacer16,
DashbotIcons.getDashbotIcon1(width: 60),
kVSpacer20,
Text(
'Hello there!',
@@ -22,7 +21,7 @@ class DashbotDefaultPage extends StatelessWidget {
),
kVSpacer10,
Text(
'Request not made yet',
'Seems like you haven\'t made any Requests yet',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14,
@@ -32,7 +31,7 @@ class DashbotDefaultPage extends StatelessWidget {
Text(
"Why not go ahead and make one?",
textAlign: TextAlign.center,
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w200),
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w400),
),
],
);

View File

@@ -1,29 +0,0 @@
import 'package:apidash_core/apidash_core.dart';
/// Context object that Dashbot needs from the host app.
///
/// Host apps should create/override a provider that returns this object
/// so Dashbot can react to changes in the current request selection.
class DashbotRequestContext {
final String? requestId;
final String? requestName;
final String? requestDescription;
final APIType apiType;
final AIRequestModel? aiRequestModel;
final HttpRequestModel? httpRequestModel;
final int? responseStatus;
final String? responseMessage;
final HttpResponseModel? httpResponseModel;
const DashbotRequestContext({
required this.apiType,
this.requestId,
this.requestName,
this.requestDescription,
this.aiRequestModel,
this.httpRequestModel,
this.responseStatus,
this.responseMessage,
this.httpResponseModel,
});
}

View File

@@ -1,9 +0,0 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../model/dashbot_request_context.dart';
/// Default provider for Dashbot's external request context.
/// The host app should override this provider at the Dashbot subtree.
final dashbotRequestContextProvider = Provider<DashbotRequestContext?>(
(ref) => null,
);

View File

@@ -1,15 +1,9 @@
import '../model/dashbot_window_model.dart';
import 'package:flutter/material.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
part 'dashbot_window_notifier.g.dart';
@Riverpod(keepAlive: true)
class DashbotWindowNotifier extends _$DashbotWindowNotifier {
@override
DashbotWindowModel build() {
return const DashbotWindowModel();
}
class DashbotWindowNotifier extends StateNotifier<DashbotWindowModel> {
DashbotWindowNotifier() : super(const DashbotWindowModel());
void updatePosition(double dx, double dy, Size screenSize) {
state = state.copyWith(
@@ -34,3 +28,8 @@ class DashbotWindowNotifier extends _$DashbotWindowNotifier {
state = state.copyWith(isActive: !state.isActive);
}
}
final dashbotWindowNotifierProvider =
StateNotifierProvider<DashbotWindowNotifier, DashbotWindowModel>((ref) {
return DashbotWindowNotifier();
});

View File

@@ -1,5 +1,3 @@
export 'dashbot_dashboard.dart';
export 'core/providers/dashbot_window_notifier.dart';
export 'core/utils/utils.dart';
export 'core/model/dashbot_request_context.dart';
export 'core/providers/dashbot_request_provider.dart';

View File

@@ -1,8 +1,9 @@
import 'package:apidash/providers/providers.dart';
import 'package:apidash_core/apidash_core.dart';
import 'package:apidash_design_system/apidash_design_system.dart';
import 'core/utils/dashbot_icons.dart';
import 'core/providers/dashbot_window_notifier.dart';
import 'core/providers/dashbot_request_provider.dart';
import 'core/routes/dashbot_router.dart';
import 'core/routes/dashbot_routes.dart';
import 'package:flutter/material.dart';
@@ -17,7 +18,11 @@ class DashbotWindow extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final windowState = ref.watch(dashbotWindowNotifierProvider);
final windowNotifier = ref.read(dashbotWindowNotifierProvider.notifier);
final dashbotCtx = ref.watch(dashbotRequestContextProvider);
final defaultModelJson = ref.watch(settingsProvider).defaultAIModel;
final dashbotCtx = defaultModelJson == null
? const AIRequestModel()
: AIRequestModel.fromJson(defaultModelJson);
final currentRequest = ref.watch(selectedRequestModelProvider);
return Stack(
children: [
@@ -66,13 +71,9 @@ class DashbotWindow extends ConsumerWidget {
// TODO: remove the show active request name/model in prod
kHSpacer12,
Text(
dashbotCtx
?.aiRequestModel
?.modelApiProvider
?.name ==
null
dashbotCtx.modelApiProvider?.name == null
? 'DashBot'
: 'DashBot · ${dashbotCtx?.aiRequestModel?.modelApiProvider?.name}',
: 'DashBot · ${dashbotCtx.modelApiProvider?.name}',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
@@ -96,10 +97,9 @@ class DashbotWindow extends ConsumerWidget {
),
Expanded(
child: Navigator(
initialRoute: DashbotRoutes.dashbotHome,
// currentRequest?.responseStatus == null
// ? DashbotRoutes.dashbotDefault
// : DashbotRoutes.dashbotHome,
initialRoute: currentRequest?.responseStatus == null
? DashbotRoutes.dashbotDefault
: DashbotRoutes.dashbotHome,
onGenerateRoute: generateRoute,
),
),

View File

@@ -37,17 +37,16 @@ class ChatBubble extends StatelessWidget {
);
}
return Align(
alignment:
role == MessageRole.user
? Alignment.centerRight
: Alignment.centerLeft,
alignment: role == MessageRole.user
? Alignment.centerRight
: Alignment.centerLeft,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (role == MessageRole.system) ...[
kVSpacer6,
Image.asset("assets/dashbot_icon_1.png", width: 42),
DashbotIcons.getDashbotIcon1(width: 42),
kVSpacer8,
],
Container(
@@ -57,10 +56,9 @@ class ChatBubble extends StatelessWidget {
maxWidth: MediaQuery.of(context).size.width * 0.75,
),
decoration: BoxDecoration(
color:
role == MessageRole.user
? Theme.of(context).colorScheme.primaryContainer
: Theme.of(context).colorScheme.surfaceContainerHighest,
color: role == MessageRole.user
? Theme.of(context).colorScheme.primaryContainer
: Theme.of(context).colorScheme.surfaceContainerHighest,
borderRadius: BorderRadius.circular(16.0),
),
child: MarkdownBody(
@@ -70,11 +68,10 @@ class ChatBubble extends StatelessWidget {
Theme.of(context),
).copyWith(
p: Theme.of(context).textTheme.bodyMedium?.copyWith(
color:
role == MessageRole.user
color: role == MessageRole.user
? Theme.of(context).colorScheme.surfaceBright
: Theme.of(context).colorScheme.onSurface,
),
),
),
),
),

View File

@@ -1,11 +1,12 @@
import 'dart:async';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:genai/genai.dart' as genai;
import 'package:apidash/providers/providers.dart';
import 'package:apidash/models/models.dart';
import 'package:nanoid/nanoid.dart';
import '../../../core/constants/dashbot_prompts.dart' as dash;
import '../../../core/model/dashbot_request_context.dart';
import '../../../core/providers/dashbot_request_provider.dart';
import '../view/widgets/chat_bubble.dart';
import '../models/chat_models.dart';
import '../repository/chat_remote_repository.dart';
@@ -17,11 +18,20 @@ class ChatViewmodel extends StateNotifier<ChatState> {
StreamSubscription<String>? _sub;
ChatRemoteRepository get _repo => _ref.read(chatRepositoryProvider);
DashbotRequestContext? get _ctx => _ref.read(dashbotRequestContextProvider);
// Currently selected request and AI model are read from app providers
RequestModel? get _currentRequest => _ref.read(selectedRequestModelProvider);
genai.AIRequestModel? get _selectedAIModel {
final json = _ref.read(settingsProvider).defaultAIModel;
if (json == null) return null;
try {
return genai.AIRequestModel.fromJson(json);
} catch (_) {
return null;
}
}
List<ChatMessage> get currentMessages {
final id = _ctx?.requestId;
if (id == null) return const [];
final id = _currentRequest?.id ?? 'global';
return state.chatSessions[id] ?? const [];
}
@@ -30,8 +40,7 @@ class ChatViewmodel extends StateNotifier<ChatState> {
ChatMessageType type = ChatMessageType.general,
bool countAsUser = true,
}) async {
final ctx = _ctx;
final ai = ctx?.aiRequestModel;
final ai = _selectedAIModel;
if (text.trim().isEmpty && countAsUser) return;
if (ai == null) {
_appendSystem(
@@ -41,7 +50,7 @@ class ChatViewmodel extends StateNotifier<ChatState> {
return;
}
final requestId = ctx?.requestId ?? 'global';
final requestId = _currentRequest?.id ?? 'global';
if (countAsUser) {
_addMessage(
@@ -56,7 +65,7 @@ class ChatViewmodel extends StateNotifier<ChatState> {
);
}
final systemPrompt = _composeSystemPrompt(ctx, type);
final systemPrompt = _composeSystemPrompt(_currentRequest, type);
final enriched = ai.copyWith(
systemPrompt: systemPrompt,
userPrompt: text,
@@ -66,39 +75,36 @@ class ChatViewmodel extends StateNotifier<ChatState> {
// start stream
_sub?.cancel();
state = state.copyWith(isGenerating: true, currentStreamingResponse: '');
_sub = _repo
.streamChat(request: enriched)
.listen(
(chunk) {
state = state.copyWith(
currentStreamingResponse:
state.currentStreamingResponse + (chunk),
);
},
onError: (e) {
state = state.copyWith(isGenerating: false);
_appendSystem('Error: $e', type);
},
onDone: () {
if (state.currentStreamingResponse.isNotEmpty) {
_addMessage(
requestId,
ChatMessage(
id: nanoid(),
content: state.currentStreamingResponse,
role: MessageRole.system,
timestamp: DateTime.now(),
messageType: type,
),
);
}
state = state.copyWith(
isGenerating: false,
currentStreamingResponse: '',
);
},
cancelOnError: true,
_sub = _repo.streamChat(request: enriched).listen(
(chunk) {
state = state.copyWith(
currentStreamingResponse: state.currentStreamingResponse + (chunk),
);
},
onError: (e) {
state = state.copyWith(isGenerating: false);
_appendSystem('Error: $e', type);
},
onDone: () {
if (state.currentStreamingResponse.isNotEmpty) {
_addMessage(
requestId,
ChatMessage(
id: nanoid(),
content: state.currentStreamingResponse,
role: MessageRole.system,
timestamp: DateTime.now(),
messageType: type,
),
);
}
state = state.copyWith(
isGenerating: false,
currentStreamingResponse: '',
);
},
cancelOnError: true,
);
}
void cancel() {
@@ -118,7 +124,7 @@ class ChatViewmodel extends StateNotifier<ChatState> {
}
void _appendSystem(String text, ChatMessageType type) {
final id = _ctx?.requestId ?? 'global';
final id = _currentRequest?.id ?? 'global';
_addMessage(
id,
ChatMessage(
@@ -132,12 +138,12 @@ class ChatViewmodel extends StateNotifier<ChatState> {
}
String _composeSystemPrompt(
DashbotRequestContext? ctx,
RequestModel? req,
ChatMessageType type,
) {
final history = _buildHistoryBlock();
final contextBlock = _buildContextBlock(ctx);
final task = _buildTaskPrompt(ctx, type);
final contextBlock = _buildContextBlock(req);
final task = _buildTaskPrompt(req, type);
return [
if (task != null) task,
if (contextBlock != null) contextBlock,
@@ -146,7 +152,7 @@ class ChatViewmodel extends StateNotifier<ChatState> {
}
String _buildHistoryBlock({int maxTurns = 8}) {
final id = _ctx?.requestId ?? 'global';
final id = _currentRequest?.id ?? 'global';
final messages = state.chatSessions[id] ?? const [];
if (messages.isEmpty) return '';
final start = messages.length > maxTurns ? messages.length - maxTurns : 0;
@@ -162,35 +168,35 @@ class ChatViewmodel extends StateNotifier<ChatState> {
return buf.toString();
}
String? _buildContextBlock(DashbotRequestContext? ctx) {
final http = ctx?.httpRequestModel;
if (ctx == null || http == null) return null;
String? _buildContextBlock(RequestModel? req) {
final http = req?.httpRequestModel;
if (req == null || http == null) return null;
final headers = http.headersMap.entries
.map((e) => '"${e.key}": "${e.value}"')
.join(', ');
return '''<request_context>
Request Name: ${ctx.requestName ?? ''}
Request Name: ${req.name}
URL: ${http.url}
Method: ${http.method.name.toUpperCase()}
Status: ${ctx.responseStatus ?? ''}
Status: ${req.responseStatus ?? ''}
Content-Type: ${http.bodyContentType.name}
Headers: { $headers }
Body: ${http.body ?? ''}
Response: ${ctx.httpResponseModel?.body ?? ''}
Response: ${req.httpResponseModel?.body ?? ''}
</request_context>''';
}
String? _buildTaskPrompt(DashbotRequestContext? ctx, ChatMessageType type) {
if (ctx == null) return null;
final http = ctx.httpRequestModel;
final resp = ctx.httpResponseModel;
String? _buildTaskPrompt(RequestModel? req, ChatMessageType type) {
if (req == null) return null;
final http = req.httpRequestModel;
final resp = req.httpResponseModel;
final prompts = dash.DashbotPrompts();
switch (type) {
case ChatMessageType.explainResponse:
return prompts.explainApiResponsePrompt(
url: http?.url,
method: http?.method.name.toUpperCase(),
responseStatus: ctx.responseStatus,
responseStatus: req.responseStatus,
bodyContentType: http?.bodyContentType.name,
message: resp?.body,
headersMap: http?.headersMap,
@@ -200,7 +206,7 @@ class ChatViewmodel extends StateNotifier<ChatState> {
return prompts.debugApiErrorPrompt(
url: http?.url,
method: http?.method.name.toUpperCase(),
responseStatus: ctx.responseStatus,
responseStatus: req.responseStatus,
bodyContentType: http?.bodyContentType.name,
message: resp?.body,
headersMap: http?.headersMap,

View File

@@ -1,22 +0,0 @@
import 'package:apidash/dashbot/dashbot.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'providers.dart';
/// Derives the DashbotRequestContext from the app's current selection.
final appDashbotRequestContextProvider =
Provider<DashbotRequestContext?>((ref) {
final req = ref.watch(selectedRequestModelProvider);
if (req == null) return null;
return DashbotRequestContext(
apiType: req.apiType,
requestId: req.id,
requestName: req.name,
requestDescription: req.description,
aiRequestModel: req.aiRequestModel,
httpRequestModel: req.httpRequestModel,
responseStatus: req.responseStatus,
responseMessage: req.message,
httpResponseModel: req.httpResponseModel,
);
});

View File

@@ -4,4 +4,3 @@ export 'environment_providers.dart';
export 'history_providers.dart';
export 'settings_providers.dart';
export 'ui_providers.dart';
export 'dashbot_context_provider.dart';

View File

@@ -130,15 +130,7 @@ class Dashboard extends ConsumerWidget {
floatingActionButton: isDashBotEnabled
? FloatingActionButton(
backgroundColor: Theme.of(context).colorScheme.primaryContainer,
onPressed: () => showDashbotWindow(
context,
ref,
overrides: [
dashbotRequestContextProvider.overrideWith(
(ref) => ref.watch(appDashbotRequestContextProvider),
),
],
),
onPressed: () => showDashbotWindow(context, ref),
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 6.0,

View File

@@ -405,13 +405,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.2.0"
dashbot:
dependency: "direct main"
description:
path: "packages/dashbot"
relative: true
source: path
version: "0.0.1"
data_table_2:
dependency: "direct main"
description:
@@ -720,14 +713,6 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
fpdart:
dependency: transitive
description:
name: fpdart
sha256: "1b84ce64453974159f08046f5d05592020d1fcb2099d7fe6ec58da0e7337af77"
url: "https://pub.dev"
source: hosted
version: "1.1.1"
freezed:
dependency: "direct dev"
description:
@@ -1467,14 +1452,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.6.1"
riverpod_annotation:
dependency: transitive
description:
name: riverpod_annotation
sha256: e14b0bf45b71326654e2705d462f21b958f987087be850afd60578fcd502d1b8
url: "https://pub.dev"
source: hosted
version: "2.6.1"
rxdart:
dependency: transitive
description:

View File

@@ -14,8 +14,6 @@ dependencies:
path: packages/apidash_core
apidash_design_system:
path: packages/apidash_design_system
dashbot:
path: packages/dashbot
carousel_slider: ^5.0.0
code_builder: ^4.10.0
csv: ^6.0.0
@@ -105,3 +103,4 @@ flutter:
uses-material-design: true
assets:
- assets/
- assets/dashbot/