GUI-Bugfixes: Defaults beim Start, kopierbare Meldungen mit Zeitstempel, Befundauflistung, Modell-ComboBox links, effektiver API-Key für Modellabruf

- Blank-Startzustand zeigt jetzt die Standardvorlage (wie nach "Neu"), neue Factory createEmptyStartState für Tests
- Meldungsbereich ist per Kontextmenü bzw. Strg+C kopierbar
- Jede Meldung trägt ein führendes [HH:mm:ss]-Präfix
- Validieren- und Tests-Aktionen akkumulieren Meldungen, automatische Validierung ersetzt still ihre Einträge
- Validieren-Meldung listet alle konkreten Befunde einzeln auf
- Modell-ComboBox und manuelles Modellfeld sind linksbündig
- ApiKeyResolutionPort liefert jetzt den effektiven API-Schlüsselwert (Default + Env-Adapter-Override), so dass der Modellliste-Test in den technischen Tests nicht mehr "API-Schlüssel fehlt" meldet, obwohl er gesetzt ist
This commit is contained in:
2026-04-21 16:04:15 +02:00
parent 6babdd226e
commit ada7e203e3
18 changed files with 471 additions and 204 deletions
@@ -1,6 +1,7 @@
package de.gecheckt.pdf.umbenenner.adapter.out.validation;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import de.gecheckt.pdf.umbenenner.application.config.provider.AiProviderFamily;
@@ -103,6 +104,50 @@ public class EnvironmentApiKeyResolutionAdapter implements ApiKeyResolutionPort
return EffectiveApiKeyDescriptor.absent();
}
/**
* Liefert den effektiven API-Schlüssel-Rohwert anhand derselben Vorrangregel wie {@link #resolve}.
*
* @param family die Provider-Familie; darf nicht {@code null} sein
* @param propertyValue aktueller Property-Wert aus dem Editor; darf nicht {@code null} sein
* @return der effektive Rohwert, falls verfügbar; sonst leer
*/
@Override
public Optional<String> resolveEffectiveApiKeyValue(AiProviderFamily family, String propertyValue) {
Objects.requireNonNull(family, "family must not be null");
Objects.requireNonNull(propertyValue, "propertyValue must not be null");
return switch (family) {
case CLAUDE -> resolveClaudeValue(propertyValue);
case OPENAI_COMPATIBLE -> resolveOpenAiCompatibleValue(propertyValue);
};
}
private Optional<String> resolveClaudeValue(String propertyValue) {
String envValue = environmentLookup.apply(ENV_CLAUDE_API_KEY);
if (isPresent(envValue)) {
return Optional.of(envValue);
}
if (isPresent(propertyValue)) {
return Optional.of(propertyValue);
}
return Optional.empty();
}
private Optional<String> resolveOpenAiCompatibleValue(String propertyValue) {
String primaryEnv = environmentLookup.apply(ENV_OPENAI_API_KEY);
if (isPresent(primaryEnv)) {
return Optional.of(primaryEnv);
}
String legacyEnv = environmentLookup.apply(ENV_LEGACY_OPENAI_API_KEY);
if (isPresent(legacyEnv)) {
return Optional.of(legacyEnv);
}
if (isPresent(propertyValue)) {
return Optional.of(propertyValue);
}
return Optional.empty();
}
private static boolean isPresent(String value) {
return value != null && !value.isBlank();
}