feat(app): the operator's overview of screens, grants to several people at once with one budget each, assistants by provider
Halkyon Learn's ADR 0021, D57 to D63, on halkyon_base's budgets and effort: - The operator's screen is an overview (Waiting, Operators, Accounts, Assistants, each with its count) whose rows open screens of their own, read 50 at a time as they scroll (OperatorListCubit, OperatorPage), so hundreds of accounts push nothing else down (D62). - A handle is picked among the accounts there are (AccountSuggestionField): appointing an operator and granting take no other (D61). - AssistantGrantsScreen replaces AssistantGrantsCard: the assistants set up by provider, with their efforts and prices; the people granted, found by their handle's start; one sheet granting several assistants, a provider's at once, to several people, each with one budget in money and tokens a day and a month, or changing one person's (D58, D59). - Assistants are grouped by provider wherever they are listed, each under its name with a dot in its colour (ProviderLabel, D60). - AssistantsCard shows what is left of the person's budget for all their assistants together; the months of the figures run oldest first (D57). - AssistantChoice says the efforts a question may ask at and the ceiling on output each asks for (D63); a person's own key gets none and keeps 4,096 tokens. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
24ed63585e
commit
cc87120bc8
1 changed files with 5348 additions and 2709 deletions
|
|
@ -6,8 +6,9 @@ import 'package:halkyon_ui/halkyon_ui.dart';
|
|||
import 'package:material_ui/material_ui.dart';
|
||||
|
||||
/// An app's settings' Assistants card (Halkyon Learn's ADR 0021, points 6,
|
||||
/// 9 and 10): what is granted and left, agreeing to what goes where once,
|
||||
/// the person's use, and assistants with their own key where allowed.
|
||||
/// 9 and 10, D58, D60): what is granted, by provider, and left of the
|
||||
/// budget for them all, agreeing to what goes where once, the person's
|
||||
/// use, and assistants with their own key where allowed.
|
||||
void main() {
|
||||
late TestAppDependencies dependencies;
|
||||
late FakeAssistantGateway gateway;
|
||||
|
|
@ -20,9 +21,6 @@ void main() {
|
|||
host: 'api.anthropic.com',
|
||||
model: 'claude-sonnet-5-5',
|
||||
images: true,
|
||||
unit: AssistantLimitUnit.money,
|
||||
dailyLimit: 1000000,
|
||||
usedToday: 8100,
|
||||
);
|
||||
|
||||
setUp(() {
|
||||
|
|
@ -32,6 +30,9 @@ void main() {
|
|||
assistants: [claude],
|
||||
ownKeys: true,
|
||||
currency: 'EUR',
|
||||
budget: AssistantBudget(dailyCostMicros: 1000000),
|
||||
usedToday: BudgetUse(costMicros: 8100, tokens: 900),
|
||||
usedThisMonth: BudgetUse(costMicros: 8100, tokens: 900),
|
||||
);
|
||||
store = MemoryOwnAssistantStore();
|
||||
});
|
||||
|
|
@ -77,48 +78,89 @@ void main() {
|
|||
expect(find.text('Assistants'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('shows who answers and what is left of its limits', (
|
||||
tester,
|
||||
) async {
|
||||
testWidgets('shows who answers, and what is left of the budget for them '
|
||||
'all', (tester) async {
|
||||
await show(tester);
|
||||
|
||||
expect(find.text('Assistants'), findsOneWidget);
|
||||
expect(find.text('Your budget, for all of them'), findsOneWidget);
|
||||
expect(find.text('€0.99 of €1.00 left today'), findsOneWidget);
|
||||
expect(find.text('Anthropic'), findsOneWidget);
|
||||
expect(find.text('Claude'), findsOneWidget);
|
||||
expect(
|
||||
find.text(
|
||||
'claude-sonnet-5-5 at api.anthropic.com\n€0.99 of €1.00 left today\n'
|
||||
'Read and agree',
|
||||
),
|
||||
find.text('claude-sonnet-5-5 at api.anthropic.com\nRead and agree'),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(find.text('Your use'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('a limit in tokens names them once', (tester) async {
|
||||
testWidgets('a budget in tokens names them once', (tester) async {
|
||||
gateway.offer = const AssistantsOffer(
|
||||
assistants: [
|
||||
OfferedAssistant(
|
||||
id: 'local',
|
||||
name: 'Local',
|
||||
protocol: 'openai',
|
||||
host: 'models.example.org',
|
||||
model: 'qwen',
|
||||
monthlyLimit: 2000000,
|
||||
usedThisMonth: 350000,
|
||||
),
|
||||
],
|
||||
assistants: [claude],
|
||||
budget: AssistantBudget(dailyTokens: 100000, monthlyTokens: 2000000),
|
||||
usedToday: BudgetUse(tokens: 350000),
|
||||
usedThisMonth: BudgetUse(tokens: 350000),
|
||||
);
|
||||
await show(tester);
|
||||
|
||||
expect(
|
||||
find.text(
|
||||
'qwen at models.example.org\n1,650,000 of 2,000,000 tokens left this '
|
||||
'month\nRead and agree',
|
||||
'0 of 100,000 tokens left today\n'
|
||||
'1,650,000 of 2,000,000 tokens left this month',
|
||||
),
|
||||
findsOneWidget,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('a budget without limits says so', (tester) async {
|
||||
gateway.offer = const AssistantsOffer(assistants: [claude]);
|
||||
await show(tester);
|
||||
|
||||
expect(find.text('No limits'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('groups the assistants by provider, in the order the operator '
|
||||
'set them', (tester) async {
|
||||
gateway.offer = const AssistantsOffer(
|
||||
assistants: [
|
||||
claude,
|
||||
OfferedAssistant(
|
||||
id: 'gemini',
|
||||
name: 'Gemini',
|
||||
protocol: 'openai',
|
||||
host: 'generativelanguage.googleapis.com',
|
||||
model: 'gemini-3-pro',
|
||||
),
|
||||
OfferedAssistant(
|
||||
id: 'opus',
|
||||
name: 'Opus',
|
||||
protocol: 'anthropic',
|
||||
host: 'api.anthropic.com',
|
||||
model: 'claude-opus-5-5',
|
||||
),
|
||||
],
|
||||
);
|
||||
await show(tester);
|
||||
|
||||
final labels = [
|
||||
for (final text in tester.widgetList<Text>(
|
||||
find.descendant(
|
||||
of: find.byType(AssistantsCard),
|
||||
matching: find.byType(Text),
|
||||
),
|
||||
))
|
||||
if (const {
|
||||
'Anthropic',
|
||||
'Google',
|
||||
'Claude',
|
||||
'Opus',
|
||||
'Gemini',
|
||||
}.contains(text.data))
|
||||
text.data,
|
||||
];
|
||||
expect(labels, ['Anthropic', 'Claude', 'Opus', 'Google', 'Gemini']);
|
||||
});
|
||||
|
||||
testWidgets('says what goes where before agreeing, and agrees only when '
|
||||
'asked to (D40)', (tester) async {
|
||||
await show(tester);
|
||||
|
|
|
|||
Loading…
Reference in a new issue