mirror of
https://github.com/foss42/apidash.git
synced 2025-11-30 17:59:18 +08:00
refactor: remove unused Dashbot context and request provider; update chat viewmodel to use the existing request model
This commit is contained in:
@@ -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),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
@@ -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,
|
||||
);
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
});
|
||||
@@ -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';
|
||||
|
||||
@@ -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,
|
||||
|
||||
23
pubspec.lock
23
pubspec.lock
@@ -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:
|
||||
|
||||
@@ -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/
|
||||
|
||||
Reference in New Issue
Block a user