Generalize from health-specific to universal personal assistant
The app manages multiple life areas (health, finance, personal, work), not just health. Updated all health-specific language throughout: Backend: - Default system prompt: general personal assistant (not health-only) - AI tool descriptions: generic (not health records/medications) - Memory categories: health, finance, personal, work, document_summary, other (replaces condition, medication, allergy, vital) - PDF template: "Prepared for" (not "Patient"), "Key Information" (not "Health Profile") - Renamed generate_health_pdf -> generate_pdf_report, health_report.html -> report.html - Renamed run_daily_health_review -> run_daily_review - Context assembly: "User Profile" (not "Health Profile") - OpenAPI: generic descriptions Frontend: - Dashboard subtitle: "Your personal AI assistant" - Memory categories: Health, Finance, Personal, Work - Document types: Report, Contract, Receipt, Certificate (not lab_result, etc.) - Updated en + ru translations throughout Documentation: - README: general personal assistant description - Removed health-only feature descriptions Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
12
README.md
12
README.md
@@ -1,14 +1,14 @@
|
||||
# Personal AI Assistant
|
||||
|
||||
A client-server web application for managing personal health and life areas with AI-powered assistance. Upload documents, chat with AI specialists, receive proactive health reminders, and track critical information across multiple life domains.
|
||||
A client-server web application for managing different areas of personal life with AI-powered assistance. Upload documents, chat with AI specialists, receive proactive reminders, and track critical information across multiple life domains (health, finance, personal, work, etc.).
|
||||
|
||||
## Key Features
|
||||
|
||||
- **AI Chat with Specialists** — create chats using configurable skills (e.g., cardiologist, nutritionist). Each skill shapes the AI's behavior as a domain expert.
|
||||
- **Document Management** — upload health records, lab results, prescriptions, and consultation notes. AI extracts and indexes content for intelligent retrieval.
|
||||
- **Proactive Notifications** — AI analyzes your health profile and schedules reminders (checkups, medication reviews) via in-app, email, or Telegram.
|
||||
- **PDF Compilation** — request AI-generated health summaries as downloadable PDF documents.
|
||||
- **Global Memory** — AI maintains a shared memory of critical health information across all your chats.
|
||||
- **AI Chat with Specialists** — create chats using configurable skills (e.g., doctor, financial advisor, personal coach). Each skill shapes the AI's behavior as a domain expert.
|
||||
- **Document Management** — upload documents (records, reports, contracts, notes, etc.). AI extracts and indexes content for intelligent retrieval.
|
||||
- **Proactive Notifications** — AI analyzes your stored data and schedules reminders (deadlines, follow-ups, recurring events) via in-app, email, or Telegram.
|
||||
- **PDF Compilation** — request AI-generated summaries and reports as downloadable PDF documents.
|
||||
- **Global Memory** — AI maintains a shared memory of critical information across all your chats.
|
||||
- **Multi-language** — English and Russian support.
|
||||
|
||||
## Tech Stack
|
||||
|
||||
@@ -21,7 +21,7 @@ async def compile_pdf(
|
||||
user: Annotated[User, Depends(get_current_user)],
|
||||
db: Annotated[AsyncSession, Depends(get_db)],
|
||||
):
|
||||
pdf = await pdf_service.generate_health_pdf(
|
||||
pdf = await pdf_service.generate_pdf_report(
|
||||
db, user.id, data.title, data.document_ids or None, data.chat_id,
|
||||
)
|
||||
return PdfResponse.model_validate(pdf)
|
||||
|
||||
@@ -32,7 +32,7 @@ async def lifespan(app: FastAPI):
|
||||
def create_app() -> FastAPI:
|
||||
app = FastAPI(
|
||||
title="AI Assistant API",
|
||||
description="Personal AI health assistant with document management, chat, and notifications.",
|
||||
description="Personal AI assistant with document management, chat, memory, and notifications.",
|
||||
version="0.1.0",
|
||||
lifespan=lifespan,
|
||||
docs_url="/api/docs" if settings.DOCS_ENABLED else None,
|
||||
@@ -41,8 +41,8 @@ def create_app() -> FastAPI:
|
||||
openapi_tags=[
|
||||
{"name": "auth", "description": "Authentication and registration"},
|
||||
{"name": "chats", "description": "AI chat conversations"},
|
||||
{"name": "documents", "description": "Health document management"},
|
||||
{"name": "memory", "description": "Health memory entries"},
|
||||
{"name": "documents", "description": "Document management"},
|
||||
{"name": "memory", "description": "Memory entries"},
|
||||
{"name": "skills", "description": "AI specialist skills"},
|
||||
{"name": "notifications", "description": "User notifications"},
|
||||
{"name": "pdf", "description": "PDF report generation"},
|
||||
|
||||
@@ -4,7 +4,7 @@ from typing import Literal
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
CategoryType = Literal["condition", "medication", "allergy", "vital", "document_summary", "other"]
|
||||
CategoryType = Literal["health", "finance", "personal", "work", "document_summary", "other"]
|
||||
ImportanceType = Literal["critical", "high", "medium", "low"]
|
||||
|
||||
|
||||
|
||||
@@ -24,13 +24,13 @@ client = AsyncAnthropic(api_key=settings.ANTHROPIC_API_KEY)
|
||||
AI_TOOLS = [
|
||||
{
|
||||
"name": "save_memory",
|
||||
"description": "Save important health information to the user's memory. Use this when the user shares critical health data like conditions, medications, allergies, or important health facts.",
|
||||
"description": "Save important information to the user's memory. Use this when the user shares critical personal data, facts, preferences, or key details they want to remember across conversations.",
|
||||
"input_schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"category": {
|
||||
"type": "string",
|
||||
"enum": ["condition", "medication", "allergy", "vital", "document_summary", "other"],
|
||||
"enum": ["health", "finance", "personal", "work", "document_summary", "other"],
|
||||
"description": "Category of the memory entry",
|
||||
},
|
||||
"title": {"type": "string", "description": "Short title for the memory entry"},
|
||||
@@ -46,7 +46,7 @@ AI_TOOLS = [
|
||||
},
|
||||
{
|
||||
"name": "search_documents",
|
||||
"description": "Search the user's uploaded health documents for relevant information. Use this when you need to find specific health records, lab results, or consultation notes.",
|
||||
"description": "Search the user's uploaded documents for relevant information. Use this when you need to find specific records, files, or notes the user has uploaded.",
|
||||
"input_schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -57,13 +57,13 @@ AI_TOOLS = [
|
||||
},
|
||||
{
|
||||
"name": "get_memory",
|
||||
"description": "Retrieve the user's stored health memories filtered by category. Use this to recall previously saved health information.",
|
||||
"description": "Retrieve the user's stored memories filtered by category. Use this to recall previously saved information.",
|
||||
"input_schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"category": {
|
||||
"type": "string",
|
||||
"enum": ["condition", "medication", "allergy", "vital", "document_summary", "other"],
|
||||
"enum": ["health", "finance", "personal", "work", "document_summary", "other"],
|
||||
"description": "Optional category filter. Omit to get all memories.",
|
||||
},
|
||||
},
|
||||
@@ -94,7 +94,7 @@ AI_TOOLS = [
|
||||
},
|
||||
{
|
||||
"name": "generate_pdf",
|
||||
"description": "Generate a PDF health report compilation from the user's data. Use this when the user asks for a document or summary of their health information.",
|
||||
"description": "Generate a PDF report compilation from the user's data. Use this when the user asks for a document or summary of their stored information.",
|
||||
"input_schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -173,8 +173,8 @@ async def _execute_tool(
|
||||
})
|
||||
|
||||
elif tool_name == "generate_pdf":
|
||||
from app.services.pdf_service import generate_health_pdf
|
||||
pdf = await generate_health_pdf(db, user_id, title=tool_input["title"])
|
||||
from app.services.pdf_service import generate_pdf_report
|
||||
pdf = await generate_pdf_report(db, user_id, title=tool_input["title"])
|
||||
await db.commit()
|
||||
return json.dumps({
|
||||
"status": "generated",
|
||||
@@ -213,7 +213,7 @@ async def assemble_context(
|
||||
memories = await get_critical_memories(db, user_id)
|
||||
if memories:
|
||||
memory_lines = [f"- [{m.category}] {m.title}: {m.content}" for m in memories]
|
||||
system_parts.append(f"---\nUser Health Profile:\n" + "\n".join(memory_lines))
|
||||
system_parts.append(f"---\nUser Profile (Key Information):\n" + "\n".join(memory_lines))
|
||||
|
||||
# 5. Relevant document excerpts (based on user message keywords)
|
||||
if user_message.strip():
|
||||
|
||||
@@ -5,14 +5,14 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.models.context_file import ContextFile
|
||||
|
||||
DEFAULT_SYSTEM_PROMPT = """You are a personal AI health assistant. Your role is to:
|
||||
- Help users understand their health data and medical documents
|
||||
- Provide health-related recommendations based on uploaded information
|
||||
- Schedule reminders for checkups, medications, and health-related activities
|
||||
- Compile health summaries when requested
|
||||
- Answer health questions clearly and compassionately
|
||||
DEFAULT_SYSTEM_PROMPT = """You are a personal AI assistant helping users manage different areas of their life. Your role is to:
|
||||
- Help users organize and understand their uploaded documents and data
|
||||
- Provide recommendations and insights based on stored information
|
||||
- Schedule reminders for important events, deadlines, and recurring activities
|
||||
- Compile summaries and reports when requested
|
||||
- Answer questions clearly and helpfully
|
||||
|
||||
Always be empathetic, accurate, and clear. When uncertain, recommend consulting a healthcare professional.
|
||||
Always be empathetic, accurate, and clear. When uncertain about specialized topics, recommend consulting a relevant professional.
|
||||
You can communicate in English and Russian based on the user's preference."""
|
||||
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ jinja_env = Environment(
|
||||
)
|
||||
|
||||
|
||||
async def generate_health_pdf(
|
||||
async def generate_pdf_report(
|
||||
db: AsyncSession,
|
||||
user_id: uuid.UUID,
|
||||
title: str,
|
||||
@@ -53,7 +53,7 @@ async def generate_health_pdf(
|
||||
})
|
||||
|
||||
# Render HTML
|
||||
template = jinja_env.get_template("health_report.html")
|
||||
template = jinja_env.get_template("report.html")
|
||||
html = template.render(
|
||||
title=title,
|
||||
user_name=user.full_name or user.username,
|
||||
|
||||
@@ -16,11 +16,11 @@ def start_scheduler():
|
||||
replace_existing=True,
|
||||
)
|
||||
|
||||
from app.workers.health_review import run_daily_health_review
|
||||
from app.workers.health_review import run_daily_review
|
||||
scheduler.add_job(
|
||||
run_daily_health_review,
|
||||
run_daily_review,
|
||||
trigger=CronTrigger(hour=8, minute=0),
|
||||
id="daily_health_review",
|
||||
id="daily_review",
|
||||
replace_existing=True,
|
||||
)
|
||||
|
||||
|
||||
@@ -26,13 +26,13 @@
|
||||
<body>
|
||||
<div class="header">
|
||||
<h1>{{ title }}</h1>
|
||||
<p>Patient: {{ user_name }}</p>
|
||||
<p>Prepared for: {{ user_name }}</p>
|
||||
<p>Generated: {{ generated_at }}</p>
|
||||
</div>
|
||||
|
||||
{% if memories %}
|
||||
<div class="section">
|
||||
<h2>Health Profile</h2>
|
||||
<h2>Key Information</h2>
|
||||
{% for m in memories %}
|
||||
<div class="memory-entry">
|
||||
<span class="category">{{ m.category }}</span>
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Daily job: proactive health review for all users with health data."""
|
||||
"""Daily job: proactive review for all users with stored data."""
|
||||
import asyncio
|
||||
import json
|
||||
import logging
|
||||
@@ -17,8 +17,8 @@ logger = logging.getLogger(__name__)
|
||||
client = AsyncAnthropic(api_key=settings.ANTHROPIC_API_KEY)
|
||||
|
||||
|
||||
async def run_daily_health_review():
|
||||
"""Review each user's health profile and generate reminder notifications."""
|
||||
async def run_daily_review():
|
||||
"""Review each user's profile and generate reminder notifications."""
|
||||
if not settings.ANTHROPIC_API_KEY:
|
||||
return
|
||||
|
||||
@@ -31,7 +31,7 @@ async def run_daily_health_review():
|
||||
await _review_user(user)
|
||||
await asyncio.sleep(1) # Rate limit
|
||||
except Exception:
|
||||
logger.exception(f"Health review failed for user {user.id}")
|
||||
logger.exception(f"Daily review failed for user {user.id}")
|
||||
|
||||
|
||||
async def _review_user(user: User):
|
||||
@@ -45,13 +45,12 @@ async def _review_user(user: User):
|
||||
response = await client.messages.create(
|
||||
model=settings.CLAUDE_MODEL,
|
||||
max_tokens=500,
|
||||
system="You are a health assistant. Based on the user's health profile, suggest any upcoming checkups, medication reviews, or health actions that should be reminded. Respond with a JSON array of objects with 'title' and 'body' fields. If no reminders are needed, return an empty array [].",
|
||||
messages=[{"role": "user", "content": f"User health profile:\n{memory_text}"}],
|
||||
system="You are a personal assistant. Based on the user's stored information, suggest any upcoming actions, deadlines, follow-ups, or reminders that would be helpful. Respond with a JSON array of objects with 'title' and 'body' fields. If no reminders are needed, return an empty array [].",
|
||||
messages=[{"role": "user", "content": f"User profile data:\n{memory_text}"}],
|
||||
)
|
||||
|
||||
try:
|
||||
text = response.content[0].text.strip()
|
||||
# Extract JSON from response
|
||||
if "[" in text:
|
||||
json_str = text[text.index("["):text.rindex("]") + 1]
|
||||
reminders = json.loads(json_str)
|
||||
@@ -60,7 +59,7 @@ async def _review_user(user: User):
|
||||
except (json.JSONDecodeError, ValueError):
|
||||
return
|
||||
|
||||
for reminder in reminders[:5]: # Max 5 reminders per user per day
|
||||
for reminder in reminders[:5]:
|
||||
if "title" in reminder and "body" in reminder:
|
||||
notif = await create_notification(
|
||||
db,
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
},
|
||||
"dashboard": {
|
||||
"welcome": "Welcome, {{name}}",
|
||||
"subtitle": "Your personal AI health assistant"
|
||||
"subtitle": "Your personal AI assistant"
|
||||
},
|
||||
"chat": {
|
||||
"new_chat": "New Chat",
|
||||
@@ -116,10 +116,10 @@
|
||||
"clear_search": "Clear",
|
||||
"types": {
|
||||
"other": "Other",
|
||||
"lab_result": "Lab Result",
|
||||
"consultation": "Consultation",
|
||||
"prescription": "Prescription",
|
||||
"imaging": "Imaging"
|
||||
"report": "Report",
|
||||
"contract": "Contract",
|
||||
"receipt": "Receipt",
|
||||
"certificate": "Certificate"
|
||||
},
|
||||
"status": {
|
||||
"pending": "Pending",
|
||||
@@ -131,7 +131,7 @@
|
||||
"memory": {
|
||||
"create": "Add Memory Entry",
|
||||
"edit": "Edit Memory Entry",
|
||||
"no_entries": "No memory entries yet. The AI will save important health information here.",
|
||||
"no_entries": "No memory entries yet. The AI will save important information here.",
|
||||
"category": "Category",
|
||||
"importance": "Importance",
|
||||
"title_field": "Title",
|
||||
@@ -139,10 +139,10 @@
|
||||
"content_field": "Content",
|
||||
"content_placeholder": "Detailed information...",
|
||||
"categories": {
|
||||
"condition": "Condition",
|
||||
"medication": "Medication",
|
||||
"allergy": "Allergy",
|
||||
"vital": "Vital Sign",
|
||||
"health": "Health",
|
||||
"finance": "Finance",
|
||||
"personal": "Personal",
|
||||
"work": "Work",
|
||||
"document_summary": "Document Summary",
|
||||
"other": "Other"
|
||||
},
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
},
|
||||
"dashboard": {
|
||||
"welcome": "Добро пожаловать, {{name}}",
|
||||
"subtitle": "Ваш персональный ИИ-ассистент по здоровью"
|
||||
"subtitle": "Ваш персональный ИИ-ассистент"
|
||||
},
|
||||
"chat": {
|
||||
"new_chat": "Новый чат",
|
||||
@@ -116,10 +116,10 @@
|
||||
"clear_search": "Очистить",
|
||||
"types": {
|
||||
"other": "Другое",
|
||||
"lab_result": "Анализы",
|
||||
"consultation": "Консультация",
|
||||
"prescription": "Рецепт",
|
||||
"imaging": "Снимки"
|
||||
"report": "Отчёт",
|
||||
"contract": "Договор",
|
||||
"receipt": "Квитанция",
|
||||
"certificate": "Сертификат"
|
||||
},
|
||||
"status": {
|
||||
"pending": "Ожидание",
|
||||
@@ -131,7 +131,7 @@
|
||||
"memory": {
|
||||
"create": "Добавить запись",
|
||||
"edit": "Редактировать запись",
|
||||
"no_entries": "Записей пока нет. ИИ будет сохранять важную информацию о здоровье здесь.",
|
||||
"no_entries": "Записей пока нет. ИИ будет сохранять важную информацию здесь.",
|
||||
"category": "Категория",
|
||||
"importance": "Важность",
|
||||
"title_field": "Заголовок",
|
||||
@@ -139,10 +139,10 @@
|
||||
"content_field": "Содержание",
|
||||
"content_placeholder": "Подробная информация...",
|
||||
"categories": {
|
||||
"condition": "Заболевание",
|
||||
"medication": "Лекарство",
|
||||
"allergy": "Аллергия",
|
||||
"vital": "Показатели",
|
||||
"health": "Здоровье",
|
||||
"finance": "Финансы",
|
||||
"personal": "Личное",
|
||||
"work": "Работа",
|
||||
"document_summary": "Сводка документа",
|
||||
"other": "Другое"
|
||||
},
|
||||
|
||||
@@ -9,7 +9,7 @@ interface UploadDialogProps {
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
const DOC_TYPES = ["other", "lab_result", "consultation", "prescription", "imaging"];
|
||||
const DOC_TYPES = ["other", "report", "contract", "receipt", "certificate"];
|
||||
|
||||
export function UploadDialog({ open, onClose }: UploadDialogProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useState, useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import type { MemoryEntry } from "@/api/memory";
|
||||
|
||||
const CATEGORIES = ["condition", "medication", "allergy", "vital", "document_summary", "other"];
|
||||
const CATEGORIES = ["health", "finance", "personal", "work", "document_summary", "other"];
|
||||
const IMPORTANCE_LEVELS = ["critical", "high", "medium", "low"];
|
||||
|
||||
interface MemoryEditorProps {
|
||||
|
||||
Reference in New Issue
Block a user