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.
109 lines
3.4 KiB
Dart
109 lines
3.4 KiB
Dart
import 'package:checks/checks.dart';
|
|
import 'package:conduit/core/models/model.dart';
|
|
import 'package:conduit/l10n/app_localizations.dart';
|
|
import 'package:conduit/l10n/conduit_localizations.dart';
|
|
import 'package:conduit/shared/theme/app_theme.dart';
|
|
import 'package:conduit/shared/theme/tweakcn_themes.dart';
|
|
import 'package:conduit/shared/theme/theme_extensions.dart';
|
|
import 'package:conduit/shared/widgets/conduit_components.dart';
|
|
import 'package:conduit/shared/widgets/horizontal_overflow_fade.dart';
|
|
import 'package:conduit/shared/widgets/model_list_tile.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:material_ui/material_ui.dart';
|
|
|
|
const _reasoningModel = Model(
|
|
id: 'reasoning-model',
|
|
name: 'Reasoning model',
|
|
supportedParameters: ['reasoning'],
|
|
);
|
|
|
|
Future<ConduitThemeExtension> _pumpTile(
|
|
WidgetTester tester, {
|
|
required bool isSelected,
|
|
}) async {
|
|
late ConduitThemeExtension theme;
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
theme: AppTheme.light(TweakcnThemes.conduit),
|
|
localizationsDelegates: conduitLocalizationsDelegates,
|
|
supportedLocales: AppLocalizations.supportedLocales,
|
|
home: Scaffold(
|
|
body: Builder(
|
|
builder: (context) {
|
|
theme = context.conduitTheme;
|
|
return ConduitCard(
|
|
child: ModelListTile(
|
|
model: _reasoningModel,
|
|
isSelected: isSelected,
|
|
onTap: () {},
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
return theme;
|
|
}
|
|
|
|
List<Color> _fadeColors(WidgetTester tester) {
|
|
final fade = find.byType(HorizontalOverflowFade);
|
|
final gradient = tester
|
|
.widgetList<DecoratedBox>(
|
|
find.descendant(of: fade, matching: find.byType(DecoratedBox)),
|
|
)
|
|
.map((box) => box.decoration)
|
|
.whereType<BoxDecoration>()
|
|
.map((decoration) => decoration.gradient)
|
|
.whereType<LinearGradient>()
|
|
.single;
|
|
return gradient.colors;
|
|
}
|
|
|
|
void main() {
|
|
testWidgets('unselected overflow fade derives from the card background', (
|
|
tester,
|
|
) async {
|
|
final theme = await _pumpTile(tester, isSelected: false);
|
|
final card = theme.cardBackground;
|
|
|
|
check(card).not((it) => it.equals(theme.surfaceBackground));
|
|
|
|
final colors = _fadeColors(tester);
|
|
check(colors)
|
|
.deepEquals([card.withValues(alpha: 0), card.withValues(alpha: 0.9)]);
|
|
});
|
|
|
|
testWidgets('selected overflow fade matches the highlighted row surface', (
|
|
tester,
|
|
) async {
|
|
final theme = await _pumpTile(tester, isSelected: true);
|
|
final highlighted = Color.alphaBlend(
|
|
theme.buttonPrimary.withValues(alpha: 0.1),
|
|
theme.cardBackground,
|
|
);
|
|
final surfaceHighlighted = Color.alphaBlend(
|
|
theme.buttonPrimary.withValues(alpha: 0.1),
|
|
theme.surfaceBackground,
|
|
);
|
|
|
|
check(highlighted).not((it) => it.equals(surfaceHighlighted));
|
|
|
|
final colors = _fadeColors(tester);
|
|
check(colors).deepEquals([
|
|
highlighted.withValues(alpha: 0),
|
|
highlighted.withValues(alpha: 0.9),
|
|
]);
|
|
|
|
// The highlight itself paints against the card surface.
|
|
final tile = find.byType(ModelListTile);
|
|
final containers = tester.widgetList<Container>(
|
|
find.descendant(of: tile, matching: find.byType(Container)),
|
|
);
|
|
final rowBackgrounds = containers
|
|
.map((container) => container.decoration)
|
|
.whereType<BoxDecoration>()
|
|
.map((decoration) => decoration.color);
|
|
check(rowBackgrounds).contains(highlighted);
|
|
});
|
|
}
|