frontend+backend: patients/settings UI, AI Auto/Off modes, wallet doc push

- patients: move status filter to its own "Filter" row above the table
- patient sheet: name + ⋯ menu on one left row; Edit moved into the ⋯ menu
- settings: redesign sections with the COSS CardFrame surface (SettingsFrame)
- AI: add Automatic (auto-pick provider) and Off modes; default to Automatic
  so a fresh install shows the setup banner until a provider is configured
- AI: fix the setup banner never showing (defaulted Ollama URL counted as
  configured); add an "AI off" notice; add a fallback render for unknown chat
  data parts so cards never silently vanish
- backend: include patient attachment metadata in the wallet record-update
  bundle so pushed documents reach the wallet
- i18n: add mode/off/card keys across all five locales

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Khalid Abdi
2026-07-13 20:22:36 +03:00
parent 17209a2cf1
commit b299501ab2
18 changed files with 421 additions and 205 deletions
+3 -1
View File
@@ -5,7 +5,9 @@ import type { Effort } from "@/lib/ai-models";
// saved to /api/ai/config. Provider API keys are write-only: they are never
// returned, only `apiKeySet` reports which providers have a stored key.
export type AiMode = "api" | "local";
// "auto" auto-picks a cloud provider when a key is set, else falls back to local
// Ollama; "off" disables the assistant entirely.
export type AiMode = "api" | "local" | "auto" | "off";
export type ApiProvider = "openai" | "anthropic" | "gemini";
export type VeilLevel = "off" | "names" | "full";
+14 -1
View File
@@ -376,6 +376,7 @@
"failedTitle": "تعذّر حذف المريض",
"failedBody": "يرجى المحاولة مرة أخرى."
},
"filterLabel": "تصفية",
"filterStatus": "تصفية حسب الحالة",
"allStatuses": "كل الحالات",
"moreActions": "إجراءات إضافية"
@@ -1149,7 +1150,12 @@
"title": "اربط نموذج ذكاء اصطناعي للبدء",
"body": "لم يتم إعداد أي مزوّد ذكاء اصطناعي بعد. أضف مفتاح API أو وجّه temetro إلى نموذج Ollama محلي حتى يتمكّن المساعد من الرد.",
"action": "فتح إعدادات الذكاء الاصطناعي",
"dismiss": "تجاهل"
"dismiss": "تجاهل",
"offTitle": "تم إيقاف مساعد الذكاء الاصطناعي",
"offBody": "الذكاء الاصطناعي مُعطّل في الإعدادات ← الذكاء الاصطناعي. فعّله لاستخدام المساعد."
},
"card": {
"unsupported": "بطاقة غير مدعومة"
},
"input": {
"placeholder": "اسأل أي شيء، أو اكتب /patient 10293",
@@ -2111,6 +2117,13 @@
"modeLocal": "نموذج محلي (Ollama)",
"modeApiHint": "تذهب الطلبات إلى مزوّدك المختار. تُزال هوية معرّفات المريض بواسطة Veil قبل مغادرتها.",
"modeLocalHint": "تعمل الطلبات مقابل نموذج على بنيتك التحتية الخاصة. لا تغادر بيانات المريض العيادة.",
"modeAuto": "تلقائي",
"modeOff": "إيقاف",
"modeAutoHint": "يستخدم مفتاح واجهة برمجة تطبيقات سحابي عند توفره، وإلا يعود إلى نموذج Ollama المحلي.",
"modeOffHint": "مساعد الذكاء الاصطناعي مُعطّل. لا تُرسل أي طلبات.",
"offTitle": "المساعد مُوقف",
"offDescription": "مساعد الذكاء الاصطناعي مُوقف لحسابك.",
"offNote": "اختر تلقائي أو مفتاح واجهة برمجة سحابي أو نموذج محلي أعلاه لتفعيل المساعد.",
"providerTitle": "المزوّد ومفتاح API",
"providerDescription": "يُشفَّر مفتاحك عند التخزين ولا يُعرض مرة أخرى أبدًا. يمكنك تخزين مفتاح لكل مزوّد والتبديل بينها.",
"provider": "المزوّد",
+14 -1
View File
@@ -364,6 +364,7 @@
"failedTitle": "Patient konnte nicht gelöscht werden",
"failedBody": "Bitte versuchen Sie es erneut."
},
"filterLabel": "Filter",
"filterStatus": "Nach Status filtern",
"allStatuses": "Alle Status",
"moreActions": "Weitere Aktionen"
@@ -1133,7 +1134,12 @@
"title": "Verbinden Sie ein KI-Modell, um zu beginnen",
"body": "Es ist noch kein KI-Anbieter eingerichtet. Fügen Sie einen API-Schlüssel hinzu oder verweisen Sie temetro auf ein lokales Ollama-Modell, damit der Assistent antworten kann.",
"action": "KI-Einstellungen öffnen",
"dismiss": "Ausblenden"
"dismiss": "Ausblenden",
"offTitle": "Der KI-Assistent ist ausgeschaltet",
"offBody": "KI ist unter Einstellungen → KI auf Aus gestellt. Schalten Sie sie ein, um den Assistenten zu nutzen."
},
"card": {
"unsupported": "Nicht unterstützte Karte"
},
"input": {
"placeholder": "Fragen Sie etwas oder tippen Sie /patient 10293",
@@ -2091,6 +2097,13 @@
"modeLocal": "Lokales Modell (Ollama)",
"modeApiHint": "Anfragen gehen an Ihren gewählten Anbieter. Patientenkennungen werden von Veil anonymisiert, bevor sie das System verlassen.",
"modeLocalHint": "Anfragen laufen gegen ein Modell auf Ihrer eigenen Infrastruktur. Keine Patientendaten verlassen die Klinik.",
"modeAuto": "Automatisch",
"modeOff": "Aus",
"modeAutoHint": "Verwendet einen Cloud-API-Schlüssel, sofern vorhanden, andernfalls Ihr lokales Ollama-Modell.",
"modeOffHint": "Der KI-Assistent ist deaktiviert. Es werden keine Anfragen gesendet.",
"offTitle": "Assistent aus",
"offDescription": "Der KI-Assistent ist für Ihr Konto ausgeschaltet.",
"offNote": "Wählen Sie oben Automatisch, Cloud-API-Schlüssel oder Lokales Modell, um den Assistenten zu aktivieren.",
"providerTitle": "Anbieter & API-Schlüssel",
"providerDescription": "Ihr Schlüssel wird verschlüsselt gespeichert und nie wieder angezeigt. Sie können einen Schlüssel pro Anbieter speichern und zwischen ihnen wechseln.",
"provider": "Anbieter",
+14 -1
View File
@@ -364,6 +364,7 @@
"failedTitle": "Couldn't delete patient",
"failedBody": "Please try again."
},
"filterLabel": "Filter",
"filterStatus": "Filter by status",
"allStatuses": "All statuses",
"moreActions": "More actions"
@@ -1133,7 +1134,12 @@
"title": "Connect an AI model to get started",
"body": "No AI provider is set up yet. Add an API key or point temetro at a local Ollama model so the assistant can answer.",
"action": "Open AI settings",
"dismiss": "Dismiss"
"dismiss": "Dismiss",
"offTitle": "The AI assistant is turned off",
"offBody": "AI is set to Off in Settings → AI. Turn it on to use the assistant."
},
"card": {
"unsupported": "Unsupported card"
},
"input": {
"placeholder": "Ask anything, or type /patient 10293",
@@ -2089,8 +2095,15 @@
"mode": "Mode",
"modeApi": "Cloud API key",
"modeLocal": "Local model (Ollama)",
"modeAuto": "Automatic",
"modeOff": "Off",
"modeApiHint": "Requests go to your chosen provider. Patient identifiers are de-identified by Veil before they leave.",
"modeLocalHint": "Requests run against a model on your own infrastructure. No patient data leaves the clinic.",
"modeAutoHint": "Uses a cloud API key when one is set, otherwise falls back to your local Ollama model.",
"modeOffHint": "The AI assistant is disabled. No requests are sent.",
"offTitle": "Assistant off",
"offDescription": "The AI assistant is turned off for your account.",
"offNote": "Choose Automatic, Cloud API key, or Local model above to enable the assistant.",
"providerTitle": "Provider & API key",
"providerDescription": "Your key is encrypted at rest and never shown again. You can store a key per provider and switch between them.",
"provider": "Provider",
+14 -1
View File
@@ -364,6 +364,7 @@
"failedTitle": "Impossible de supprimer le patient",
"failedBody": "Veuillez réessayer."
},
"filterLabel": "Filtre",
"filterStatus": "Filtrer par statut",
"allStatuses": "Tous les statuts",
"moreActions": "Plus dactions"
@@ -1133,7 +1134,12 @@
"title": "Connectez un modèle d'IA pour commencer",
"body": "Aucun fournisseur d'IA n'est encore configuré. Ajoutez une clé API ou pointez temetro vers un modèle Ollama local pour que l'assistant puisse répondre.",
"action": "Ouvrir les paramètres d'IA",
"dismiss": "Ignorer"
"dismiss": "Ignorer",
"offTitle": "L'assistant IA est désactivé",
"offBody": "L'IA est réglée sur Désactivée dans Paramètres → IA. Activez-la pour utiliser l'assistant."
},
"card": {
"unsupported": "Carte non prise en charge"
},
"input": {
"placeholder": "Demandez n'importe quoi, ou tapez /patient 10293",
@@ -2091,6 +2097,13 @@
"modeLocal": "Modèle local (Ollama)",
"modeApiHint": "Les requêtes vont vers le fournisseur que vous avez choisi. Les identifiants des patients sont dé-identifiés par Veil avant leur départ.",
"modeLocalHint": "Les requêtes s'exécutent sur un modèle de votre propre infrastructure. Aucune donnée patient ne quitte la clinique.",
"modeAuto": "Automatique",
"modeOff": "Désactivé",
"modeAutoHint": "Utilise une clé API cloud si elle est définie, sinon revient à votre modèle Ollama local.",
"modeOffHint": "L'assistant IA est désactivé. Aucune requête n'est envoyée.",
"offTitle": "Assistant désactivé",
"offDescription": "L'assistant IA est désactivé pour votre compte.",
"offNote": "Choisissez Automatique, Clé API cloud ou Modèle local ci-dessus pour activer l'assistant.",
"providerTitle": "Fournisseur & clé API",
"providerDescription": "Votre clé est chiffrée au repos et n'est plus jamais affichée. Vous pouvez stocker une clé par fournisseur et basculer entre elles.",
"provider": "Fournisseur",
+14 -1
View File
@@ -364,6 +364,7 @@
"failedTitle": "Bukaanka lama tirtiri karin",
"failedBody": "Fadlan isku day mar kale."
},
"filterLabel": "Kala sooc",
"filterStatus": "Ku kala sooc xaaladda",
"allStatuses": "Dhammaan xaaladaha",
"moreActions": "Ficillo dheeraad ah"
@@ -1133,7 +1134,12 @@
"title": "Ku xir moodeel AI si aad u bilowdo",
"body": "Weli bixiye AI lama dejin. Ku dar furaha API ama ku tilmaam temetro moodeel Ollama oo maxalli ah si kaaliyuhu u jawaabo.",
"action": "Fur dejinta AI",
"dismiss": "Rid"
"dismiss": "Rid",
"offTitle": "Kaaliyaha AI waa la damiyay",
"offBody": "AI waxaa loo dejiyay Damin gudaha Settings → AI. Shid si aad u isticmaasho kaaliyaha."
},
"card": {
"unsupported": "Kaadh aan la taageerin"
},
"input": {
"placeholder": "Waydii wax kasta, ama qor /patient 10293",
@@ -2091,6 +2097,13 @@
"modeLocal": "Moodeel maxalli ah (Ollama)",
"modeApiHint": "Codsiyada waxay u socdaan bixiyahaaga aad dooratay. Aqoonsiyada bukaanka waxaa qariya Veil ka hor inta aysan bixin.",
"modeLocalHint": "Codsiyada waxay ku socdaan moodeel kaabayaashaada ah. Xog bukaan kama baxdo rugta.",
"modeAuto": "Toos ah",
"modeOff": "Damin",
"modeAutoHint": "Wuxuu isticmaalaa furaha API-ga daruuraha marka la dejiyo, haddii kale wuxuu ku noqdaa moodelkaaga Ollama ee gudaha.",
"modeOffHint": "Kaaliyaha AI waa la damiyay. Wax codsi ah lama dirayo.",
"offTitle": "Kaaliyuhu waa damsan yahay",
"offDescription": "Kaaliyaha AI waa laga damiyay akoonkaaga.",
"offNote": "Dooro Toos ah, Furaha API daruuraha, ama Moodel Gudaha kor si aad u shidid kaaliyaha.",
"providerTitle": "Bixiye & furaha API",
"providerDescription": "Furahaaga waa la shifraa marka la kaydiyo mana dib loo muujiyo. Waxaad kaydin kartaa fur bixiye kasta oo aad kala beddeli kartaa.",
"provider": "Bixiye",