feat(learn): an assistant reads PDFs' pages, and sees images the person chooses or approves

ADR 0021, D12: text first. Lookups find a studyset's PDFs and read a page's
text as PDFium extracts it. An assistant that reads images is offered a
card's images as chips, and may ask to see a card's image or a PDF's page
drawn whole; the conversation asks the person each time (Show it, Don't
show) and sends nothing meanwhile. Images go only as png, jpeg, gif or webp
of at most 5 MB, are never kept in a turn, and a follow-up goes without them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-10-25 21:54:56 +01:00
commit 2b7d04fd70
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64
5 changed files with 2030 additions and 77 deletions

View file

@ -1814,6 +1814,7 @@
"assistantLineExplanation": "Erklärung: {text}",
"assistantLineDescription": "Beschreibung: {text}",
"assistantChipStudyset": "Lernset: {title}",
"assistantChipImage": "Bild {number}",
"assistantHintWeb": "Im Web suchen…",
"assistantSendWeb": "Suchen",
"assistantSearchFailed": "Die Suche ließ sich nicht öffnen.",
@ -1858,6 +1859,18 @@
"assistantLookedUpNotes": "Notizen nach „{query}“ durchsucht: {count, plural, =0{keine gefunden} =1{1 gefunden} other{{count} gefunden}}",
"assistantLookedUpNote": "Eine Notiz gelesen",
"assistantLookedUpNoNote": "Eine Notiz gesucht, keine gefunden",
"assistantListedPdfs": "{count, plural, =0{Keine PDFs} =1{1 PDF} other{{count} PDFs}} aufgelistet",
"assistantLookedUpPdfs": "PDFs nach „{query}“ durchsucht: {count, plural, =0{keine gefunden} =1{1 gefunden} other{{count} gefunden}}",
"assistantLookedUpPage": "Eine Seite einer PDF gelesen",
"assistantLookedUpNoPage": "Eine Seite einer PDF gesucht, nicht lesbar",
"assistantShownImage": "Hat ein Bild gesehen, das du freigegeben hast",
"assistantNotShownImage": "Hat ein Bild nicht gesehen",
"assistantShownPage": "Hat eine Seite gesehen, die du freigegeben hast",
"assistantNotShownPage": "Hat eine Seite nicht gesehen",
"assistantImageAskCard": "{name} möchte Bild {number} der Karte „{title}“ sehen.",
"assistantImageAskPage": "{name} möchte Seite {page} von „{title}“ als Bild sehen.",
"assistantImageShow": "Zeigen",
"assistantImageDontShow": "Nicht zeigen",
"assistantPrivateTitle": "{count, plural, =1{Ein privates Lernset an {name} senden?} other{Private Lernsets an {name} senden?}}",
"assistantPrivateGranted": "{count, plural, =1{Es ist Ende-zu-Ende-verschlüsselt, sodass nicht einmal {instance} es lesen kann. Was {name} darin nachschlägt oder was du daraus auswählst, geht durch {instance}, das nichts davon aufbewahrt, an {host}, wo es nach den Bedingungen des Anbieters gelesen wird.} other{Sie sind Ende-zu-Ende-verschlüsselt, sodass nicht einmal {instance} sie lesen kann. Was {name} darin nachschlägt oder was du daraus auswählst, geht durch {instance}, das nichts davon aufbewahrt, an {host}, wo es nach den Bedingungen des Anbieters gelesen wird.}}",
"assistantPrivateOwn": "{count, plural, =1{Es ist Ende-zu-Ende-verschlüsselt, sodass nicht einmal die Instanz es lesen kann. Was {name} darin nachschlägt oder was du daraus auswählst, geht direkt an {host}, wo es nach den Bedingungen des Anbieters gelesen wird.} other{Sie sind Ende-zu-Ende-verschlüsselt, sodass nicht einmal die Instanz sie lesen kann. Was {name} darin nachschlägt oder was du daraus auswählst, geht direkt an {host}, wo es nach den Bedingungen des Anbieters gelesen wird.}}",

View file

