mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-13 06:32:03 +00:00
feat(insights): turn the no-provider dead-ends into guided setup
Coach, the daily briefing, and the per-metric assessment tiles all default ON but need a connected AI provider that onboarding never mentions, so a fresh user hits a bare error on the headline feature. - The daily-briefing and per-metric no-provider states now explain what the AI does and why a provider is needed (your own key or a local model, data stays with you) rather than a one-line 'unavailable'. - The per-metric tile gains a direct Settings link. - The Coach 'no provider configured' turn maps to its own copy with a Settings link instead of the transient 'try again in a moment' that would 422 forever. Adds insights.noProviderAction, insights.coach.errorNoProvider and errorNoProviderAction across all six locales.
This commit is contained in:
1 parent
63bca26177
commit
3d213d1fbf
10 files changed
+73
-17
No files matched your search
+5
-2
@@ -1892,7 +1892,8 @@
|
||||
"bmiEmptyAction": "Account-Einstellungen öffnen",
|
||||
"noAnalysisYet": "Noch keine Einschätzung vorhanden.",
|
||||
"assessmentPreparing": "Einschätzung wird vorbereitet…",
|
||||
"noProviderConfigured": "Einschätzung nicht verfügbar.",
|
||||
"noProviderConfigured": "Verbinde einen KI-Anbieter, um hier eine Einschätzung zu deinen Werten zu sehen.",
|
||||
"noProviderAction": "Zu den KI-Einstellungen",
|
||||
"startAnalysis": "Einschätzung starten",
|
||||
"refreshAnalysis": "Einschätzung aktualisieren",
|
||||
"keyTakeaway": "Das Wichtigste",
|
||||
@@ -2007,7 +2008,7 @@
|
||||
"emptyAction": "Briefing erstellen",
|
||||
"loadingLabel": "Briefing wird zusammengestellt",
|
||||
"noProviderTitle": "Kein KI-Anbieter verbunden",
|
||||
"noProviderDescription": "Verbinde einen KI-Anbieter in den Einstellungen, um tägliche Briefings zu erhalten.",
|
||||
"noProviderDescription": "Das Tagesbriefing fasst deine Werte in Klartext zusammen. Dafür braucht HealthLog einen KI-Anbieter — nutze deinen eigenen Schlüssel (OpenAI, Anthropic) oder ein lokales Modell. Deine Daten bleiben dabei bei dir.",
|
||||
"noProviderAction": "Zu den KI-Einstellungen"
|
||||
},
|
||||
"trendsRow": {
|
||||
@@ -2072,6 +2073,8 @@
|
||||
"refusalInjection": "In deiner Nachricht stehen Anweisungen, die meine Vorgaben überschreiben sollen. Ich bin der HealthLog-Coach und fasse ausschließlich deine eigenen Gesundheitsdaten zusammen — formuliere deine Frage bitte in diesem Rahmen neu.",
|
||||
"errorBudget": "Du hast das heutige Coach-Limit erreicht. Das Budget setzt sich um Mitternacht (UTC) zurück.",
|
||||
"errorProvider": "Der Coach konnte gerade keinen Auswertungs-Anbieter erreichen. Bitte versuche es in einem Moment erneut.",
|
||||
"errorNoProvider": "Der Coach liest deine erfassten Gesundheitsdaten und beantwortet Fragen dazu (keine medizinische Beratung). Dafür braucht HealthLog einen KI-Anbieter — verbinde deinen eigenen Schlüssel oder ein lokales Modell.",
|
||||
"errorNoProviderAction": "KI-Anbieter verbinden",
|
||||
"errorNetwork": "Keine Internetverbindung — versuche es erneut, sobald du online bist.",
|
||||
"prefsSaveError": "Coach-Einstellungen konnten nicht gespeichert werden. Bitte versuche es erneut.",
|
||||
"errorCredentialExpired": "Deine Verbindung zum KI-Anbieter ist abgelaufen. Stelle sie in den Einstellungen wieder her, damit der Coach weiterläuft.",
|
||||
|
||||
+5
-2
@@ -1892,7 +1892,8 @@
|
||||
"bmiEmptyTitle": "Set your height to see BMI",
|
||||
"bmiEmptyDescription": "BMI needs your height. Add it under Settings → Account.",
|
||||
"bmiEmptyAction": "Open account settings",
|
||||
"noProviderConfigured": "Assessment unavailable.",
|
||||
"noProviderConfigured": "Connect an AI provider to see an assessment of your readings here.",
|
||||
"noProviderAction": "Open AI settings",
|
||||
"startAnalysis": "Start assessment",
|
||||
"refreshAnalysis": "Refresh assessment",
|
||||
"keyTakeaway": "Key Takeaway",
|
||||
@@ -2007,7 +2008,7 @@
|
||||
"emptyAction": "Generate briefing",
|
||||
"loadingLabel": "Synthesising today's briefing",
|
||||
"noProviderTitle": "No AI provider connected",
|
||||
"noProviderDescription": "Connect an AI provider in settings to receive daily briefings.",
|
||||
"noProviderDescription": "The daily briefing turns your readings into plain-language summary. HealthLog needs an AI provider for that — use your own key (OpenAI, Anthropic) or a local model. Your data stays with you.",
|
||||
"noProviderAction": "Open AI settings"
|
||||
},
|
||||
"trendsRow": {
|
||||
@@ -2072,6 +2073,8 @@
|
||||
"refusalInjection": "I noticed wording that tries to override my instructions. I am the HealthLog Coach and only summarise your own health data — please rephrase your question in those terms.",
|
||||
"errorBudget": "You've reached today's Coach usage limit. The budget refreshes at midnight UTC.",
|
||||
"errorProvider": "The Coach could not reach an Insights provider just now. Please try again in a moment.",
|
||||
"errorNoProvider": "The Coach reads your logged health data and answers questions about it (not medical advice). HealthLog needs an AI provider for that — connect your own key or a local model.",
|
||||
"errorNoProviderAction": "Connect an AI provider",
|
||||
"errorNetwork": "No internet connection — try again once you're back online.",
|
||||
"prefsSaveError": "Couldn't save your Coach preferences. Please try again.",
|
||||
"errorCredentialExpired": "Your AI provider connection has expired. Reconnect it in Settings to keep the Coach running.",
|
||||
|
||||
+5
-2
@@ -1892,7 +1892,8 @@
|
||||
"bmiEmptyTitle": "Añade tu altura para calcular el IMC",
|
||||
"bmiEmptyDescription": "El IMC necesita tu altura. Guárdala en Ajustes → Cuenta.",
|
||||
"bmiEmptyAction": "Abrir ajustes de cuenta",
|
||||
"noProviderConfigured": "Valoración no disponible.",
|
||||
"noProviderConfigured": "Conecta un proveedor de IA para ver aquí una valoración de tus lecturas.",
|
||||
"noProviderAction": "Abrir ajustes de IA",
|
||||
"startAnalysis": "Iniciar valoración",
|
||||
"refreshAnalysis": "Actualizar valoración",
|
||||
"keyTakeaway": "Lo más importante",
|
||||
@@ -2007,7 +2008,7 @@
|
||||
"emptyAction": "Generar resumen",
|
||||
"loadingLabel": "Preparando el resumen",
|
||||
"noProviderTitle": "Ningún proveedor de IA conectado",
|
||||
"noProviderDescription": "Conecta un proveedor de IA en los ajustes para recibir resúmenes diarios.",
|
||||
"noProviderDescription": "El briefing diario resume tus lecturas en lenguaje claro. HealthLog necesita un proveedor de IA para ello: usa tu propia clave (OpenAI, Anthropic) o un modelo local. Tus datos siguen siendo tuyos.",
|
||||
"noProviderAction": "Abrir ajustes de IA"
|
||||
},
|
||||
"trendsRow": {
|
||||
@@ -2072,6 +2073,8 @@
|
||||
"refusalInjection": "Tu mensaje contiene instrucciones que pretenden sobrescribir mis indicaciones. Soy el coach de HealthLog y solo resumo tus propios datos de salud — reformula tu pregunta dentro de ese marco.",
|
||||
"errorBudget": "Has alcanzado el límite diario del coach. El cupo se reinicia a medianoche (UTC).",
|
||||
"errorProvider": "El coach no ha podido contactar con un proveedor de análisis ahora mismo. Inténtalo en un momento.",
|
||||
"errorNoProvider": "El coach lee tus datos de salud registrados y responde preguntas sobre ellos (no es consejo médico). HealthLog necesita un proveedor de IA para ello: conecta tu propia clave o un modelo local.",
|
||||
"errorNoProviderAction": "Conectar un proveedor de IA",
|
||||
"errorNetwork": "Sin conexión a Internet — inténtalo de nuevo cuando vuelvas a estar en línea.",
|
||||
"prefsSaveError": "No se pudieron guardar tus preferencias del Coach. Inténtalo de nuevo.",
|
||||
"errorCredentialExpired": "Tu conexión con el proveedor de IA ha caducado. Vuelve a conectarla en Ajustes para que el Coach siga funcionando.",
|
||||
|
||||
+5
-2
@@ -1892,7 +1892,8 @@
|
||||
"bmiEmptyTitle": "Ajoute ta taille pour l’IMC",
|
||||
"bmiEmptyDescription": "L’IMC nécessite ta taille. Renseigne-la dans Paramètres → Compte.",
|
||||
"bmiEmptyAction": "Ouvrir les paramètres du compte",
|
||||
"noProviderConfigured": "Évaluation indisponible.",
|
||||
"noProviderConfigured": "Connecte un fournisseur d'IA pour voir ici une évaluation de tes mesures.",
|
||||
"noProviderAction": "Ouvrir les réglages IA",
|
||||
"startAnalysis": "Lancer l’évaluation",
|
||||
"refreshAnalysis": "Actualiser l’évaluation",
|
||||
"keyTakeaway": "L’essentiel",
|
||||
@@ -2007,7 +2008,7 @@
|
||||
"emptyAction": "Générer le briefing",
|
||||
"loadingLabel": "Préparation du briefing",
|
||||
"noProviderTitle": "Aucun fournisseur d'IA connecté",
|
||||
"noProviderDescription": "Connecte un fournisseur d'IA dans les réglages pour recevoir des briefings quotidiens.",
|
||||
"noProviderDescription": "Le briefing quotidien résume tes mesures en langage clair. HealthLog a besoin d'un fournisseur d'IA pour cela : utilise ta propre clé (OpenAI, Anthropic) ou un modèle local. Tes données restent chez toi.",
|
||||
"noProviderAction": "Ouvrir les réglages IA"
|
||||
},
|
||||
"trendsRow": {
|
||||
@@ -2072,6 +2073,8 @@
|
||||
"refusalInjection": "Ton message contient des instructions qui cherchent à remplacer mes consignes. Je suis le coach HealthLog et je ne résume que tes propres données de santé — reformule ta question dans ce cadre.",
|
||||
"errorBudget": "Tu as atteint la limite quotidienne du coach. Le quota se réinitialise à minuit (UTC).",
|
||||
"errorProvider": "Le coach n’a pas pu joindre un fournisseur d’analyse maintenant. Réessaie dans un instant.",
|
||||
"errorNoProvider": "Le coach lit tes données de santé enregistrées et répond aux questions à leur sujet (pas un avis médical). HealthLog a besoin d'un fournisseur d'IA pour cela : connecte ta propre clé ou un modèle local.",
|
||||
"errorNoProviderAction": "Connecter un fournisseur d'IA",
|
||||
"errorNetwork": "Pas de connexion Internet — réessaie une fois reconnecté.",
|
||||
"prefsSaveError": "Impossible d’enregistrer vos préférences du Coach. Réessayez.",
|
||||
"errorCredentialExpired": "Ta connexion au fournisseur d'IA a expiré. Reconnecte-la dans les Réglages pour que le Coach continue de fonctionner.",
|
||||
|
||||
+5
-2
@@ -1892,7 +1892,8 @@
|
||||
"bmiEmptyTitle": "Aggiungi l’altezza per il BMI",
|
||||
"bmiEmptyDescription": "Per il BMI serve la tua altezza. Salvala in Impostazioni → Account.",
|
||||
"bmiEmptyAction": "Apri impostazioni account",
|
||||
"noProviderConfigured": "Valutazione non disponibile.",
|
||||
"noProviderConfigured": "Collega un provider IA per vedere qui una valutazione delle tue misurazioni.",
|
||||
"noProviderAction": "Apri impostazioni IA",
|
||||
"startAnalysis": "Avvia valutazione",
|
||||
"refreshAnalysis": "Aggiorna valutazione",
|
||||
"keyTakeaway": "Ciò che conta",
|
||||
@@ -2007,7 +2008,7 @@
|
||||
"emptyAction": "Genera briefing",
|
||||
"loadingLabel": "Preparazione del briefing",
|
||||
"noProviderTitle": "Nessun provider IA collegato",
|
||||
"noProviderDescription": "Collega un provider IA nelle impostazioni per ricevere briefing giornalieri.",
|
||||
"noProviderDescription": "Il briefing giornaliero riassume le tue misurazioni in linguaggio chiaro. HealthLog ha bisogno di un provider IA per questo: usa la tua chiave (OpenAI, Anthropic) o un modello locale. I tuoi dati restano tuoi.",
|
||||
"noProviderAction": "Apri impostazioni IA"
|
||||
},
|
||||
"trendsRow": {
|
||||
@@ -2072,6 +2073,8 @@
|
||||
"refusalInjection": "Nel tuo messaggio ci sono istruzioni che cercano di sovrascrivere le mie indicazioni. Sono il coach di HealthLog e riassumo solo i tuoi dati di salute — riformula la domanda in questo perimetro.",
|
||||
"errorBudget": "Hai raggiunto il limite giornaliero del coach. Il budget si reimposta a mezzanotte (UTC).",
|
||||
"errorProvider": "Il coach non è riuscito a contattare un provider di analisi ora. Riprova fra poco.",
|
||||
"errorNoProvider": "Il coach legge i tuoi dati di salute registrati e risponde a domande su di essi (non è consulenza medica). HealthLog ha bisogno di un provider IA per questo: collega la tua chiave o un modello locale.",
|
||||
"errorNoProviderAction": "Collega un provider IA",
|
||||
"errorNetwork": "Nessuna connessione Internet — riprova quando sei di nuovo online.",
|
||||
"prefsSaveError": "Impossibile salvare le preferenze del Coach. Riprova.",
|
||||
"errorCredentialExpired": "La connessione al provider di IA è scaduta. Riconnettila nelle Impostazioni per mantenere attivo il Coach.",
|
||||
|
||||
+5
-2
@@ -1892,7 +1892,8 @@
|
||||
"bmiEmptyTitle": "Dodaj wzrost, aby obliczyć BMI",
|
||||
"bmiEmptyDescription": "BMI wymaga twojego wzrostu. Zapisz go w Ustawieniach → Konto.",
|
||||
"bmiEmptyAction": "Otwórz ustawienia konta",
|
||||
"noProviderConfigured": "Ocena niedostępna.",
|
||||
"noProviderConfigured": "Połącz dostawcę AI, aby zobaczyć tutaj ocenę swoich pomiarów.",
|
||||
"noProviderAction": "Otwórz ustawienia AI",
|
||||
"startAnalysis": "Uruchom ocenę",
|
||||
"refreshAnalysis": "Odśwież ocenę",
|
||||
"keyTakeaway": "Najważniejsze",
|
||||
@@ -2007,7 +2008,7 @@
|
||||
"emptyAction": "Wygeneruj briefing",
|
||||
"loadingLabel": "Przygotowanie briefingu",
|
||||
"noProviderTitle": "Brak połączonego dostawcy AI",
|
||||
"noProviderDescription": "Połącz dostawcę AI w ustawieniach, aby otrzymywać codzienne briefingi.",
|
||||
"noProviderDescription": "Codzienny briefing podsumowuje Twoje pomiary prostym językiem. HealthLog potrzebuje do tego dostawcy AI — użyj własnego klucza (OpenAI, Anthropic) lub modelu lokalnego. Twoje dane pozostają u Ciebie.",
|
||||
"noProviderAction": "Otwórz ustawienia AI"
|
||||
},
|
||||
"trendsRow": {
|
||||
@@ -2072,6 +2073,8 @@
|
||||
"refusalInjection": "Twoja wiadomość zawiera instrukcje, które próbują nadpisać moje wytyczne. Jestem coachem HealthLog i podsumowuję wyłącznie twoje własne dane zdrowotne — przeformułuj pytanie w tych ramach.",
|
||||
"errorBudget": "Osiągnięto dzienny limit coacha. Limit resetuje się o północy (UTC).",
|
||||
"errorProvider": "Coach nie mógł teraz skontaktować się z dostawcą analizy. Spróbuj za chwilę.",
|
||||
"errorNoProvider": "Coach czyta Twoje zapisane dane zdrowotne i odpowiada na pytania o nie (to nie porada medyczna). HealthLog potrzebuje do tego dostawcy AI — połącz własny klucz lub model lokalny.",
|
||||
"errorNoProviderAction": "Połącz dostawcę AI",
|
||||
"errorNetwork": "Brak połączenia z Internetem — spróbuj ponownie, gdy wrócisz online.",
|
||||
"prefsSaveError": "Nie udało się zapisać ustawień Coacha. Spróbuj ponownie.",
|
||||
"errorCredentialExpired": "Połączenie z dostawcą AI wygasło. Połącz je ponownie w Ustawieniach, aby Coach działał dalej.",
|
||||
|
||||
@@ -80,7 +80,11 @@ describe("<InsightStatusCard>", () => {
|
||||
text="ignored when hasProvider is false"
|
||||
/>,
|
||||
);
|
||||
expect(html).toContain("Assessment unavailable.");
|
||||
// v1.18.6 — the no-provider tile is a guided-setup explainer + a
|
||||
// Settings link, not a bare "unavailable" line.
|
||||
expect(html).toContain("Connect an AI provider");
|
||||
expect(html).toContain("Open AI settings");
|
||||
expect(html).toContain("/settings/ai");
|
||||
expect(html).not.toContain("ignored when hasProvider is false");
|
||||
});
|
||||
|
||||
|
||||
@@ -662,13 +662,20 @@ describe("errorCodeToI18nKey", () => {
|
||||
for (const code of [
|
||||
"coach.provider.unavailable",
|
||||
"coach.provider.empty",
|
||||
"coach.provider.none",
|
||||
"coach.stream",
|
||||
]) {
|
||||
expect(errorCodeToI18nKey(code)).toBe("insights.coach.errorProvider");
|
||||
}
|
||||
});
|
||||
|
||||
// v1.18.6 — a "no provider configured anywhere" turn is a setup gap,
|
||||
// not a transient failure, so it carries its own guided-setup copy.
|
||||
it("maps the no-provider case to its dedicated guided-setup copy", () => {
|
||||
expect(errorCodeToI18nKey("coach.provider.none")).toBe(
|
||||
"insights.coach.errorNoProvider",
|
||||
);
|
||||
});
|
||||
|
||||
// v1.4.43 QoL (M6) — `coach.network` no longer collapses to the
|
||||
// generic provider copy; the user needs the actionable offline hint.
|
||||
it("maps coach.network to the dedicated offline key", () => {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { Fragment, useEffect, useId, useMemo, useRef, useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { toast } from "sonner";
|
||||
import {
|
||||
@@ -168,9 +169,14 @@ export function errorCodeToI18nKey(code: string): string {
|
||||
// (auth-class failure). The next action is "reconnect", not "try
|
||||
// again later", so it carries its own copy pointing at Settings.
|
||||
return "insights.coach.errorCredentialExpired";
|
||||
case "coach.provider.none":
|
||||
// v1.18.6 — no AI provider is configured anywhere, so "try again
|
||||
// in a moment" is the wrong instruction (it will 422 forever). Send
|
||||
// the user to guided setup with a one-line explainer + a Settings
|
||||
// link rendered alongside the bubble copy.
|
||||
return "insights.coach.errorNoProvider";
|
||||
case "coach.provider.unavailable":
|
||||
case "coach.provider.empty":
|
||||
case "coach.provider.none":
|
||||
case "coach.stream":
|
||||
return "insights.coach.errorProvider";
|
||||
default:
|
||||
@@ -613,6 +619,19 @@ function ChatBubble({
|
||||
{safeError && content && (
|
||||
<p className="text-warning/90 text-xs">{safeError}</p>
|
||||
)}
|
||||
{/* v1.18.6 — a "no provider configured anywhere" turn is a
|
||||
setup gap, not a transient failure: surface a direct link to
|
||||
Settings → AI so the Coach guides the user into BYOK / local
|
||||
setup instead of inviting an endless retry. */}
|
||||
{errorCode === "coach.provider.none" && (
|
||||
<Link
|
||||
href="/settings/ai"
|
||||
data-slot="coach-no-provider-cta"
|
||||
className="text-primary text-xs font-medium underline-offset-4 hover:underline"
|
||||
>
|
||||
{t("insights.coach.errorNoProviderAction")}
|
||||
</Link>
|
||||
)}
|
||||
{/* v1.12.0 — collapse the whole provenance block ("what was
|
||||
included") behind one disclosure, collapsed by default. The
|
||||
source chips used to render fully expanded above the
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import Link from "next/link";
|
||||
|
||||
import { Card, CardContent, CardHeader } from "@/components/ui/card";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
@@ -144,14 +145,21 @@ export function InsightStatusCard({
|
||||
|
||||
if (!hasProvider) {
|
||||
return (
|
||||
<Card className="gap-1.5 py-4 opacity-60 md:py-5">
|
||||
<Card className="gap-1.5 py-4 opacity-80 md:py-5">
|
||||
<CardHeader className="pb-1">
|
||||
<TileHeader icon={nodeIcon(icon)} title={title} />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<CardContent className="flex flex-col gap-1.5">
|
||||
<p className="text-muted-foreground text-sm">
|
||||
{t("insights.noProviderConfigured")}
|
||||
</p>
|
||||
<Link
|
||||
href="/settings/ai"
|
||||
data-slot="insight-status-no-provider-cta"
|
||||
className="text-primary text-sm font-medium underline-offset-4 hover:underline"
|
||||
>
|
||||
{t("insights.noProviderAction")}
|
||||
</Link>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
|
||||
Reference in new issue
Block a user