mirror of
https://github.com/cogwheel0/conduit.git
synced 2026-10-19 09:43:36 +00:00
Some checks are pending
L10n / l10n (push) Waiting to run
* Fix ten reported issues and vendor Hermes Agent as an API reference Closes #693, #695, #699, #700, #701, #703, #708, #712, #716, #723. - Hermes (#693, #699): load transcripts over REST even when session.resume fails, page at 200 messages and shrink on oversized responses, and show distinct errors for too-large, not-found, and RPC failures. Add the hermes-src submodule (NousResearch/hermes-agent v2026.9.11) as the RPC and REST contract reference. - Server version banner (#695): dismissible, remembered per server and version. - Model selector (#700): overflow fade and selection tint use the card surface. - OpenRouter (#701): skip Batch API-only ":batch" model variants. - Persisted chat content (#703): store plain output text instead of rendered <details> markup when structured output exists, matching the web client. - Direct connections (#708): probes report their real failure instead of a generic message, the Open WebUI logout fence no longer blocks Direct setup, and 401/403 under the api-key header mode hints at Bearer auth. - Reasoning effort (#712): per-server, per-model user picks override the model's configured default and are sent explicitly in the request. - Voice calls (#716): stop the record plugin from clearing the Android communication device and verify the loudspeaker route before reporting it. - Voice mode (#723): finalize speech on the responseDone marker. - Add the five new strings to every locale. * Address review: keep incomplete-clear block, pass effort on sends, keep all dismissals - Direct profiles stay hidden and unusable after an incomplete full app-data clear, in memory and across restarts, until the logout fence is cleared by a completed cleanup or a new authenticated session. Ordinary failed sign-outs still do not block Direct setup. - The primary Open WebUI send path now passes the resolved per-model reasoning effort like regeneration and queued completions already did. - Server version warning dismissals are stored as a bounded set so dismissing one server no longer forgets another. * Address review: release rejected speaker device, speak settled text on responseDone - When Android accepts the built-in speaker but the read-back disagrees, clear that selection before the legacy setSpeakerphoneOn fallback, which is ignored while a communication device stays selected. - Voice mode takes the final text from the message once responseDone is set; the streaming content provider can lag behind the flushed message. * Address review: await the incomplete-clear restart marker before returning The marker is written through the app-data-clear barrier with a checked write and awaited by the clear coordinator, so a process death right after an incomplete clear cannot reload the surviving Direct profiles on restart. * Address review: arm the restart fence before the wipe and propagate marker failures The incomplete-clear marker is now armed before any data is wiped, disarmed only when the clear completes or yields ownership, re-asserted on an incomplete outcome, and a failed checked write aborts the clear instead of being swallowed. The in-memory block is unchanged. * Do not raise an error banner for response.incomplete Open WebUI forwards the provider's `response.incomplete` event over the socket but treats it as a no-op: the output is kept and the turn still ends with a normal `done`. Raising an error on that event flashed "The response stopped before it was complete" under the answer until `done` cleared it. `response.failed` still surfaces the provider error.
74 lines
2.6 KiB
Dart
74 lines
2.6 KiB
Dart
import 'dart:convert';
|
|
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
|
|
import '../../core/persistence/persistence_keys.dart';
|
|
import '../../core/persistence/preferences_store.dart';
|
|
import '../../core/utils/debug_logger.dart';
|
|
|
|
/// Builds the dismissal token for the unsupported-server warning.
|
|
///
|
|
/// The token is scoped to the active server *and* its reported version, so a
|
|
/// dismissed warning reappears only for a different server or after that
|
|
/// server upgrades to another unsupported version.
|
|
String serverVersionWarningToken({
|
|
required String serverId,
|
|
required String? version,
|
|
}) => '$serverId|${version?.trim() ?? ''}';
|
|
|
|
/// Upper bound on remembered dismissals. Oldest entries drop first, so the
|
|
/// preference cannot grow without limit across many servers and upgrades.
|
|
const int kMaxServerVersionWarningDismissals = 32;
|
|
|
|
/// The persisted `<serverId>|<version>` tokens the user dismissed.
|
|
final serverVersionWarningDismissedProvider =
|
|
NotifierProvider<ServerVersionWarningController, Set<String>>(
|
|
ServerVersionWarningController.new,
|
|
);
|
|
|
|
class ServerVersionWarningController extends Notifier<Set<String>> {
|
|
@override
|
|
Set<String> build() => decodeServerVersionWarningDismissals(
|
|
PreferencesStore.getString(PreferenceKeys.serverVersionWarningDismissed),
|
|
);
|
|
|
|
/// Remembers [token] alongside every earlier dismissal, so acknowledging
|
|
/// the warning on one server never re-surfaces it on another.
|
|
Future<void> dismiss(String token) async {
|
|
if (state.contains(token)) return;
|
|
final next = <String>{...state, token};
|
|
while (next.length > kMaxServerVersionWarningDismissals) {
|
|
next.remove(next.first);
|
|
}
|
|
state = next;
|
|
try {
|
|
await PreferencesStore.put(
|
|
PreferenceKeys.serverVersionWarningDismissed,
|
|
jsonEncode(next.toList(growable: false)),
|
|
);
|
|
} catch (error) {
|
|
DebugLogger.warning(
|
|
'Failed to persist server version warning dismissal',
|
|
scope: 'ui/server-version-warning',
|
|
data: {'error': error.toString()},
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Parses the stored dismissal list. A bare token (the first shipped format)
|
|
/// is accepted as a single entry; anything unparseable yields no dismissals.
|
|
Set<String> decodeServerVersionWarningDismissals(String? raw) {
|
|
if (raw == null || raw.isEmpty) return const <String>{};
|
|
if (!raw.startsWith('[')) return {raw};
|
|
try {
|
|
final decoded = jsonDecode(raw);
|
|
if (decoded is! List) return const <String>{};
|
|
return {
|
|
for (final entry in decoded)
|
|
if (entry is String && entry.isNotEmpty) entry,
|
|
};
|
|
} on FormatException {
|
|
return const <String>{};
|
|
}
|
|
}
|