@ -2128,6 +2128,8 @@
"@assistantLineDescription": {"placeholders": {"text": {"type": "String"}}},
"assistantChipStudyset": "Studyset: {title}",
"@assistantChipStudyset": {"placeholders": {"title": {"type": "String"}}},
"assistantChipImage": "Image {number}",
"@assistantChipImage": {"description": "A chip sending an image on the card (ADR 0021, D12).", "placeholders": {"number": {"type": "int"}}},
"assistantHintWeb": "Search the web…",
"assistantSendWeb": "Search",
"assistantSearchFailed": "The search couldn't be opened.",
@ -2182,6 +2184,22 @@
"@assistantLookedUpNotes": {"placeholders": {"query": {"type": "String"}, "count": {"type": "int"}}},
"assistantLookedUpNote": "Read a note",
"assistantLookedUpNoNote": "Looked for a note, found none",
"assistantListedPdfs": "Listed {count, plural, =0{no PDFs} =1{1 PDF} other{{count} PDFs}}",
"@assistantListedPdfs": {"placeholders": {"count": {"type": "int"}}},
"assistantLookedUpPdfs": "Looked for “{query}” in PDFs: {count, plural, =0{none found} =1{1 found} other{{count} found}}",
"@assistantLookedUpPdfs": {"placeholders": {"query": {"type": "String"}, "count": {"type": "int"}}},
"assistantLookedUpPage": "Read a page of a PDF",
"assistantLookedUpNoPage": "Looked for a page of a PDF, couldn't read it",
"assistantShownImage": "Was shown an image you approved",
"assistantNotShownImage": "Was not shown an image",
"assistantShownPage": "Was shown a page you approved",
"assistantNotShownPage": "Was not shown a page",
"assistantImageAskCard": "{name} asks to see image {number} of the card “{title}”.",
"@assistantImageAskCard": {"description": "The model asks for an image; the person approves each (D12).", "placeholders": {"name": {"type": "String"}, "number": {"type": "int"}, "title": {"type": "String"}}},
"assistantImageAskPage": "{name} asks to see page {page} of “{title}” as a picture.",
"@assistantImageAskPage": {"placeholders": {"name": {"type": "String"}, "page": {"type": "int"}, "title": {"type": "String"}}},
"assistantImageShow": "Show it",
"assistantImageDontShow": "Don't show",
"assistantPrivateTitle": "{count, plural, =1{Send a private studyset to {name}?} other{Send private studysets to {name}?}}",
"@assistantPrivateTitle": {"description": "The warning before a private studyset's content goes to a remote model (ADR 0021, D9).", "placeholders": {"count": {"type": "int"}, "name": {"type": "String"}}},
"assistantPrivateGranted": "{count, plural, =1{It is end-to-end encrypted, so that not even {instance} can read it. What {name} looks up in it, or what you chose of it, passes through {instance}, which keeps none of it, to {host}, which reads it under the provider's terms.} other{They are end-to-end encrypted, so that not even {instance} can read them. What {name} looks up in them, or what you chose of them, passes through {instance}, which keeps none of it, to {host}, which reads it under the provider's terms.}}",

View file

