feat(assistant)!: no hand-off and no connector; the field searches the web (ADR 0022)
The developer decided on 23 Oct to drop the hand-off to an AI assistant outside the app, and Claude's connector with it, in v0.19.0, the app and the server together: build 22 raises minimumAppBuild anyway, so no app calling the connector's endpoints uses the instance after it. An instance is to have one way to an assistant, its operator's (ADR 0021, proposed). The app: the field at the foot of Home, and the button in the Library, on a studyset and while studying, search the web alone, through the engine chosen in Settings or Android's own search, with the parts of a card the person picks; a studyset's name and description are never offered. Gone: the assistants a question opened in (AiAssistant, AssistantHandOff), the switch between them and the web, Settings' choice of assistant, Connect Claude with its route, cubit, repository and gateway, and their texts. A settings file from before keeps the old keys until the next change of a setting writes it anew; nothing reads them. The server: lib/src/assistant/ (MCP, its OAuth, the five tools, connect codes and tokens), AssistantEndpoint, the daily forgetting of the connector's secrets (its scheduled call cancelled at start), its lines in the deletion hook, instance.info().assistantConnectorUrl (nullable, so an older app reads it as none), and Caddy's forwarding of /mcp, /oauth/* and /.well-known/oauth-*. The migration drops the connector's five tables, children first: DROP TABLE "assistant_token" CASCADE; -- tokens of a connection DROP TABLE "assistant_grant" CASCADE; -- OAuth codes of a connection DROP TABLE "assistant_connection" CASCADE; -- a person's connected client DROP TABLE "assistant_connect_code" CASCADE; DROP TABLE "assistant_client" CASCADE; -- OAuth clients registered No other table references them, so CASCADE drops only their own foreign keys. For the minute between the maintenance run and the switch, the old server answers the connector with errors and fails to delete an account (its hook still deletes from these tables); ADR 0022 says so. BREAKING CHANGE: a connected Claude gets no answer from the deploy on, and the app no longer opens a question in an AI assistant. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
1454fd7bfc
commit
ca24eb9cf9
90 changed files with 16550 additions and 10700 deletions
File diff suppressed because one or more lines are too long
|
|
@ -117,11 +117,7 @@ The sync tests need a running server (`serverpod start`); run them in
|
|||
`deploy/`, at the repository's root, holds a Docker Compose setup: Caddy (HTTPS), the
|
||||
server and PostgreSQL. Copy `deploy/.env.example` to `deploy/.env` and fill it in; it
|
||||
explains every value. The licences require offering the source of what the instance runs
|
||||
to the people who use it. Assistants such as Claude read people's studysets through the
|
||||
instance's MCP connector at `/mcp`
|
||||
([ADR 0006](../../docs/adr/0006-assistant-connector.md)): `webServer` in
|
||||
`apps/learn/halkyon_learn_server/config/production.yaml` names its public address, as
|
||||
`apiServer` does for the app.
|
||||
to the people who use it.
|
||||
|
||||
learn.halkyon.eu itself deploys from `main` once CI is green: `deploy/redeploy.sh`, run by
|
||||
the CI runner described in `deploy/runner/`, deploys only commits signed by a key in
|
||||
|
|
|
|||
|
|
@ -1,96 +0,0 @@
|
|||
/* AUTOMATICALLY GENERATED CODE DO NOT MODIFY */
|
||||
/* To generate run: "serverpod generate" */
|
||||
|
||||
// ignore_for_file: implementation_imports
|
||||
// ignore_for_file: library_private_types_in_public_api
|
||||
// ignore_for_file: non_constant_identifier_names
|
||||
// ignore_for_file: public_member_api_docs
|
||||
// ignore_for_file: type_literal_in_constant_pattern
|
||||
// ignore_for_file: use_super_parameters
|
||||
// ignore_for_file: invalid_use_of_internal_member
|
||||
|
||||
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||
import 'package:serverpod_client/serverpod_client.dart' as _isc;
|
||||
|
||||
/// A code for connecting an assistant, as the app shows it (ADR 0006, rule 3).
|
||||
abstract class AssistantCode
|
||||
implements _isc.SerializableModel, _isc.ProtocolSerialization {
|
||||
AssistantCode._({
|
||||
required this.code,
|
||||
required this.expiresAt,
|
||||
});
|
||||
|
||||
factory AssistantCode({
|
||||
required String code,
|
||||
required DateTime expiresAt,
|
||||
}) = _AssistantCodeImpl;
|
||||
|
||||
factory AssistantCode.fromJson(Map<String, dynamic> jsonSerialization) {
|
||||
return AssistantCode(
|
||||
code: jsonSerialization['code'] as String,
|
||||
expiresAt: _isc.DateTimeJsonExtension.fromJson(
|
||||
jsonSerialization['expiresAt'],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// The code, as `K7QX-M2PD`.
|
||||
String code;
|
||||
|
||||
/// When it stops working, in UTC.
|
||||
DateTime expiresAt;
|
||||
|
||||
/// Returns a shallow copy of this [AssistantCode]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_isc.useResult
|
||||
AssistantCode copyWith({
|
||||
String? code,
|
||||
DateTime? expiresAt,
|
||||
});
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'__className__': 'AssistantCode',
|
||||
'code': code,
|
||||
'expiresAt': expiresAt.toJson(),
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJsonForProtocol() {
|
||||
return {
|
||||
'__className__': 'AssistantCode',
|
||||
'code': code,
|
||||
'expiresAt': expiresAt.toJson(),
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return _isc.SerializationManager.encode(this);
|
||||
}
|
||||
}
|
||||
|
||||
class _AssistantCodeImpl extends AssistantCode {
|
||||
_AssistantCodeImpl({
|
||||
required String code,
|
||||
required DateTime expiresAt,
|
||||
}) : super._(
|
||||
code: code,
|
||||
expiresAt: expiresAt,
|
||||
);
|
||||
|
||||
/// Returns a shallow copy of this [AssistantCode]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_isc.useResult
|
||||
@override
|
||||
AssistantCode copyWith({
|
||||
String? code,
|
||||
DateTime? expiresAt,
|
||||
}) {
|
||||
return AssistantCode(
|
||||
code: code ?? this.code,
|
||||
expiresAt: expiresAt ?? this.expiresAt,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,143 +0,0 @@
|
|||
/* AUTOMATICALLY GENERATED CODE DO NOT MODIFY */
|
||||
/* To generate run: "serverpod generate" */
|
||||
|
||||
// ignore_for_file: implementation_imports
|
||||
// ignore_for_file: library_private_types_in_public_api
|
||||
// ignore_for_file: non_constant_identifier_names
|
||||
// ignore_for_file: public_member_api_docs
|
||||
// ignore_for_file: type_literal_in_constant_pattern
|
||||
// ignore_for_file: use_super_parameters
|
||||
// ignore_for_file: invalid_use_of_internal_member
|
||||
|
||||
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||
import 'package:serverpod_client/serverpod_client.dart' as _isc;
|
||||
|
||||
/// A connected assistant, as the app lists it.
|
||||
abstract class AssistantConnectionInfo
|
||||
implements _isc.SerializableModel, _isc.ProtocolSerialization {
|
||||
AssistantConnectionInfo._({
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.host,
|
||||
required this.connectedAt,
|
||||
this.lastUsedAt,
|
||||
});
|
||||
|
||||
factory AssistantConnectionInfo({
|
||||
required int id,
|
||||
required String name,
|
||||
required String host,
|
||||
required DateTime connectedAt,
|
||||
DateTime? lastUsedAt,
|
||||
}) = _AssistantConnectionInfoImpl;
|
||||
|
||||
factory AssistantConnectionInfo.fromJson(
|
||||
Map<String, dynamic> jsonSerialization,
|
||||
) {
|
||||
return AssistantConnectionInfo(
|
||||
id: jsonSerialization['id'] as int,
|
||||
name: jsonSerialization['name'] as String,
|
||||
host: jsonSerialization['host'] as String,
|
||||
connectedAt: _isc.DateTimeJsonExtension.fromJson(
|
||||
jsonSerialization['connectedAt'],
|
||||
),
|
||||
lastUsedAt: jsonSerialization['lastUsedAt'] == null
|
||||
? null
|
||||
: _isc.DateTimeJsonExtension.fromJson(
|
||||
jsonSerialization['lastUsedAt'],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// The connection's id, to disconnect it.
|
||||
int id;
|
||||
|
||||
/// What the assistant calls itself.
|
||||
String name;
|
||||
|
||||
/// The host the person was sent back to on connecting, e.g. `claude.ai`.
|
||||
String host;
|
||||
|
||||
/// When the person connected it.
|
||||
DateTime connectedAt;
|
||||
|
||||
/// When it last read something; null before.
|
||||
DateTime? lastUsedAt;
|
||||
|
||||
/// Returns a shallow copy of this [AssistantConnectionInfo]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_isc.useResult
|
||||
AssistantConnectionInfo copyWith({
|
||||
int? id,
|
||||
String? name,
|
||||
String? host,
|
||||
DateTime? connectedAt,
|
||||
DateTime? lastUsedAt,
|
||||
});
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'__className__': 'AssistantConnectionInfo',
|
||||
'id': id,
|
||||
'name': name,
|
||||
'host': host,
|
||||
'connectedAt': connectedAt.toJson(),
|
||||
if (lastUsedAt != null) 'lastUsedAt': lastUsedAt?.toJson(),
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJsonForProtocol() {
|
||||
return {
|
||||
'__className__': 'AssistantConnectionInfo',
|
||||
'id': id,
|
||||
'name': name,
|
||||
'host': host,
|
||||
'connectedAt': connectedAt.toJson(),
|
||||
if (lastUsedAt != null) 'lastUsedAt': lastUsedAt?.toJson(),
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return _isc.SerializationManager.encode(this);
|
||||
}
|
||||
}
|
||||
|
||||
class _Undefined {}
|
||||
|
||||
class _AssistantConnectionInfoImpl extends AssistantConnectionInfo {
|
||||
_AssistantConnectionInfoImpl({
|
||||
required int id,
|
||||
required String name,
|
||||
required String host,
|
||||
required DateTime connectedAt,
|
||||
DateTime? lastUsedAt,
|
||||
}) : super._(
|
||||
id: id,
|
||||
name: name,
|
||||
host: host,
|
||||
connectedAt: connectedAt,
|
||||
lastUsedAt: lastUsedAt,
|
||||
);
|
||||
|
||||
/// Returns a shallow copy of this [AssistantConnectionInfo]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_isc.useResult
|
||||
@override
|
||||
AssistantConnectionInfo copyWith({
|
||||
int? id,
|
||||
String? name,
|
||||
String? host,
|
||||
DateTime? connectedAt,
|
||||
Object? lastUsedAt = _Undefined,
|
||||
}) {
|
||||
return AssistantConnectionInfo(
|
||||
id: id ?? this.id,
|
||||
name: name ?? this.name,
|
||||
host: host ?? this.host,
|
||||
connectedAt: connectedAt ?? this.connectedAt,
|
||||
lastUsedAt: lastUsedAt is DateTime? ? lastUsedAt : this.lastUsedAt,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -15,10 +15,6 @@ import 'dart:typed_data' as _idt;
|
|||
import 'package:halkyon_base_client/halkyon_base_client.dart' as _ihk4p5t7;
|
||||
import 'package:halkyon_learn_client/src/protocol/asset/file_usage.dart'
|
||||
as _iimptq0q;
|
||||
import 'package:halkyon_learn_client/src/protocol/assistant/assistant_code.dart'
|
||||
as _itx3eecd;
|
||||
import 'package:halkyon_learn_client/src/protocol/assistant/assistant_connection_info.dart'
|
||||
as _iplrdti2;
|
||||
import 'package:halkyon_learn_client/src/protocol/auth/device.dart'
|
||||
as _ieqh4yr9;
|
||||
import 'package:halkyon_learn_client/src/protocol/dictionary/device_dictionary_file.dart'
|
||||
|
|
@ -140,43 +136,6 @@ class EndpointAsset extends _isc.EndpointRef {
|
|||
);
|
||||
}
|
||||
|
||||
/// Connecting assistants to the signed-in person's account (ADR 0006): a
|
||||
/// code to type on an assistant's authorization page, and the assistants
|
||||
/// connected, to disconnect. Anonymous people too: the code is how they
|
||||
/// connect without signing in anywhere.
|
||||
/// {@category Endpoint}
|
||||
class EndpointAssistant extends _isc.EndpointRef {
|
||||
EndpointAssistant(_isc.EndpointCaller caller) : super(caller);
|
||||
|
||||
@override
|
||||
String get name => 'assistant';
|
||||
|
||||
/// A new code for connecting an assistant, valid for ten minutes and
|
||||
/// once; the person's earlier codes stop working. Only its hash is kept.
|
||||
_ida.Future<_itx3eecd.AssistantCode> connectCode() =>
|
||||
caller.callServerEndpoint<_itx3eecd.AssistantCode>(
|
||||
'assistant',
|
||||
'connectCode',
|
||||
{},
|
||||
);
|
||||
|
||||
/// The assistants connected to the person's account, the newest first.
|
||||
_ida.Future<List<_iplrdti2.AssistantConnectionInfo>> connections() =>
|
||||
caller.callServerEndpoint<List<_iplrdti2.AssistantConnectionInfo>>(
|
||||
'assistant',
|
||||
'connections',
|
||||
{},
|
||||
);
|
||||
|
||||
/// Disconnects the person's assistant [id]: its tokens stop working at
|
||||
/// once. Someone else's connection is one that doesn't exist.
|
||||
_ida.Future<void> disconnect(int id) => caller.callServerEndpoint<void>(
|
||||
'assistant',
|
||||
'disconnect',
|
||||
{'id': id},
|
||||
);
|
||||
}
|
||||
|
||||
/// Your devices: the sessions you're signed in with, and signing one of them
|
||||
/// out, for example a lost phone.
|
||||
///
|
||||
|
|
@ -801,7 +760,9 @@ class EndpointInbox extends _isc.EndpointRef {
|
|||
|
||||
/// What this instance offers. Public: the app asks before anyone is signed in.
|
||||
/// Released apps read the oldest build the server works with here, so it
|
||||
/// keeps every field it had: halkyon_base's own basics fill those it shares.
|
||||
/// keeps every field they need: halkyon_base's own basics fill those it
|
||||
/// shares. A nullable one may go, as an older app reads it as none (the
|
||||
/// connector's address, ADR 0022).
|
||||
/// {@category Endpoint}
|
||||
class EndpointInstance extends _isc.EndpointRef {
|
||||
EndpointInstance(_isc.EndpointCaller caller) : super(caller);
|
||||
|
|
@ -812,7 +773,7 @@ class EndpointInstance extends _isc.EndpointRef {
|
|||
/// This instance's offer: GitHub sign-in only with the operator's OAuth
|
||||
/// app, email sign-in only where it can mail codes, where its source is,
|
||||
/// the oldest app build it works with, the domain its people's handles
|
||||
/// are on, where assistants connect, the Jellyfin server it suggests, and
|
||||
/// are on, the Jellyfin server it suggests, and
|
||||
/// where it keeps people's data and its backups.
|
||||
_ida.Future<_i26u8vl4.InstanceInfo> info() =>
|
||||
caller.callServerEndpoint<_i26u8vl4.InstanceInfo>(
|
||||
|
|
@ -944,7 +905,6 @@ class Client extends _isc.ServerpodClientShared {
|
|||
httpClientOverride: httpClientOverride,
|
||||
) {
|
||||
asset = EndpointAsset(this);
|
||||
assistant = EndpointAssistant(this);
|
||||
devices = EndpointDevices(this);
|
||||
dictionary = EndpointDictionary(this);
|
||||
discussions = EndpointDiscussions(this);
|
||||
|
|
@ -960,8 +920,6 @@ class Client extends _isc.ServerpodClientShared {
|
|||
|
||||
late final EndpointAsset asset;
|
||||
|
||||
late final EndpointAssistant assistant;
|
||||
|
||||
late final EndpointDevices devices;
|
||||
|
||||
late final EndpointDictionary dictionary;
|
||||
|
|
@ -987,7 +945,6 @@ class Client extends _isc.ServerpodClientShared {
|
|||
@override
|
||||
Map<String, _isc.EndpointRef> get endpointRefLookup => {
|
||||
'asset': asset,
|
||||
'assistant': assistant,
|
||||
'devices': devices,
|
||||
'dictionary': dictionary,
|
||||
'discussions': discussions,
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ abstract class InstanceInfo
|
|||
required this.sourceCodeUrl,
|
||||
this.minimumAppBuild,
|
||||
this.accountDomain,
|
||||
this.assistantConnectorUrl,
|
||||
this.jellyfinUrl,
|
||||
this.emailSignIn,
|
||||
this.dataLocation,
|
||||
|
|
@ -33,7 +32,6 @@ abstract class InstanceInfo
|
|||
required String sourceCodeUrl,
|
||||
int? minimumAppBuild,
|
||||
String? accountDomain,
|
||||
String? assistantConnectorUrl,
|
||||
String? jellyfinUrl,
|
||||
bool? emailSignIn,
|
||||
String? dataLocation,
|
||||
|
|
@ -46,8 +44,6 @@ abstract class InstanceInfo
|
|||
sourceCodeUrl: jsonSerialization['sourceCodeUrl'] as String,
|
||||
minimumAppBuild: jsonSerialization['minimumAppBuild'] as int?,
|
||||
accountDomain: jsonSerialization['accountDomain'] as String?,
|
||||
assistantConnectorUrl:
|
||||
jsonSerialization['assistantConnectorUrl'] as String?,
|
||||
jellyfinUrl: jsonSerialization['jellyfinUrl'] as String?,
|
||||
emailSignIn: jsonSerialization['emailSignIn'] == null
|
||||
? null
|
||||
|
|
@ -75,11 +71,6 @@ abstract class InstanceInfo
|
|||
/// it.
|
||||
String? accountDomain;
|
||||
|
||||
/// Where assistants such as Claude connect to read people's studysets (ADR
|
||||
/// 0006): the MCP endpoint, as `https://learn.halkyon.eu/mcp`. Null where
|
||||
/// the instance offers none, and from servers that predate it.
|
||||
String? assistantConnectorUrl;
|
||||
|
||||
/// The Jellyfin server this instance suggests to its people, which the
|
||||
/// app offers to sign in to (decided 2026-10-13): the operator's
|
||||
/// JELLYFIN_URL, else jellyfin. on the domain registered for the
|
||||
|
|
@ -109,7 +100,6 @@ abstract class InstanceInfo
|
|||
String? sourceCodeUrl,
|
||||
int? minimumAppBuild,
|
||||
String? accountDomain,
|
||||
String? assistantConnectorUrl,
|
||||
String? jellyfinUrl,
|
||||
bool? emailSignIn,
|
||||
String? dataLocation,
|
||||
|
|
@ -123,8 +113,6 @@ abstract class InstanceInfo
|
|||
'sourceCodeUrl': sourceCodeUrl,
|
||||
if (minimumAppBuild != null) 'minimumAppBuild': minimumAppBuild,
|
||||
if (accountDomain != null) 'accountDomain': accountDomain,
|
||||
if (assistantConnectorUrl != null)
|
||||
'assistantConnectorUrl': assistantConnectorUrl,
|
||||
if (jellyfinUrl != null) 'jellyfinUrl': jellyfinUrl,
|
||||
if (emailSignIn != null) 'emailSignIn': emailSignIn,
|
||||
if (dataLocation != null) 'dataLocation': dataLocation,
|
||||
|
|
@ -140,8 +128,6 @@ abstract class InstanceInfo
|
|||
'sourceCodeUrl': sourceCodeUrl,
|
||||
if (minimumAppBuild != null) 'minimumAppBuild': minimumAppBuild,
|
||||
if (accountDomain != null) 'accountDomain': accountDomain,
|
||||
if (assistantConnectorUrl != null)
|
||||
'assistantConnectorUrl': assistantConnectorUrl,
|
||||
if (jellyfinUrl != null) 'jellyfinUrl': jellyfinUrl,
|
||||
if (emailSignIn != null) 'emailSignIn': emailSignIn,
|
||||
if (dataLocation != null) 'dataLocation': dataLocation,
|
||||
|
|
@ -163,7 +149,6 @@ class _InstanceInfoImpl extends InstanceInfo {
|
|||
required String sourceCodeUrl,
|
||||
int? minimumAppBuild,
|
||||
String? accountDomain,
|
||||
String? assistantConnectorUrl,
|
||||
String? jellyfinUrl,
|
||||
bool? emailSignIn,
|
||||
String? dataLocation,
|
||||
|
|
@ -173,7 +158,6 @@ class _InstanceInfoImpl extends InstanceInfo {
|
|||
sourceCodeUrl: sourceCodeUrl,
|
||||
minimumAppBuild: minimumAppBuild,
|
||||
accountDomain: accountDomain,
|
||||
assistantConnectorUrl: assistantConnectorUrl,
|
||||
jellyfinUrl: jellyfinUrl,
|
||||
emailSignIn: emailSignIn,
|
||||
dataLocation: dataLocation,
|
||||
|
|
@ -189,7 +173,6 @@ class _InstanceInfoImpl extends InstanceInfo {
|
|||
String? sourceCodeUrl,
|
||||
Object? minimumAppBuild = _Undefined,
|
||||
Object? accountDomain = _Undefined,
|
||||
Object? assistantConnectorUrl = _Undefined,
|
||||
Object? jellyfinUrl = _Undefined,
|
||||
Object? emailSignIn = _Undefined,
|
||||
Object? dataLocation = _Undefined,
|
||||
|
|
@ -206,9 +189,6 @@ class _InstanceInfoImpl extends InstanceInfo {
|
|||
accountDomain: accountDomain is String?
|
||||
? accountDomain
|
||||
: this.accountDomain,
|
||||
assistantConnectorUrl: assistantConnectorUrl is String?
|
||||
? assistantConnectorUrl
|
||||
: this.assistantConnectorUrl,
|
||||
jellyfinUrl: jellyfinUrl is String? ? jellyfinUrl : this.jellyfinUrl,
|
||||
emailSignIn: emailSignIn is bool? ? emailSignIn : this.emailSignIn,
|
||||
dataLocation: dataLocation is String? ? dataLocation : this.dataLocation,
|
||||
|
|
|
|||
|
|
@ -12,8 +12,6 @@
|
|||
|
||||
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||
import 'package:halkyon_base_client/halkyon_base_client.dart' as _ihk4p5t7;
|
||||
import 'package:halkyon_learn_client/src/protocol/assistant/assistant_connection_info.dart'
|
||||
as _iplrdti2;
|
||||
import 'package:halkyon_learn_client/src/protocol/auth/device.dart'
|
||||
as _ieqh4yr9;
|
||||
import 'package:halkyon_learn_client/src/protocol/dictionary/device_dictionary_file.dart'
|
||||
|
|
@ -44,8 +42,6 @@ import 'asset/asset.dart' as _ipb7vkxr;
|
|||
import 'asset/file_allowance.dart' as _i9uv0f1n;
|
||||
import 'asset/file_limit_exceeded_exception.dart' as _it5d94hq;
|
||||
import 'asset/file_usage.dart' as _iurwo9k8;
|
||||
import 'assistant/assistant_code.dart' as _i8xi9jqo;
|
||||
import 'assistant/assistant_connection_info.dart' as _icukzjbz;
|
||||
import 'auth/device.dart' as _i6c5ny7n;
|
||||
import 'card/card.dart' as _ipiw1cmx;
|
||||
import 'card/card_revision.dart' as _ipbyuef1;
|
||||
|
|
@ -101,8 +97,6 @@ export 'asset/asset.dart';
|
|||
export 'asset/file_allowance.dart';
|
||||
export 'asset/file_limit_exceeded_exception.dart';
|
||||
export 'asset/file_usage.dart';
|
||||
export 'assistant/assistant_code.dart';
|
||||
export 'assistant/assistant_connection_info.dart';
|
||||
export 'auth/device.dart';
|
||||
export 'card/card.dart';
|
||||
export 'card/card_revision.dart';
|
||||
|
|
@ -2409,12 +2403,6 @@ class Protocol extends _isd.DatabaseSerializationManager {
|
|||
if (t == _iurwo9k8.FileUsage) {
|
||||
return _iurwo9k8.FileUsage.fromJson(data) as T;
|
||||
}
|
||||
if (t == _i8xi9jqo.AssistantCode) {
|
||||
return _i8xi9jqo.AssistantCode.fromJson(data) as T;
|
||||
}
|
||||
if (t == _icukzjbz.AssistantConnectionInfo) {
|
||||
return _icukzjbz.AssistantConnectionInfo.fromJson(data) as T;
|
||||
}
|
||||
if (t == _i6c5ny7n.Device) {
|
||||
return _i6c5ny7n.Device.fromJson(data) as T;
|
||||
}
|
||||
|
|
@ -2584,16 +2572,6 @@ class Protocol extends _isd.DatabaseSerializationManager {
|
|||
if (t == _isc.getType<_iurwo9k8.FileUsage?>()) {
|
||||
return (data != null ? _iurwo9k8.FileUsage.fromJson(data) : null) as T;
|
||||
}
|
||||
if (t == _isc.getType<_i8xi9jqo.AssistantCode?>()) {
|
||||
return (data != null ? _i8xi9jqo.AssistantCode.fromJson(data) : null)
|
||||
as T;
|
||||
}
|
||||
if (t == _isc.getType<_icukzjbz.AssistantConnectionInfo?>()) {
|
||||
return (data != null
|
||||
? _icukzjbz.AssistantConnectionInfo.fromJson(data)
|
||||
: null)
|
||||
as T;
|
||||
}
|
||||
if (t == _isc.getType<_i6c5ny7n.Device?>()) {
|
||||
return (data != null ? _i6c5ny7n.Device.fromJson(data) : null) as T;
|
||||
}
|
||||
|
|
@ -2824,12 +2802,6 @@ class Protocol extends _isd.DatabaseSerializationManager {
|
|||
.toList()
|
||||
as T;
|
||||
}
|
||||
if (t == List<_iplrdti2.AssistantConnectionInfo>) {
|
||||
return (data as List)
|
||||
.map((e) => deserialize<_iplrdti2.AssistantConnectionInfo>(e))
|
||||
.toList()
|
||||
as T;
|
||||
}
|
||||
if (t == List<_ieqh4yr9.Device>) {
|
||||
return (data as List)
|
||||
.map((e) => deserialize<_ieqh4yr9.Device>(e))
|
||||
|
|
@ -2927,8 +2899,6 @@ class Protocol extends _isd.DatabaseSerializationManager {
|
|||
_i9uv0f1n.FileAllowance => 'FileAllowance',
|
||||
_it5d94hq.FileLimitExceededException => 'FileLimitExceededException',
|
||||
_iurwo9k8.FileUsage => 'FileUsage',
|
||||
_i8xi9jqo.AssistantCode => 'AssistantCode',
|
||||
_icukzjbz.AssistantConnectionInfo => 'AssistantConnectionInfo',
|
||||
_i6c5ny7n.Device => 'Device',
|
||||
_ipiw1cmx.Card => 'Card',
|
||||
_ipbyuef1.CardRevision => 'CardRevision',
|
||||
|
|
@ -3005,10 +2975,6 @@ class Protocol extends _isd.DatabaseSerializationManager {
|
|||
return 'FileLimitExceededException';
|
||||
case _iurwo9k8.FileUsage():
|
||||
return 'FileUsage';
|
||||
case _i8xi9jqo.AssistantCode():
|
||||
return 'AssistantCode';
|
||||
case _icukzjbz.AssistantConnectionInfo():
|
||||
return 'AssistantConnectionInfo';
|
||||
case _i6c5ny7n.Device():
|
||||
return 'Device';
|
||||
case _ipiw1cmx.Card():
|
||||
|
|
@ -3155,12 +3121,6 @@ class Protocol extends _isd.DatabaseSerializationManager {
|
|||
if (dataClassName == 'FileUsage') {
|
||||
return deserialize<_iurwo9k8.FileUsage>(data['data']);
|
||||
}
|
||||
if (dataClassName == 'AssistantCode') {
|
||||
return deserialize<_i8xi9jqo.AssistantCode>(data['data']);
|
||||
}
|
||||
if (dataClassName == 'AssistantConnectionInfo') {
|
||||
return deserialize<_icukzjbz.AssistantConnectionInfo>(data['data']);
|
||||
}
|
||||
if (dataClassName == 'Device') {
|
||||
return deserialize<_i6c5ny7n.Device>(data['data']);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1817,55 +1817,23 @@
|
|||
"feedbackNeedsIdentity": "Melde dich an, um im Original zu melden und vorzuschlagen.",
|
||||
"kanjiExampleWords": "Wörter damit",
|
||||
|
||||
"settingsAssistant": "Assistent",
|
||||
"settingsAskAssistant": "Einen KI-Assistenten fragen oder im Web suchen",
|
||||
"settingsAskAssistantSubtitle": "Aus, bis du es einschaltest. Fügt ein Feld auf der Startseite und einen Knopf in der Bibliothek, bei einem Lernset und beim Lernen hinzu. Eine Frage öffnet sich in dem KI-Assistenten, den du wählst, oder als Websuche, nur mit dem Zusammenhang, den du wählst; von hier aus wird nichts gesendet.",
|
||||
"assistantHint": "Frag {assistant}…",
|
||||
"assistantHintStudyset": "Frag {assistant} zu {title}…",
|
||||
"assistantHintCard": "Frag {assistant} zu dieser Karte…",
|
||||
"assistantSend": "In {assistant} fragen",
|
||||
"assistantOpen": "Fragen oder suchen",
|
||||
"settingsAssistant": "Websuche",
|
||||
"settingsAskAssistant": "Aus der App im Web suchen",
|
||||
"settingsAskAssistantSubtitle": "Aus, bis du es einschaltest. Fügt ein Feld auf der Startseite und einen Knopf in der Bibliothek, bei einem Lernset und beim Lernen hinzu. Eine Frage öffnet sich als Websuche, nur mit den Teilen einer Karteikarte, die du wählst; von hier aus wird nichts gesendet.",
|
||||
"assistantOpen": "Im Web suchen",
|
||||
"assistantClose": "Schließen",
|
||||
"assistantOpenFailed": "{assistant} ließ sich nicht öffnen.",
|
||||
|
||||
"assistantConnectTitle": "Claude verbinden",
|
||||
"assistantConnectSubtitle": "Claude deine Lernsets lesen lassen",
|
||||
"assistantConnectIntro": "Claude kann über den Konnektor von Halkyon Learn deine Lernsets, Karten, Notizen und deinen Lernstand auf {instance} lesen und dazu antworten. Ändern kann es nichts. Was es liest, geht an Anthropic, unter deren Datenschutzerklärung.",
|
||||
"assistantStepAdd": "Öffne in Claude Einstellungen › Konnektoren und füge einen eigenen Konnektor namens Halkyon Learn mit dieser Adresse hinzu. Einmal hinzugefügt, gilt er auch in den Apps von Claude.",
|
||||
"assistantStepCode": "Claude öffnet dann eine Seite, die nach einem Code fragt. Hol ihn dir hier: Er gilt einmal, zehn Minuten lang.",
|
||||
"assistantShowCode": "Code anzeigen",
|
||||
"assistantNewCode": "Neuer Code",
|
||||
"assistantCodeValid": "Gültig bis {time}",
|
||||
"assistantCopy": "Kopieren",
|
||||
"assistantCopied": "Kopiert",
|
||||
"assistantConnected": "Verbunden",
|
||||
"assistantNoneConnected": "Noch nichts verbunden.",
|
||||
"assistantConnectedOn": "verbunden am {date}",
|
||||
"assistantLastRead": "zuletzt gelesen am {date}",
|
||||
"assistantDisconnect": "Trennen",
|
||||
"assistantDisconnectTitle": "{name} trennen?",
|
||||
"assistantDisconnectMessage": "Es kann deine Lernsets nicht mehr lesen. Mit einem neuen Code kannst du es wieder verbinden.",
|
||||
"assistantNotOffered": "Diese Instanz bietet noch keinen Konnektor an. Fragen öffnen sich trotzdem in dem KI-Assistenten, den du gewählt hast, mit dem, worum es geht.",
|
||||
"assistantUnreachable": "Die Instanz ist nicht erreichbar. Versuch es noch einmal, sobald du online bist.",
|
||||
"assistantRetry": "Erneut versuchen",
|
||||
|
||||
"assistantLineFront": "Vorderseite: {text}",
|
||||
"assistantLineBack": "Rückseite: {text}",
|
||||
"assistantLineHint": "Hinweis {number}: {text}",
|
||||
"assistantLineExplanation": "Erklärung: {text}",
|
||||
"assistantLineStudyset": "Lernset: „{title}“ in Halkyon Learn auf {instance}",
|
||||
"assistantLineDescription": "Beschreibung: {text}",
|
||||
"assistantChipStudyset": "Lernset: {title}",
|
||||
"assistantContextLabel": "Mitsenden",
|
||||
"assistantTargetWeb": "Web",
|
||||
"assistantSwitchToWeb": "Stattdessen im Web suchen",
|
||||
"assistantSwitchToAssistant": "Stattdessen {assistant} fragen",
|
||||
"assistantHintWeb": "Im Web suchen…",
|
||||
"assistantSendWeb": "Suchen",
|
||||
"assistantSearchFailed": "Die Suche ließ sich nicht öffnen.",
|
||||
"settingsSearchEngine": "Suchmaschine",
|
||||
"settingsAiAssistant": "KI-Assistent",
|
||||
"aiAssistantCustomHint": "https://example.org/chat?q=%s",
|
||||
"searchEngineDevice": "Suche des Geräts",
|
||||
"searchEngineCustom": "Eigene Adresse",
|
||||
"searchEngineCustomHint": "https://example.org/search?q=%s",
|
||||
|
|
@ -2071,7 +2039,7 @@
|
|||
"studysetPrivateTitle": "„{title}“ privat machen?",
|
||||
"studysetPrivateBody": "Alles darin wird auf deinen Geräten Ende-zu-Ende-verschlüsselt: Die Instanz kann es weder lesen noch veröffentlichen noch Claude zeigen. Kopien in den Sicherungen der Instanz bleiben lesbar, bis diese ablaufen.",
|
||||
"studysetReadableTitle": "„{title}“ nicht mehr privat?",
|
||||
"studysetReadableBody": "Alles darin wird entschlüsselt und neu synchronisiert, sodass die Instanz es lesen kann: Du kannst es veröffentlichen, und Claude kann es über den Connector lesen.",
|
||||
"studysetReadableBody": "Alles darin wird entschlüsselt und neu synchronisiert, sodass die Instanz es lesen kann: Du kannst es veröffentlichen.",
|
||||
"studysetPrivateNoKeys": "Melde dich auf diesem Gerät mit deinem Passwort an, um Lernsets privat zu machen.",
|
||||
"studysetPrivateSomeLocked": "Manches darin ließ sich auf diesem Gerät nicht öffnen und blieb, wie es war.",
|
||||
"publishTagsSent": "Die Namen der Tags, die mitgehen und die die Instanz erfährt: {names}",
|
||||
|
|
@ -2111,7 +2079,7 @@
|
|||
"studysetPrivacyPrivateTitle": "„{title}“ ist privat",
|
||||
"studysetPrivacyPrivateBody": "Alles darin ist Ende-zu-Ende-verschlüsselt: Nur deine Geräte können es lesen. Die Instanz kann es weder veröffentlichen noch Claude zeigen. Nicht mehr privat, wird es entschlüsselt und neu synchronisiert, sodass die Instanz es lesen kann.",
|
||||
"studysetPrivacyReadableTitle": "„{title}“ ist nicht privat",
|
||||
"studysetPrivacyReadableBody": "Die Instanz kann es lesen: Du kannst es veröffentlichen, und Claude kann es über den Connector lesen. Privat wird es auf deinen Geräten Ende-zu-Ende-verschlüsselt; Kopien in den Sicherungen der Instanz bleiben lesbar, bis diese ablaufen.",
|
||||
"studysetPrivacyReadableBody": "Die Instanz kann es lesen: Du kannst es veröffentlichen. Privat wird es auf deinen Geräten Ende-zu-Ende-verschlüsselt; Kopien in den Sicherungen der Instanz bleiben lesbar, bis diese ablaufen.",
|
||||
"studysetPrivacyTopic": "Seine Privatsphäre ist die des Lernsets, in dem es liegt.",
|
||||
"studysetPrivacyPublish": "Veröffentlichen …"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2125,47 +2125,12 @@
|
|||
"feedbackNeedsIdentity": "Sign in to report to the author and suggest.",
|
||||
"kanjiExampleWords": "Words with it",
|
||||
|
||||
"settingsAssistant": "Assistant",
|
||||
"settingsAskAssistant": "Ask an AI assistant or search the web",
|
||||
"settingsAskAssistantSubtitle": "Off unless you turn it on. Adds a field on Home and a button in the Library, on a studyset and while studying. A question opens in the AI assistant you choose or as a web search, with only the context you choose; nothing is sent from here.",
|
||||
"assistantHint": "Ask {assistant}…",
|
||||
"@assistantHint": {"placeholders": {"assistant": {"type": "String"}}},
|
||||
"assistantHintStudyset": "Ask {assistant} about {title}…",
|
||||
"@assistantHintStudyset": {"placeholders": {"assistant": {"type": "String"}, "title": {"type": "String"}}},
|
||||
"assistantHintCard": "Ask {assistant} about this card…",
|
||||
"@assistantHintCard": {"placeholders": {"assistant": {"type": "String"}}},
|
||||
"assistantSend": "Ask in {assistant}",
|
||||
"@assistantSend": {"placeholders": {"assistant": {"type": "String"}}},
|
||||
"assistantOpen": "Ask or search",
|
||||
"settingsAssistant": "Web search",
|
||||
"settingsAskAssistant": "Search the web from the app",
|
||||
"settingsAskAssistantSubtitle": "Off unless you turn it on. Adds a field on Home and a button in the Library, on a studyset and while studying. A question opens as a web search, with only the parts of a card you choose; nothing is sent from here.",
|
||||
"assistantOpen": "Search the web",
|
||||
"assistantClose": "Close",
|
||||
"assistantOpenFailed": "{assistant} couldn't be opened.",
|
||||
"@assistantOpenFailed": {"placeholders": {"assistant": {"type": "String"}}},
|
||||
|
||||
"assistantConnectTitle": "Connect Claude",
|
||||
"assistantConnectSubtitle": "Let Claude read your studysets",
|
||||
"assistantConnectIntro": "Claude can read your studysets, cards, notes and progress on {instance} through Halkyon Learn's connector, and answer about them. It can't change anything. What it reads goes to Anthropic, under their privacy policy.",
|
||||
"@assistantConnectIntro": {"placeholders": {"instance": {"type": "String"}}},
|
||||
"assistantStepAdd": "In Claude, open Settings › Connectors and add a custom connector named Halkyon Learn, with this address. Added once, it works in Claude's apps too.",
|
||||
"assistantStepCode": "Claude then opens a page asking for a code. Get one here: it works once, for ten minutes.",
|
||||
"assistantShowCode": "Show a code",
|
||||
"assistantNewCode": "New code",
|
||||
"assistantCodeValid": "Valid until {time}",
|
||||
"@assistantCodeValid": {"placeholders": {"time": {"type": "String"}}},
|
||||
"assistantCopy": "Copy",
|
||||
"assistantCopied": "Copied",
|
||||
"assistantConnected": "Connected",
|
||||
"assistantNoneConnected": "Nothing connected yet.",
|
||||
"assistantConnectedOn": "connected {date}",
|
||||
"@assistantConnectedOn": {"placeholders": {"date": {"type": "String"}}},
|
||||
"assistantLastRead": "last read {date}",
|
||||
"@assistantLastRead": {"placeholders": {"date": {"type": "String"}}},
|
||||
"assistantDisconnect": "Disconnect",
|
||||
"assistantDisconnectTitle": "Disconnect {name}?",
|
||||
"@assistantDisconnectTitle": {"placeholders": {"name": {"type": "String"}}},
|
||||
"assistantDisconnectMessage": "It can't read your studysets any more. You can connect it again with a new code.",
|
||||
"assistantNotOffered": "This instance doesn't offer a connector yet. Questions still open in the AI assistant you chose, with what they are about.",
|
||||
"assistantUnreachable": "The instance can't be reached. Try again once you are online.",
|
||||
"assistantRetry": "Try again",
|
||||
|
||||
"assistantLineFront": "Front: {text}",
|
||||
"@assistantLineFront": {"description": "A line of context handed to the AI assistant after the question.", "placeholders": {"text": {"type": "String"}}},
|
||||
|
|
@ -2175,23 +2140,14 @@
|
|||
"@assistantLineHint": {"placeholders": {"number": {"type": "int"}, "text": {"type": "String"}}},
|
||||
"assistantLineExplanation": "Explanation: {text}",
|
||||
"@assistantLineExplanation": {"placeholders": {"text": {"type": "String"}}},
|
||||
"assistantLineStudyset": "Studyset: “{title}” in Halkyon Learn on {instance}",
|
||||
"@assistantLineStudyset": {"placeholders": {"title": {"type": "String"}, "instance": {"type": "String"}}},
|
||||
"assistantLineDescription": "Description: {text}",
|
||||
"@assistantLineDescription": {"placeholders": {"text": {"type": "String"}}},
|
||||
"assistantChipStudyset": "Studyset: {title}",
|
||||
"@assistantChipStudyset": {"placeholders": {"title": {"type": "String"}}},
|
||||
"assistantContextLabel": "Send along",
|
||||
"assistantTargetWeb": "Web",
|
||||
"assistantSwitchToWeb": "Search the web instead",
|
||||
"assistantSwitchToAssistant": "Ask {assistant} instead",
|
||||
"@assistantSwitchToAssistant": {"placeholders": {"assistant": {"type": "String"}}},
|
||||
"assistantHintWeb": "Search the web…",
|
||||
"assistantSendWeb": "Search",
|
||||
"assistantSearchFailed": "The search couldn't be opened.",
|
||||
"settingsSearchEngine": "Search engine",
|
||||
"settingsAiAssistant": "AI assistant",
|
||||
"aiAssistantCustomHint": "https://example.org/chat?q=%s",
|
||||
"searchEngineDevice": "The device's search",
|
||||
"searchEngineCustom": "Your own address",
|
||||
"searchEngineCustomHint": "https://example.org/search?q=%s",
|
||||
|
|
@ -2437,7 +2393,7 @@
|
|||
"studysetPrivateBody": "Everything in it is end-to-end encrypted on your devices: the instance can't read it, publish it or show it to Claude. Copies in the instance's backups stay readable until those expire.",
|
||||
"studysetReadableTitle": "Make “{title}” non-private?",
|
||||
"@studysetReadableTitle": {"placeholders": {"title": {"type": "String"}}},
|
||||
"studysetReadableBody": "Everything in it is decrypted and synced again, so the instance can read it: you can publish it, and Claude can read it through the connector.",
|
||||
"studysetReadableBody": "Everything in it is decrypted and synced again, so the instance can read it: you can publish it.",
|
||||
"studysetPrivateNoKeys": "Sign in with your password on this device to make studysets private.",
|
||||
"studysetPrivateSomeLocked": "Some of it couldn't be opened on this device and stayed as it was.",
|
||||
"publishTagsSent": "The names of the tags that go with it, which the instance learns: {names}",
|
||||
|
|
@ -2479,7 +2435,7 @@
|
|||
"studysetPrivacyPrivateBody": "Everything in it is end-to-end encrypted: only your devices can read it. The instance can't publish it, nor show it to Claude. Making it non-private decrypts it and syncs it again, so the instance can read it.",
|
||||
"studysetPrivacyReadableTitle": "“{title}” isn't private",
|
||||
"@studysetPrivacyReadableTitle": {"placeholders": {"title": {"type": "String"}}},
|
||||
"studysetPrivacyReadableBody": "The instance can read it: you can publish it, and Claude can read it through the connector. Making it private encrypts it end to end on your devices; copies in the instance's backups stay readable until those expire.",
|
||||
"studysetPrivacyReadableBody": "The instance can read it: you can publish it. Making it private encrypts it end to end on your devices; copies in the instance's backups stay readable until those expire.",
|
||||
"studysetPrivacyTopic": "Its privacy is that of the studyset it's in.",
|
||||
"studysetPrivacyPublish": "Publish…"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10740,49 +10740,25 @@ abstract class AppLocalizations {
|
|||
/// No description provided for @settingsAssistant.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Assistant'**
|
||||
/// **'Web search'**
|
||||
String get settingsAssistant;
|
||||
|
||||
/// No description provided for @settingsAskAssistant.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Ask an AI assistant or search the web'**
|
||||
/// **'Search the web from the app'**
|
||||
String get settingsAskAssistant;
|
||||
|
||||
/// No description provided for @settingsAskAssistantSubtitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Off unless you turn it on. Adds a field on Home and a button in the Library, on a studyset and while studying. A question opens in the AI assistant you choose or as a web search, with only the context you choose; nothing is sent from here.'**
|
||||
/// **'Off unless you turn it on. Adds a field on Home and a button in the Library, on a studyset and while studying. A question opens as a web search, with only the parts of a card you choose; nothing is sent from here.'**
|
||||
String get settingsAskAssistantSubtitle;
|
||||
|
||||
/// No description provided for @assistantHint.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Ask {assistant}…'**
|
||||
String assistantHint(String assistant);
|
||||
|
||||
/// No description provided for @assistantHintStudyset.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Ask {assistant} about {title}…'**
|
||||
String assistantHintStudyset(String assistant, String title);
|
||||
|
||||
/// No description provided for @assistantHintCard.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Ask {assistant} about this card…'**
|
||||
String assistantHintCard(String assistant);
|
||||
|
||||
/// No description provided for @assistantSend.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Ask in {assistant}'**
|
||||
String assistantSend(String assistant);
|
||||
|
||||
/// No description provided for @assistantOpen.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Ask or search'**
|
||||
/// **'Search the web'**
|
||||
String get assistantOpen;
|
||||
|
||||
/// No description provided for @assistantClose.
|
||||
|
|
@ -10791,132 +10767,6 @@ abstract class AppLocalizations {
|
|||
/// **'Close'**
|
||||
String get assistantClose;
|
||||
|
||||
/// No description provided for @assistantOpenFailed.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'{assistant} couldn\'t be opened.'**
|
||||
String assistantOpenFailed(String assistant);
|
||||
|
||||
/// No description provided for @assistantConnectTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Connect Claude'**
|
||||
String get assistantConnectTitle;
|
||||
|
||||
/// No description provided for @assistantConnectSubtitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Let Claude read your studysets'**
|
||||
String get assistantConnectSubtitle;
|
||||
|
||||
/// No description provided for @assistantConnectIntro.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Claude can read your studysets, cards, notes and progress on {instance} through Halkyon Learn\'s connector, and answer about them. It can\'t change anything. What it reads goes to Anthropic, under their privacy policy.'**
|
||||
String assistantConnectIntro(String instance);
|
||||
|
||||
/// No description provided for @assistantStepAdd.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'In Claude, open Settings › Connectors and add a custom connector named Halkyon Learn, with this address. Added once, it works in Claude\'s apps too.'**
|
||||
String get assistantStepAdd;
|
||||
|
||||
/// No description provided for @assistantStepCode.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Claude then opens a page asking for a code. Get one here: it works once, for ten minutes.'**
|
||||
String get assistantStepCode;
|
||||
|
||||
/// No description provided for @assistantShowCode.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Show a code'**
|
||||
String get assistantShowCode;
|
||||
|
||||
/// No description provided for @assistantNewCode.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'New code'**
|
||||
String get assistantNewCode;
|
||||
|
||||
/// No description provided for @assistantCodeValid.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Valid until {time}'**
|
||||
String assistantCodeValid(String time);
|
||||
|
||||
/// No description provided for @assistantCopy.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Copy'**
|
||||
String get assistantCopy;
|
||||
|
||||
/// No description provided for @assistantCopied.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Copied'**
|
||||
String get assistantCopied;
|
||||
|
||||
/// No description provided for @assistantConnected.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Connected'**
|
||||
String get assistantConnected;
|
||||
|
||||
/// No description provided for @assistantNoneConnected.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Nothing connected yet.'**
|
||||
String get assistantNoneConnected;
|
||||
|
||||
/// No description provided for @assistantConnectedOn.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'connected {date}'**
|
||||
String assistantConnectedOn(String date);
|
||||
|
||||
/// No description provided for @assistantLastRead.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'last read {date}'**
|
||||
String assistantLastRead(String date);
|
||||
|
||||
/// No description provided for @assistantDisconnect.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Disconnect'**
|
||||
String get assistantDisconnect;
|
||||
|
||||
/// No description provided for @assistantDisconnectTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Disconnect {name}?'**
|
||||
String assistantDisconnectTitle(String name);
|
||||
|
||||
/// No description provided for @assistantDisconnectMessage.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'It can\'t read your studysets any more. You can connect it again with a new code.'**
|
||||
String get assistantDisconnectMessage;
|
||||
|
||||
/// No description provided for @assistantNotOffered.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'This instance doesn\'t offer a connector yet. Questions still open in the AI assistant you chose, with what they are about.'**
|
||||
String get assistantNotOffered;
|
||||
|
||||
/// No description provided for @assistantUnreachable.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'The instance can\'t be reached. Try again once you are online.'**
|
||||
String get assistantUnreachable;
|
||||
|
||||
/// No description provided for @assistantRetry.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Try again'**
|
||||
String get assistantRetry;
|
||||
|
||||
/// A line of context handed to the AI assistant after the question.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
|
@ -10941,12 +10791,6 @@ abstract class AppLocalizations {
|
|||
/// **'Explanation: {text}'**
|
||||
String assistantLineExplanation(String text);
|
||||
|
||||
/// No description provided for @assistantLineStudyset.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Studyset: “{title}” in Halkyon Learn on {instance}'**
|
||||
String assistantLineStudyset(String title, String instance);
|
||||
|
||||
/// No description provided for @assistantLineDescription.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
|
@ -10959,30 +10803,6 @@ abstract class AppLocalizations {
|
|||
/// **'Studyset: {title}'**
|
||||
String assistantChipStudyset(String title);
|
||||
|
||||
/// No description provided for @assistantContextLabel.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Send along'**
|
||||
String get assistantContextLabel;
|
||||
|
||||
/// No description provided for @assistantTargetWeb.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Web'**
|
||||
String get assistantTargetWeb;
|
||||
|
||||
/// No description provided for @assistantSwitchToWeb.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Search the web instead'**
|
||||
String get assistantSwitchToWeb;
|
||||
|
||||
/// No description provided for @assistantSwitchToAssistant.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Ask {assistant} instead'**
|
||||
String assistantSwitchToAssistant(String assistant);
|
||||
|
||||
/// No description provided for @assistantHintWeb.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
|
@ -11007,18 +10827,6 @@ abstract class AppLocalizations {
|
|||
/// **'Search engine'**
|
||||
String get settingsSearchEngine;
|
||||
|
||||
/// No description provided for @settingsAiAssistant.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'AI assistant'**
|
||||
String get settingsAiAssistant;
|
||||
|
||||
/// No description provided for @aiAssistantCustomHint.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'https://example.org/chat?q=%s'**
|
||||
String get aiAssistantCustomHint;
|
||||
|
||||
/// No description provided for @searchEngineDevice.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
|
@ -12216,7 +12024,7 @@ abstract class AppLocalizations {
|
|||
/// No description provided for @studysetReadableBody.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Everything in it is decrypted and synced again, so the instance can read it: you can publish it, and Claude can read it through the connector.'**
|
||||
/// **'Everything in it is decrypted and synced again, so the instance can read it: you can publish it.'**
|
||||
String get studysetReadableBody;
|
||||
|
||||
/// No description provided for @studysetPrivateNoKeys.
|
||||
|
|
@ -12438,7 +12246,7 @@ abstract class AppLocalizations {
|
|||
/// No description provided for @studysetPrivacyReadableBody.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'The instance can read it: you can publish it, and Claude can read it through the connector. Making it private encrypts it end to end on your devices; copies in the instance\'s backups stay readable until those expire.'**
|
||||
/// **'The instance can read it: you can publish it. Making it private encrypts it end to end on your devices; copies in the instance\'s backups stay readable until those expire.'**
|
||||
String get studysetPrivacyReadableBody;
|
||||
|
||||
/// No description provided for @studysetPrivacyTopic.
|
||||
|
|
|
|||
|
|
@ -6718,122 +6718,21 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
String get kanjiExampleWords => 'Wörter damit';
|
||||
|
||||
@override
|
||||
String get settingsAssistant => 'Assistent';
|
||||
String get settingsAssistant => 'Websuche';
|
||||
|
||||
@override
|
||||
String get settingsAskAssistant =>
|
||||
'Einen KI-Assistenten fragen oder im Web suchen';
|
||||
String get settingsAskAssistant => 'Aus der App im Web suchen';
|
||||
|
||||
@override
|
||||
String get settingsAskAssistantSubtitle =>
|
||||
'Aus, bis du es einschaltest. Fügt ein Feld auf der Startseite und einen Knopf in der Bibliothek, bei einem Lernset und beim Lernen hinzu. Eine Frage öffnet sich in dem KI-Assistenten, den du wählst, oder als Websuche, nur mit dem Zusammenhang, den du wählst; von hier aus wird nichts gesendet.';
|
||||
'Aus, bis du es einschaltest. Fügt ein Feld auf der Startseite und einen Knopf in der Bibliothek, bei einem Lernset und beim Lernen hinzu. Eine Frage öffnet sich als Websuche, nur mit den Teilen einer Karteikarte, die du wählst; von hier aus wird nichts gesendet.';
|
||||
|
||||
@override
|
||||
String assistantHint(String assistant) {
|
||||
return 'Frag $assistant…';
|
||||
}
|
||||
|
||||
@override
|
||||
String assistantHintStudyset(String assistant, String title) {
|
||||
return 'Frag $assistant zu $title…';
|
||||
}
|
||||
|
||||
@override
|
||||
String assistantHintCard(String assistant) {
|
||||
return 'Frag $assistant zu dieser Karte…';
|
||||
}
|
||||
|
||||
@override
|
||||
String assistantSend(String assistant) {
|
||||
return 'In $assistant fragen';
|
||||
}
|
||||
|
||||
@override
|
||||
String get assistantOpen => 'Fragen oder suchen';
|
||||
String get assistantOpen => 'Im Web suchen';
|
||||
|
||||
@override
|
||||
String get assistantClose => 'Schließen';
|
||||
|
||||
@override
|
||||
String assistantOpenFailed(String assistant) {
|
||||
return '$assistant ließ sich nicht öffnen.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get assistantConnectTitle => 'Claude verbinden';
|
||||
|
||||
@override
|
||||
String get assistantConnectSubtitle => 'Claude deine Lernsets lesen lassen';
|
||||
|
||||
@override
|
||||
String assistantConnectIntro(String instance) {
|
||||
return 'Claude kann über den Konnektor von Halkyon Learn deine Lernsets, Karten, Notizen und deinen Lernstand auf $instance lesen und dazu antworten. Ändern kann es nichts. Was es liest, geht an Anthropic, unter deren Datenschutzerklärung.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get assistantStepAdd =>
|
||||
'Öffne in Claude Einstellungen › Konnektoren und füge einen eigenen Konnektor namens Halkyon Learn mit dieser Adresse hinzu. Einmal hinzugefügt, gilt er auch in den Apps von Claude.';
|
||||
|
||||
@override
|
||||
String get assistantStepCode =>
|
||||
'Claude öffnet dann eine Seite, die nach einem Code fragt. Hol ihn dir hier: Er gilt einmal, zehn Minuten lang.';
|
||||
|
||||
@override
|
||||
String get assistantShowCode => 'Code anzeigen';
|
||||
|
||||
@override
|
||||
String get assistantNewCode => 'Neuer Code';
|
||||
|
||||
@override
|
||||
String assistantCodeValid(String time) {
|
||||
return 'Gültig bis $time';
|
||||
}
|
||||
|
||||
@override
|
||||
String get assistantCopy => 'Kopieren';
|
||||
|
||||
@override
|
||||
String get assistantCopied => 'Kopiert';
|
||||
|
||||
@override
|
||||
String get assistantConnected => 'Verbunden';
|
||||
|
||||
@override
|
||||
String get assistantNoneConnected => 'Noch nichts verbunden.';
|
||||
|
||||
@override
|
||||
String assistantConnectedOn(String date) {
|
||||
return 'verbunden am $date';
|
||||
}
|
||||
|
||||
@override
|
||||
String assistantLastRead(String date) {
|
||||
return 'zuletzt gelesen am $date';
|
||||
}
|
||||
|
||||
@override
|
||||
String get assistantDisconnect => 'Trennen';
|
||||
|
||||
@override
|
||||
String assistantDisconnectTitle(String name) {
|
||||
return '$name trennen?';
|
||||
}
|
||||
|
||||
@override
|
||||
String get assistantDisconnectMessage =>
|
||||
'Es kann deine Lernsets nicht mehr lesen. Mit einem neuen Code kannst du es wieder verbinden.';
|
||||
|
||||
@override
|
||||
String get assistantNotOffered =>
|
||||
'Diese Instanz bietet noch keinen Konnektor an. Fragen öffnen sich trotzdem in dem KI-Assistenten, den du gewählt hast, mit dem, worum es geht.';
|
||||
|
||||
@override
|
||||
String get assistantUnreachable =>
|
||||
'Die Instanz ist nicht erreichbar. Versuch es noch einmal, sobald du online bist.';
|
||||
|
||||
@override
|
||||
String get assistantRetry => 'Erneut versuchen';
|
||||
|
||||
@override
|
||||
String assistantLineFront(String text) {
|
||||
return 'Vorderseite: $text';
|
||||
|
|
@ -6854,11 +6753,6 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
return 'Erklärung: $text';
|
||||
}
|
||||
|
||||
@override
|
||||
String assistantLineStudyset(String title, String instance) {
|
||||
return 'Lernset: „$title“ in Halkyon Learn auf $instance';
|
||||
}
|
||||
|
||||
@override
|
||||
String assistantLineDescription(String text) {
|
||||
return 'Beschreibung: $text';
|
||||
|
|
@ -6869,20 +6763,6 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
return 'Lernset: $title';
|
||||
}
|
||||
|
||||
@override
|
||||
String get assistantContextLabel => 'Mitsenden';
|
||||
|
||||
@override
|
||||
String get assistantTargetWeb => 'Web';
|
||||
|
||||
@override
|
||||
String get assistantSwitchToWeb => 'Stattdessen im Web suchen';
|
||||
|
||||
@override
|
||||
String assistantSwitchToAssistant(String assistant) {
|
||||
return 'Stattdessen $assistant fragen';
|
||||
}
|
||||
|
||||
@override
|
||||
String get assistantHintWeb => 'Im Web suchen…';
|
||||
|
||||
|
|
@ -6895,12 +6775,6 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
@override
|
||||
String get settingsSearchEngine => 'Suchmaschine';
|
||||
|
||||
@override
|
||||
String get settingsAiAssistant => 'KI-Assistent';
|
||||
|
||||
@override
|
||||
String get aiAssistantCustomHint => 'https://example.org/chat?q=%s';
|
||||
|
||||
@override
|
||||
String get searchEngineDevice => 'Suche des Geräts';
|
||||
|
||||
|
|
@ -7745,7 +7619,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
|
||||
@override
|
||||
String get studysetReadableBody =>
|
||||
'Alles darin wird entschlüsselt und neu synchronisiert, sodass die Instanz es lesen kann: Du kannst es veröffentlichen, und Claude kann es über den Connector lesen.';
|
||||
'Alles darin wird entschlüsselt und neu synchronisiert, sodass die Instanz es lesen kann: Du kannst es veröffentlichen.';
|
||||
|
||||
@override
|
||||
String get studysetPrivateNoKeys =>
|
||||
|
|
@ -7877,7 +7751,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
|
||||
@override
|
||||
String get studysetPrivacyReadableBody =>
|
||||
'Die Instanz kann es lesen: Du kannst es veröffentlichen, und Claude kann es über den Connector lesen. Privat wird es auf deinen Geräten Ende-zu-Ende-verschlüsselt; Kopien in den Sicherungen der Instanz bleiben lesbar, bis diese ablaufen.';
|
||||
'Die Instanz kann es lesen: Du kannst es veröffentlichen. Privat wird es auf deinen Geräten Ende-zu-Ende-verschlüsselt; Kopien in den Sicherungen der Instanz bleiben lesbar, bis diese ablaufen.';
|
||||
|
||||
@override
|
||||
String get studysetPrivacyTopic =>
|
||||
|
|
|
|||
|
|
@ -6655,121 +6655,21 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
String get kanjiExampleWords => 'Words with it';
|
||||
|
||||
@override
|
||||
String get settingsAssistant => 'Assistant';
|
||||
String get settingsAssistant => 'Web search';
|
||||
|
||||
@override
|
||||
String get settingsAskAssistant => 'Ask an AI assistant or search the web';
|
||||
String get settingsAskAssistant => 'Search the web from the app';
|
||||
|
||||
@override
|
||||
String get settingsAskAssistantSubtitle =>
|
||||
'Off unless you turn it on. Adds a field on Home and a button in the Library, on a studyset and while studying. A question opens in the AI assistant you choose or as a web search, with only the context you choose; nothing is sent from here.';
|
||||
'Off unless you turn it on. Adds a field on Home and a button in the Library, on a studyset and while studying. A question opens as a web search, with only the parts of a card you choose; nothing is sent from here.';
|
||||
|
||||
@override
|
||||
String assistantHint(String assistant) {
|
||||
return 'Ask $assistant…';
|
||||
}
|
||||
|
||||
@override
|
||||
String assistantHintStudyset(String assistant, String title) {
|
||||
return 'Ask $assistant about $title…';
|
||||
}
|
||||
|
||||
@override
|
||||
String assistantHintCard(String assistant) {
|
||||
return 'Ask $assistant about this card…';
|
||||
}
|
||||
|
||||
@override
|
||||
String assistantSend(String assistant) {
|
||||
return 'Ask in $assistant';
|
||||
}
|
||||
|
||||
@override
|
||||
String get assistantOpen => 'Ask or search';
|
||||
String get assistantOpen => 'Search the web';
|
||||
|
||||
@override
|
||||
String get assistantClose => 'Close';
|
||||
|
||||
@override
|
||||
String assistantOpenFailed(String assistant) {
|
||||
return '$assistant couldn\'t be opened.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get assistantConnectTitle => 'Connect Claude';
|
||||
|
||||
@override
|
||||
String get assistantConnectSubtitle => 'Let Claude read your studysets';
|
||||
|
||||
@override
|
||||
String assistantConnectIntro(String instance) {
|
||||
return 'Claude can read your studysets, cards, notes and progress on $instance through Halkyon Learn\'s connector, and answer about them. It can\'t change anything. What it reads goes to Anthropic, under their privacy policy.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get assistantStepAdd =>
|
||||
'In Claude, open Settings › Connectors and add a custom connector named Halkyon Learn, with this address. Added once, it works in Claude\'s apps too.';
|
||||
|
||||
@override
|
||||
String get assistantStepCode =>
|
||||
'Claude then opens a page asking for a code. Get one here: it works once, for ten minutes.';
|
||||
|
||||
@override
|
||||
String get assistantShowCode => 'Show a code';
|
||||
|
||||
@override
|
||||
String get assistantNewCode => 'New code';
|
||||
|
||||
@override
|
||||
String assistantCodeValid(String time) {
|
||||
return 'Valid until $time';
|
||||
}
|
||||
|
||||
@override
|
||||
String get assistantCopy => 'Copy';
|
||||
|
||||
@override
|
||||
String get assistantCopied => 'Copied';
|
||||
|
||||
@override
|
||||
String get assistantConnected => 'Connected';
|
||||
|
||||
@override
|
||||
String get assistantNoneConnected => 'Nothing connected yet.';
|
||||
|
||||
@override
|
||||
String assistantConnectedOn(String date) {
|
||||
return 'connected $date';
|
||||
}
|
||||
|
||||
@override
|
||||
String assistantLastRead(String date) {
|
||||
return 'last read $date';
|
||||
}
|
||||
|
||||
@override
|
||||
String get assistantDisconnect => 'Disconnect';
|
||||
|
||||
@override
|
||||
String assistantDisconnectTitle(String name) {
|
||||
return 'Disconnect $name?';
|
||||
}
|
||||
|
||||
@override
|
||||
String get assistantDisconnectMessage =>
|
||||
'It can\'t read your studysets any more. You can connect it again with a new code.';
|
||||
|
||||
@override
|
||||
String get assistantNotOffered =>
|
||||
'This instance doesn\'t offer a connector yet. Questions still open in the AI assistant you chose, with what they are about.';
|
||||
|
||||
@override
|
||||
String get assistantUnreachable =>
|
||||
'The instance can\'t be reached. Try again once you are online.';
|
||||
|
||||
@override
|
||||
String get assistantRetry => 'Try again';
|
||||
|
||||
@override
|
||||
String assistantLineFront(String text) {
|
||||
return 'Front: $text';
|
||||
|
|
@ -6790,11 +6690,6 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
return 'Explanation: $text';
|
||||
}
|
||||
|
||||
@override
|
||||
String assistantLineStudyset(String title, String instance) {
|
||||
return 'Studyset: “$title” in Halkyon Learn on $instance';
|
||||
}
|
||||
|
||||
@override
|
||||
String assistantLineDescription(String text) {
|
||||
return 'Description: $text';
|
||||
|
|
@ -6805,20 +6700,6 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
return 'Studyset: $title';
|
||||
}
|
||||
|
||||
@override
|
||||
String get assistantContextLabel => 'Send along';
|
||||
|
||||
@override
|
||||
String get assistantTargetWeb => 'Web';
|
||||
|
||||
@override
|
||||
String get assistantSwitchToWeb => 'Search the web instead';
|
||||
|
||||
@override
|
||||
String assistantSwitchToAssistant(String assistant) {
|
||||
return 'Ask $assistant instead';
|
||||
}
|
||||
|
||||
@override
|
||||
String get assistantHintWeb => 'Search the web…';
|
||||
|
||||
|
|
@ -6831,12 +6712,6 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
@override
|
||||
String get settingsSearchEngine => 'Search engine';
|
||||
|
||||
@override
|
||||
String get settingsAiAssistant => 'AI assistant';
|
||||
|
||||
@override
|
||||
String get aiAssistantCustomHint => 'https://example.org/chat?q=%s';
|
||||
|
||||
@override
|
||||
String get searchEngineDevice => 'The device\'s search';
|
||||
|
||||
|
|
@ -7672,7 +7547,7 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
|
||||
@override
|
||||
String get studysetReadableBody =>
|
||||
'Everything in it is decrypted and synced again, so the instance can read it: you can publish it, and Claude can read it through the connector.';
|
||||
'Everything in it is decrypted and synced again, so the instance can read it: you can publish it.';
|
||||
|
||||
@override
|
||||
String get studysetPrivateNoKeys =>
|
||||
|
|
@ -7804,7 +7679,7 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
|
||||
@override
|
||||
String get studysetPrivacyReadableBody =>
|
||||
'The instance can read it: you can publish it, and Claude can read it through the connector. Making it private encrypts it end to end on your devices; copies in the instance\'s backups stay readable until those expire.';
|
||||
'The instance can read it: you can publish it. Making it private encrypts it end to end on your devices; copies in the instance\'s backups stay readable until those expire.';
|
||||
|
||||
@override
|
||||
String get studysetPrivacyTopic =>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import 'package:halkyon_learn_flutter/src/core/dependencies/person_dependencies.
|
|||
import 'package:halkyon_learn_flutter/src/core/router/app_shell.dart';
|
||||
import 'package:halkyon_learn_flutter/src/core/router/route_paths.dart';
|
||||
import 'package:halkyon_learn_flutter/src/core/router/tab_visibility.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/assistant/widget/connect_claude_screen.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/auth/widget/profile_screen.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/card/bloc/card_composer_bloc.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/card/widget/card_browser_screen.dart';
|
||||
|
|
@ -322,13 +321,6 @@ GoRouter buildRouter({String initialLocation = RoutePaths.home}) => GoRouter(
|
|||
GoRoute(
|
||||
path: RoutePaths.settings,
|
||||
builder: (context, state) => const FramedScreen(child: SettingsScreen()),
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: 'assistant',
|
||||
builder: (context, state) =>
|
||||
const FramedScreen(child: ConnectClaudeScreen()),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
|
|
|
|||
|
|
@ -23,9 +23,6 @@ import 'package:halkyon_learn_flutter/src/core/video/media_players.dart';
|
|||
import 'package:halkyon_learn_flutter/src/core/video/youtube_videos.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/anki/data/anki_package_reader.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/archive/data/archive_reader.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/assistant/data/assistant_hand_off.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/assistant/data/assistant_repository.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/assistant/data/serverpod_assistant_gateway.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/assistant/data/web_search.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/card/data/card_deletions.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/card/data/card_repository.dart';
|
||||
|
|
@ -175,15 +172,9 @@ class Dependencies({
|
|||
/// What the system shows of the Pomodoro timer, and its alerts.
|
||||
required final PomodoroPlatform pomodoro,
|
||||
|
||||
/// Hands questions to the AI assistant the person chose.
|
||||
required final AssistantHandOff assistantHandOff,
|
||||
|
||||
/// Searches the web for questions.
|
||||
required final WebSearch webSearch,
|
||||
|
||||
/// Connects assistants to the account on the instance (ADR 0006).
|
||||
required final AssistantRepository assistant,
|
||||
|
||||
/// The person's own Jellyfin server, to watch and learn from: the
|
||||
/// device's, kept across instances.
|
||||
required final JellyfinRepository jellyfin,
|
||||
|
|
@ -287,9 +278,7 @@ class Dependencies({
|
|||
deviceSettings: await device.deviceSettingsStore.read(),
|
||||
appVersion: appVersion,
|
||||
pomodoro: device.pomodoro,
|
||||
assistantHandOff: AssistantHandOff(),
|
||||
webSearch: WebSearch(),
|
||||
assistant: AssistantRepository(ServerpodAssistantGateway(client)),
|
||||
jellyfin: device.jellyfin,
|
||||
jellyfinSuggestion: JellyfinSuggestion(
|
||||
() async => (await client.instance.info()).jellyfinUrl,
|
||||
|
|
|
|||
|
|
@ -30,21 +30,12 @@ class DeviceSettingsBloc(
|
|||
on<DeviceSettingsEventAssistantChosen>(
|
||||
(event, emit) => _apply(emit, state.withAssistant(event.on)),
|
||||
);
|
||||
on<DeviceSettingsEventAssistantTargetChosen>(
|
||||
(event, emit) => _apply(emit, state.withAssistantTarget(event.target)),
|
||||
);
|
||||
on<DeviceSettingsEventSearchEngineChosen>(
|
||||
(event, emit) => _apply(
|
||||
emit,
|
||||
state.withSearchEngine(event.name, customUrl: event.customUrl),
|
||||
),
|
||||
);
|
||||
on<DeviceSettingsEventAiAssistantChosen>(
|
||||
(event, emit) => _apply(
|
||||
emit,
|
||||
state.withAiAssistant(event.name, customUrl: event.customUrl),
|
||||
),
|
||||
);
|
||||
on<DeviceSettingsEventPdfSidebarDragged>(
|
||||
(event, emit) => _apply(emit, state.withPdfSidebarShare(event.share)),
|
||||
);
|
||||
|
|
@ -105,21 +96,13 @@ final class const DeviceSettingsEventPomodoroWhileStudyingChosen(
|
|||
List<Object?> get props => [shown];
|
||||
}
|
||||
|
||||
/// The person turned asking an assistant [on] or off.
|
||||
/// The person turned searching the web from the app [on] or off.
|
||||
final class const DeviceSettingsEventAssistantChosen(final bool on)
|
||||
extends DeviceSettingsEvent {
|
||||
@override
|
||||
List<Object?> get props => [on];
|
||||
}
|
||||
|
||||
/// The person sent a question to [target], which the field remembers.
|
||||
final class const DeviceSettingsEventAssistantTargetChosen(
|
||||
final AssistantTarget target,
|
||||
) extends DeviceSettingsEvent {
|
||||
@override
|
||||
List<Object?> get props => [target];
|
||||
}
|
||||
|
||||
/// The person chose the search engine [name] (null for the platform's
|
||||
/// default), at [customUrl] for a custom one.
|
||||
final class const DeviceSettingsEventSearchEngineChosen(
|
||||
|
|
@ -130,16 +113,6 @@ final class const DeviceSettingsEventSearchEngineChosen(
|
|||
List<Object?> get props => [name, customUrl];
|
||||
}
|
||||
|
||||
/// The person chose the AI assistant [name] (null for the default), at
|
||||
/// [customUrl] for a custom one.
|
||||
final class const DeviceSettingsEventAiAssistantChosen(
|
||||
final String? name, {
|
||||
final String? customUrl,
|
||||
}) extends DeviceSettingsEvent {
|
||||
@override
|
||||
List<Object?> get props => [name, customUrl];
|
||||
}
|
||||
|
||||
/// The person shows the card browser of [studysetId] one card at a time
|
||||
/// ([detailed]) or as a grid.
|
||||
final class const DeviceSettingsEventBrowserViewChosen(
|
||||
|
|
|
|||
|
|
@ -51,13 +51,9 @@ class FileDeviceSettingsStore(final File _file) implements DeviceSettingsStore {
|
|||
if (json['detailedBrowsers'] case final List<Object?> ids)
|
||||
for (final id in ids.whereType<String>()) id,
|
||||
},
|
||||
// A file from before ADR 0022 also holds `assistantTarget`,
|
||||
// `aiAssistant` and `customAssistantUrl`, which nothing reads.
|
||||
assistant: json['assistant'] == true,
|
||||
// Asking Claude was the only assistant before 2026-10-14
|
||||
// (`claude`).
|
||||
assistantTarget: switch (json['assistantTarget']) {
|
||||
'web' => AssistantTarget.web,
|
||||
_ => AssistantTarget.assistant,
|
||||
},
|
||||
searchEngine: switch (json['searchEngine']) {
|
||||
final String name => name,
|
||||
_ => null,
|
||||
|
|
@ -66,14 +62,6 @@ class FileDeviceSettingsStore(final File _file) implements DeviceSettingsStore {
|
|||
final String url => url,
|
||||
_ => null,
|
||||
},
|
||||
aiAssistant: switch (json['aiAssistant']) {
|
||||
final String name => name,
|
||||
_ => null,
|
||||
},
|
||||
customAssistantUrl: switch (json['customAssistantUrl']) {
|
||||
final String url => url,
|
||||
_ => null,
|
||||
},
|
||||
pdfSidebarShare: switch (json['pdfSidebarShare']) {
|
||||
final num share when share > 0 && share < 1 => share.toDouble(),
|
||||
_ => null,
|
||||
|
|
@ -97,11 +85,8 @@ class FileDeviceSettingsStore(final File _file) implements DeviceSettingsStore {
|
|||
'pomodoroWhileStudying': settings.pomodoroWhileStudying,
|
||||
'detailedBrowsers': [...settings.detailedBrowsers],
|
||||
'assistant': settings.assistant,
|
||||
'assistantTarget': settings.assistantTarget.name,
|
||||
'searchEngine': ?settings.searchEngine,
|
||||
'customSearchUrl': ?settings.customSearchUrl,
|
||||
'aiAssistant': ?settings.aiAssistant,
|
||||
'customAssistantUrl': ?settings.customAssistantUrl,
|
||||
'pdfSidebarShare': ?settings.pdfSidebarShare,
|
||||
}),
|
||||
flush: true,
|
||||
|
|
|
|||
|
|
@ -8,19 +8,11 @@ enum Appearance() {
|
|||
dark,
|
||||
}
|
||||
|
||||
/// Where a question asked from the app goes: to the AI assistant the
|
||||
/// person chose, or into a web search; the field remembers the last choice
|
||||
/// (decided 2026-10-10).
|
||||
enum AssistantTarget() {
|
||||
assistant,
|
||||
web,
|
||||
}
|
||||
|
||||
/// The settings of one device, which stay on it (decided 2026-10-07): how the
|
||||
/// app looks, the language it speaks, the style it cites sources in,
|
||||
/// whether studying shows the Pomodoro timer, which studysets' card
|
||||
/// browsers show one card at a time, and whether and how the person asks
|
||||
/// an AI assistant or searches the web from the app.
|
||||
/// browsers show one card at a time, and whether and how the person
|
||||
/// searches the web from the app.
|
||||
/// Unlike the person's settings, they don't follow anyone to other devices,
|
||||
/// and they apply before anyone is signed in.
|
||||
final class const DeviceSettings({
|
||||
|
|
@ -40,22 +32,12 @@ final class const DeviceSettings({
|
|||
/// list (detail) rather than a grid, as last left.
|
||||
final Set<String> detailedBrowsers = const {},
|
||||
|
||||
/// Whether the app offers to ask an AI assistant or search the web
|
||||
/// (decided 2026-10-10): a field on Home, a button in the Library, on a
|
||||
/// studyset and while studying. Off unless the person turns it on.
|
||||
/// Whether the app offers to search the web (decided 2026-10-10): a
|
||||
/// field on Home, a button in the Library, on a studyset and while
|
||||
/// studying. Off unless the person turns it on. It also asked an AI
|
||||
/// assistant outside the app until ADR 0022, hence its name.
|
||||
final bool assistant = false,
|
||||
|
||||
/// Where the field sends a question, as last chosen.
|
||||
final AssistantTarget assistantTarget = AssistantTarget.assistant,
|
||||
|
||||
/// The AI assistant a question opens in, by its name in the assistant's
|
||||
/// list; null for the default, Proton's Lumo (decided 2026-10-14).
|
||||
final String? aiAssistant,
|
||||
|
||||
/// The address a custom assistant opens, with `%s` for the question;
|
||||
/// null without.
|
||||
final String? customAssistantUrl,
|
||||
|
||||
/// The search engine a web search uses, by its name in the assistant's
|
||||
/// list; null for the platform's default (the device's own search on
|
||||
/// Android, Brave Search elsewhere).
|
||||
|
|
@ -93,24 +75,14 @@ final class const DeviceSettings({
|
|||
: ({...detailedBrowsers}..remove(studysetId)),
|
||||
);
|
||||
|
||||
/// These settings with asking an assistant or searching the web [on] or
|
||||
/// off.
|
||||
/// These settings with searching the web from the app [on] or off.
|
||||
DeviceSettings withAssistant(bool on) => _copy(assistant: on);
|
||||
|
||||
/// These settings with questions going to [target].
|
||||
DeviceSettings withAssistantTarget(AssistantTarget target) =>
|
||||
_copy(assistantTarget: target);
|
||||
|
||||
/// These settings searching with the engine [name] (null for the
|
||||
/// platform's default), at [customUrl] for a custom one.
|
||||
DeviceSettings withSearchEngine(String? name, {String? customUrl}) =>
|
||||
_copy(searchEngine: name, customSearchUrl: customUrl);
|
||||
|
||||
/// These settings asking the assistant [name] (null for the default), at
|
||||
/// [customUrl] for a custom one.
|
||||
DeviceSettings withAiAssistant(String? name, {String? customUrl}) =>
|
||||
_copy(aiAssistant: name, customAssistantUrl: customUrl);
|
||||
|
||||
/// These settings with the editor beside a PDF taking [share] of the
|
||||
/// room.
|
||||
DeviceSettings withPdfSidebarShare(double share) =>
|
||||
|
|
@ -123,11 +95,8 @@ final class const DeviceSettings({
|
|||
bool? pomodoroWhileStudying,
|
||||
Set<String>? detailedBrowsers,
|
||||
bool? assistant,
|
||||
AssistantTarget? assistantTarget,
|
||||
Object? searchEngine = _keep,
|
||||
Object? customSearchUrl = _keep,
|
||||
Object? aiAssistant = _keep,
|
||||
Object? customAssistantUrl = _keep,
|
||||
double? pdfSidebarShare,
|
||||
}) => DeviceSettings(
|
||||
appearance: appearance ?? this.appearance,
|
||||
|
|
@ -136,19 +105,12 @@ final class const DeviceSettings({
|
|||
pomodoroWhileStudying: pomodoroWhileStudying ?? this.pomodoroWhileStudying,
|
||||
detailedBrowsers: detailedBrowsers ?? this.detailedBrowsers,
|
||||
assistant: assistant ?? this.assistant,
|
||||
assistantTarget: assistantTarget ?? this.assistantTarget,
|
||||
searchEngine: identical(searchEngine, _keep)
|
||||
? this.searchEngine
|
||||
: searchEngine as String?,
|
||||
customSearchUrl: identical(customSearchUrl, _keep)
|
||||
? this.customSearchUrl
|
||||
: customSearchUrl as String?,
|
||||
aiAssistant: identical(aiAssistant, _keep)
|
||||
? this.aiAssistant
|
||||
: aiAssistant as String?,
|
||||
customAssistantUrl: identical(customAssistantUrl, _keep)
|
||||
? this.customAssistantUrl
|
||||
: customAssistantUrl as String?,
|
||||
pdfSidebarShare: pdfSidebarShare ?? this.pdfSidebarShare,
|
||||
);
|
||||
|
||||
|
|
@ -160,11 +122,8 @@ final class const DeviceSettings({
|
|||
pomodoroWhileStudying,
|
||||
detailedBrowsers,
|
||||
assistant,
|
||||
assistantTarget,
|
||||
searchEngine,
|
||||
customSearchUrl,
|
||||
aiAssistant,
|
||||
customAssistantUrl,
|
||||
pdfSidebarShare,
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,9 +16,6 @@ abstract final class RoutePaths() {
|
|||
/// Settings, over the tabs.
|
||||
static const settings = '/settings';
|
||||
|
||||
/// Connecting Claude to the account (Settings › Assistant), over Settings.
|
||||
static const connectAssistant = '/settings/assistant';
|
||||
|
||||
/// The reading deadlines on a line of days, over the tabs.
|
||||
static const readingTimeline = '/reading-timeline';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,111 +0,0 @@
|
|||
import 'dart:developer';
|
||||
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/assistant/data/assistant_repository.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/assistant/model/connected_assistant.dart';
|
||||
|
||||
/// Connecting Claude to the person's account: where the instance's
|
||||
/// connector is, a code to type on Claude's page, and what is connected.
|
||||
class ConnectAssistantCubit(final AssistantRepository _assistant)
|
||||
extends Cubit<ConnectAssistantState> {
|
||||
this : super(const ConnectAssistantState());
|
||||
|
||||
/// Asks the instance where its connector is and what is connected.
|
||||
Future<void> load() async {
|
||||
emit(state.copyWith(loading: true, failed: false));
|
||||
try {
|
||||
final url = await _assistant.connectorUrl();
|
||||
final connections = url == null
|
||||
? const <ConnectedAssistant>[]
|
||||
: await _assistant.connections();
|
||||
_emit(
|
||||
state.copyWith(
|
||||
loading: false,
|
||||
offered: url != null,
|
||||
connectorUrl: url,
|
||||
connections: connections,
|
||||
),
|
||||
);
|
||||
} on Exception catch (error) {
|
||||
log('Asking about the connector failed', name: 'Assistant', error: error);
|
||||
_emit(state.copyWith(loading: false, failed: true));
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets a new code; the one shown before stops working.
|
||||
Future<void> newCode() async {
|
||||
try {
|
||||
final code = await _assistant.connectCode();
|
||||
_emit(state.copyWith(code: code, failed: false));
|
||||
} on Exception catch (error) {
|
||||
log('Getting a code failed', name: 'Assistant', error: error);
|
||||
_emit(state.copyWith(failed: true));
|
||||
}
|
||||
}
|
||||
|
||||
/// Disconnects [assistant], then lists what is still connected.
|
||||
Future<void> disconnect(ConnectedAssistant assistant) async {
|
||||
try {
|
||||
await _assistant.disconnect(assistant.id);
|
||||
_emit(state.copyWith(connections: await _assistant.connections()));
|
||||
} on Exception catch (error) {
|
||||
log('Disconnecting failed', name: 'Assistant', error: error);
|
||||
_emit(state.copyWith(failed: true));
|
||||
}
|
||||
}
|
||||
|
||||
void _emit(ConnectAssistantState next) {
|
||||
if (!isClosed) {
|
||||
emit(next);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// What the screen shows of connecting Claude.
|
||||
final class const ConnectAssistantState({
|
||||
/// Whether the instance is being asked.
|
||||
final bool loading = true,
|
||||
|
||||
/// Whether the instance offers a connector.
|
||||
final bool offered = false,
|
||||
|
||||
/// The connector's address, to add in Claude.
|
||||
final String? connectorUrl,
|
||||
|
||||
/// The assistants connected.
|
||||
final List<ConnectedAssistant> connections = const [],
|
||||
|
||||
/// The last code got; null before.
|
||||
final ConnectCode? code,
|
||||
|
||||
/// Whether the last request to the instance failed.
|
||||
final bool failed = false,
|
||||
}) with Equatable {
|
||||
/// This state with the given fields replaced.
|
||||
ConnectAssistantState copyWith({
|
||||
bool? loading,
|
||||
bool? offered,
|
||||
String? connectorUrl,
|
||||
List<ConnectedAssistant>? connections,
|
||||
ConnectCode? code,
|
||||
bool? failed,
|
||||
}) => ConnectAssistantState(
|
||||
loading: loading ?? this.loading,
|
||||
offered: offered ?? this.offered,
|
||||
connectorUrl: connectorUrl ?? this.connectorUrl,
|
||||
connections: connections ?? this.connections,
|
||||
code: code ?? this.code,
|
||||
failed: failed ?? this.failed,
|
||||
);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
loading,
|
||||
offered,
|
||||
connectorUrl,
|
||||
connections,
|
||||
code,
|
||||
failed,
|
||||
];
|
||||
}
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
import 'package:halkyon_learn_flutter/src/feature/assistant/model/connected_assistant.dart';
|
||||
|
||||
/// The instance's side of connecting assistants (ADR 0006).
|
||||
abstract interface class AssistantGateway() {
|
||||
/// Where assistants connect on this instance (its MCP endpoint); null
|
||||
/// where it offers none.
|
||||
Future<String?> connectorUrl();
|
||||
|
||||
/// A new code for connecting an assistant; the last one stops working.
|
||||
Future<ConnectCode> connectCode();
|
||||
|
||||
/// The assistants connected to the person's account, newest first.
|
||||
Future<List<ConnectedAssistant>> connections();
|
||||
|
||||
/// Disconnects the assistant [id]: it can't read anything from then on.
|
||||
Future<void> disconnect(int id);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue