Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 43 additions & 25 deletions lib/core/screens/chat_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1293,24 +1293,30 @@ class _ChatScreenState extends State<ChatScreen> with WidgetsBindingObserver {
}
}

/// Dashboard client used to list models when no Desktop Gateway is
/// configured. Listing needs only `api/model/info` + `api/model/options`
/// over REST — the gateway WebSocket is required solely to push a
/// per-session override, which [_setSessionModel] skips when no gateway is
/// present (the chosen model rides on the chat request instead).
DashboardClient _modelListingClient() => DashboardClient(
host: widget.connection.host,
port: widget.connection.dashboardPort,
pathPrefix: widget.connection.dashboardPrefix ?? '',
proxied: widget.connection.dashboardProxied,
useHttps: widget.connection.useHttps,
username: widget.connection.dashboardUsername,
password: widget.connection.dashboardPassword,
);

Future<void> _showModelSelector() async {
final desktopGateway = _desktopGateway;
if (desktopGateway == null) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text(
'Configure Dashboard credentials to choose a chat model.',
),
),
);
return;
}
final restClient = desktopGateway == null ? _modelListingClient() : null;

setState(() => _loadingModelOptions = true);
try {
final results = await Future.wait([
desktopGateway.getModelInfo(),
desktopGateway.getModelOptions(),
desktopGateway?.getModelInfo() ?? restClient!.getModelInfo(),
desktopGateway?.getModelOptions() ?? restClient!.getModelOptions(),
]);
if (!mounted) return;
final modelInfo = results[0];
Expand All @@ -1325,9 +1331,11 @@ class _ChatScreenState extends State<ChatScreen> with WidgetsBindingObserver {
_sessionReasoningEffort ??
WsClient.normalizeReasoningEffort(modelInfo['reasoning_effort']);
try {
currentEffort = await desktopGateway.getSessionReasoning(
widget.session.id,
);
if (desktopGateway != null) {
currentEffort = await desktopGateway.getSessionReasoning(
widget.session.id,
);
}
} catch (_) {
// Older gateways may not expose session-scoped config.get. The model
// selector remains usable with the profile/default effort.
Expand Down Expand Up @@ -1486,19 +1494,25 @@ class _ChatScreenState extends State<ChatScreen> with WidgetsBindingObserver {

Future<void> _setSessionModel(_ModelSelection selection) async {
final desktopGateway = _desktopGateway;
if (desktopGateway == null || _changingModel) return;
if (_changingModel) return;
final choice = selection.choice;
setState(() => _changingModel = true);
try {
await desktopGateway.setSessionModel(
sessionId: widget.session.id,
provider: choice.provider,
model: choice.model,
);
await desktopGateway.setSessionReasoning(
sessionId: widget.session.id,
effort: selection.reasoningEffort,
);
// Without a gateway there is no session-scoped RPC to push the override
// to, so the selection stays local and is sent as the `model` field on
// each chat request instead. Reasoning effort is gateway-only and is
// simply not applied in that mode.
if (desktopGateway != null) {
await desktopGateway.setSessionModel(
sessionId: widget.session.id,
provider: choice.provider,
model: choice.model,
);
await desktopGateway.setSessionReasoning(
sessionId: widget.session.id,
effort: selection.reasoningEffort,
);
}
final store = await _chatModelStore;
await store.save(
connectionIdentity: _chatModelConnectionIdentity,
Expand Down Expand Up @@ -1621,6 +1635,10 @@ class _ChatScreenState extends State<ChatScreen> with WidgetsBindingObserver {
await _gateway.sendMessageStreaming(
message: text,
sessionId: widget.session.id,
// Carry the per-chat override on the request. The API server resolves
// `model` per call, so this reproduces session-scoped model selection
// without needing the gateway WebSocket to hold session state.
model: _sessionModelOverride ? _sessionModel : null,
history: history,
imageDataUrl: imageDataUrl,
onToken: (token) {
Expand Down
Loading