@ -10701,6 +10701,12 @@ abstract class AppLocalizations {
/// **'Studyset: {title}'**
String assistantChipStudyset(String title);
/// A chip sending an image on the card (ADR 0021, D12).
///
/// In en, this message translates to:
/// **'Image {number}'**
String assistantChipImage(int number);
/// No description provided for @assistantHintWeb.
///
/// In en, this message translates to:
@ -10965,6 +10971,78 @@ abstract class AppLocalizations {
/// **'Looked for a note, found none'**
String get assistantLookedUpNoNote;
/// No description provided for @assistantListedPdfs.
///
/// In en, this message translates to:
/// **'Listed {count, plural, =0{no PDFs} =1{1 PDF} other{{count} PDFs}}'**
String assistantListedPdfs(int count);
/// No description provided for @assistantLookedUpPdfs.
///
/// In en, this message translates to:
/// **'Looked for “{query}” in PDFs: {count, plural, =0{none found} =1{1 found} other{{count} found}}'**
String assistantLookedUpPdfs(String query, int count);
/// No description provided for @assistantLookedUpPage.
///
/// In en, this message translates to:
/// **'Read a page of a PDF'**
String get assistantLookedUpPage;
/// No description provided for @assistantLookedUpNoPage.
///
/// In en, this message translates to:
/// **'Looked for a page of a PDF, couldn\'t read it'**
String get assistantLookedUpNoPage;
/// No description provided for @assistantShownImage.
///
/// In en, this message translates to:
/// **'Was shown an image you approved'**
String get assistantShownImage;
/// No description provided for @assistantNotShownImage.
///
/// In en, this message translates to:
/// **'Was not shown an image'**
String get assistantNotShownImage;
/// No description provided for @assistantShownPage.
///
/// In en, this message translates to:
/// **'Was shown a page you approved'**
String get assistantShownPage;
/// No description provided for @assistantNotShownPage.
///
/// In en, this message translates to:
/// **'Was not shown a page'**
String get assistantNotShownPage;
/// The model asks for an image; the person approves each (D12).
///
/// In en, this message translates to:
/// **'{name} asks to see image {number} of the card “{title}”.'**
String assistantImageAskCard(String name, int number, String title);
/// No description provided for @assistantImageAskPage.
///
/// In en, this message translates to:
/// **'{name} asks to see page {page} of “{title}” as a picture.'**
String assistantImageAskPage(String name, int page, String title);
/// No description provided for @assistantImageShow.
///
/// In en, this message translates to:
/// **'Show it'**
String get assistantImageShow;
/// No description provided for @assistantImageDontShow.
///
/// In en, this message translates to:
/// **'Don\'t show'**
String get assistantImageDontShow;
/// The warning before a private studyset's content goes to a remote model (ADR 0021, D9).
///
/// In en, this message translates to:

View file

@ -6707,6 +6707,11 @@ class AppLocalizationsDe extends AppLocalizations {
return 'Lernset: $title';
}
@override
String assistantChipImage(int number) {
return 'Bild $number';
}
@override
String get assistantHintWeb => 'Im Web suchen…';
@ -6876,6 +6881,67 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get assistantLookedUpNoNote => 'Eine Notiz gesucht, keine gefunden';
@override
String assistantListedPdfs(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count PDFs',
one: '1 PDF',
zero: 'Keine PDFs',
);
return '$_temp0 aufgelistet';
}
@override
String assistantLookedUpPdfs(String query, int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count gefunden',
one: '1 gefunden',
zero: 'keine gefunden',
);
return 'PDFs nach „$query“ durchsucht: $_temp0';
}
@override
String get assistantLookedUpPage => 'Eine Seite einer PDF gelesen';
@override
String get assistantLookedUpNoPage =>
'Eine Seite einer PDF gesucht, nicht lesbar';
@override
String get assistantShownImage =>
'Hat ein Bild gesehen, das du freigegeben hast';
@override
String get assistantNotShownImage => 'Hat ein Bild nicht gesehen';
@override
String get assistantShownPage =>
'Hat eine Seite gesehen, die du freigegeben hast';
@override
String get assistantNotShownPage => 'Hat eine Seite nicht gesehen';
@override
String assistantImageAskCard(String name, int number, String title) {
return '$name möchte Bild $number der Karte „$title“ sehen.';
}
@override
String assistantImageAskPage(String name, int page, String title) {
return '$name möchte Seite $page von „$title“ als Bild sehen.';
}
@override
String get assistantImageShow => 'Zeigen';
@override
String get assistantImageDontShow => 'Nicht zeigen';
@override
String assistantPrivateTitle(int count, String name) {
String _temp0 = intl.Intl.pluralLogic(

View file

@ -6643,6 +6643,11 @@ class AppLocalizationsEn extends AppLocalizations {
return 'Studyset: $title';
}
@override
String assistantChipImage(int number) {
return 'Image $number';
}
@override
String get assistantHintWeb => 'Search the web…';
@ -6812,6 +6817,65 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get assistantLookedUpNoNote => 'Looked for a note, found none';
@override
String assistantListedPdfs(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count PDFs',
one: '1 PDF',
zero: 'no PDFs',
);
return 'Listed $_temp0';
}
@override
String assistantLookedUpPdfs(String query, int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count found',
one: '1 found',
zero: 'none found',
);
return 'Looked for “$query” in PDFs: $_temp0';
}
@override
String get assistantLookedUpPage => 'Read a page of a PDF';
@override
String get assistantLookedUpNoPage =>
'Looked for a page of a PDF, couldn\'t read it';
@override
String get assistantShownImage => 'Was shown an image you approved';
@override
String get assistantNotShownImage => 'Was not shown an image';
@override
String get assistantShownPage => 'Was shown a page you approved';
@override
String get assistantNotShownPage => 'Was not shown a page';
@override
String assistantImageAskCard(String name, int number, String title) {
return '$name asks to see image $number of the card “$title”.';
}
@override
String assistantImageAskPage(String name, int page, String title) {
return '$name asks to see page $page of “$title” as a picture.';
}
@override
String get assistantImageShow => 'Show it';
@override
String get assistantImageDontShow => 'Don\'t show';
@override
String assistantPrivateTitle(int count, String name) {
String _temp0 = intl.Intl.pluralLogic(