File size: 7,511 Bytes
5b91be2
2381bf9
 
 
 
 
 
 
 
 
703c1e7
c564692
7c8aa49
d0c93af
 
8c33138
5d21b80
d0c93af
 
 
 
 
 
 
 
 
 
 
 
 
703c1e7
d0c93af
 
 
2381bf9
d0c93af
 
 
5d21b80
2381bf9
8c33138
d0c93af
 
 
 
2381bf9
5d21b80
d0c93af
703c1e7
5d21b80
 
 
 
 
 
8c33138
703c1e7
5d21b80
 
 
8c33138
 
 
 
 
 
 
 
 
 
 
 
 
5d21b80
8c33138
 
 
5d21b80
8c33138
703c1e7
8c33138
5d21b80
 
 
 
d0c93af
5d21b80
d0c93af
 
 
 
5b91be2
d0c93af
 
 
 
 
 
 
 
5d21b80
8c33138
d0c93af
5d21b80
d0c93af
5d21b80
d0c93af
 
703c1e7
 
 
 
 
c564692
5d21b80
 
 
703c1e7
2381bf9
8c33138
 
703c1e7
8c33138
c564692
 
 
 
5d21b80
 
703c1e7
d0c93af
703c1e7
d0c93af
 
5d21b80
d0c93af
 
 
 
 
 
 
 
8c33138
 
2381bf9
8c33138
88ba2a8
 
 
8c33138
 
 
 
5d21b80
d0c93af
8c33138
9f9e436
8c33138
d0c93af
 
 
 
2381bf9
d0c93af
 
2381bf9
9f9e436
2381bf9
 
 
 
8c33138
d0c93af
2381bf9
5b91be2
 
5d21b80
 
8c33138
c564692
 
 
 
5d21b80
8c33138
5d21b80
 
d0c93af
 
5d21b80
d0c93af
 
 
 
 
 
 
5d21b80
d0c93af
 
 
 
 
 
703c1e7
d0c93af
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
import streamlit as st
import warnings
import os
from pathlib import Path

# Suppress warnings yang tidak perlu
warnings.filterwarnings("ignore", category=UserWarning)
warnings.filterwarnings("ignore", message=".*torchvision.*")
warnings.filterwarnings("ignore", message=".*UNEXPECTED.*")

from google import genai
from google.genai import types   # ← Penting
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_community.vectorstores import FAISS
from langchain_huggingface import HuggingFaceEmbeddings
from langchain_core.documents import Document

# ================== CONFIG ==================
st.set_page_config(
    page_title="Human+ Lab-to-Protocol AI",
    page_icon="🧬",
    layout="centered"
)

st.title("🧬 Human+ Lab-to-Protocol AI")
st.markdown("""
**Upload hasil lab Anda β†’ Dapatkan rekomendasi IV drip & protocol personal**  
*Semua data diproses sementara dan hilang setelah sesi selesai (privacy-first)*
""")

# ================== GEMINI CLIENT ==================
GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")

if not GEMINI_API_KEY:
    st.error("❌ GEMINI_API_KEY tidak ditemukan. Tambahkan di Settings β†’ Secrets.")
    st.stop()

try:
    client = genai.Client(api_key=GEMINI_API_KEY)
    MODEL_NAME = "gemini-2.5-flash"
    st.success("βœ… Gemini client siap")
except Exception as e:
    st.error(f"Gagal menginisialisasi Gemini: {str(e)}")
    st.stop()

# ================== LOAD KNOWLEDGE BASE ==================
@st.cache_resource(show_spinner=False, ttl=7200)
def load_knowledge_base():
    with st.spinner("⏳ Memuat knowledge base Human+..."):
        try:
            knowledge_dir = Path("/app/knowledge")
            if not knowledge_dir.exists():
                st.error(f"❌ Folder knowledge tidak ditemukan di: {knowledge_dir}")
                return None

            documents = []
            for file_path in knowledge_dir.glob("*.md"):
                with open(file_path, "r", encoding="utf-8") as f:
                    content = f.read().strip()
                    if content:
                        documents.append(
                            Document(
                                page_content=content,
                                metadata={"source": file_path.name}
                            )
                        )

            text_splitter = RecursiveCharacterTextSplitter(
                chunk_size=800,
                chunk_overlap=150,
                separators=["\n\n## ", "\n\n### ", "\n\n", "\n", " ", "."]
            )
            chunks = text_splitter.split_documents(documents)

            embeddings = HuggingFaceEmbeddings(
                model_name="sentence-transformers/all-mpnet-base-v2"
            )

            vector_store = FAISS.from_documents(chunks, embeddings)
            
            st.success(f"βœ… Knowledge base siap ({len(documents)} file β€’ {len(chunks)} chunks)")
            return vector_store

        except Exception as e:
            st.error(f"Gagal memuat knowledge base: {str(e)}")
            return None


vector_store = load_knowledge_base()
if vector_store is None:
    st.stop()

# ================== UPLOAD & ANALYSIS ==================
uploaded_file = st.file_uploader(
    "Upload hasil lab (PDF)", 
    type=["pdf"],
    help="Maksimal 10MB. Semua data hanya diproses sementara."
)

if uploaded_file:
    if uploaded_file.size > 10 * 1024 * 1024:
        st.error("❌ File terlalu besar. Maksimal 10MB.")
        st.stop()

    pdf_bytes = uploaded_file.read()

    with st.spinner("Menganalisis hasil lab menggunakan Gemini..."):
        try:
            pdf_part = types.Part.from_bytes(
                data=pdf_bytes,
                mime_type="application/pdf"
            )

            # Perbaikan utama: gunakan config=GenerateContentConfig
            response = client.models.generate_content(
                model=MODEL_NAME,
                contents=[
                    "Extract semua biomarker penting dari hasil lab PDF ini dalam format JSON yang terstruktur. "
                    "Sertakan nama tes, nilai, unit, reference range, dan flag (high/low/normal). "
                    "Fokus pada marker longevity: hs-CRP, Homocysteine, Vitamin D, HbA1c, Testosterone, "
                    "Magnesium, Zinc, B12, Ferritin, ApoB, dll.",
                    pdf_part
                ],
                config=types.GenerateContentConfig(
                    temperature=0.1,
                    max_output_tokens=4096
                )
            )

            extracted_text = response.text
            st.success("βœ… Lab berhasil dianalisis!")
            
            with st.expander("πŸ“‹ Hasil Ekstraksi Biomarker"):
                st.markdown(extracted_text)

        except Exception as e:
            st.error(f"Gagal menganalisis PDF: {str(e)}")
            st.stop()

    # ================== GENERATE PROTOCOL ==================
    if st.button("πŸ”¬ Generate Human+ Personalized Protocol", type="primary"):
        with st.spinner("Membuat rekomendasi protocol hyperpersonalized..."):
            try:
                retriever = vector_store.as_retriever(
                    search_type="mmr",
                    search_kwargs={"k": 8, "fetch_k": 20, "lambda_mult": 0.7}
                )
                
                relevant_docs = retriever.invoke(extracted_text)
                
                context = "\n\n".join([
                    f"[Sumber: {doc.metadata.get('source', 'unknown')}] {doc.page_content}"
                    for doc in relevant_docs
                ])

                prompt = f"""
Kamu adalah AI Longevity Specialist resmi dari **Human+ Bali**, didirikan oleh Benjamin White (@bennywhitethatsright).

Filosofi utama: **"From Injured β†’ Optimized"**

Hasil lab pasien:
{extracted_text}

Knowledge base Human+:
{context}

Buatlah rekomendasi protocol yang jelas, positif, empowering, dan actionable dengan struktur berikut:

1. **Ringkasan Temuan Utama** (apa yang paling perlu dioptimalkan + motivasi)
2. **Rekomendasi IV Drip** (The All-In atau varian custom + komposisi)
3. **Suplemen Harian** (dosis & timing)
4. **Lifestyle & Recovery Protocol** (ice bath, structured water, breathing, sunlight)
5. **Jadwal Retest & Next Step**

Gunakan bahasa Indonesia yang mudah dipahami dan sesuai voice Benjamin White. Akhiri dengan semangat "From Injured β†’ Optimized".
"""

                response = client.models.generate_content(
                    model=MODEL_NAME,
                    contents=[prompt],
                    config=types.GenerateContentConfig(
                        temperature=0.4,
                        max_output_tokens=4096
                    )
                )
                
                protocol = response.text

                st.subheader("πŸ“‹ Rekomendasi Protocol Personal dari Human+")
                st.markdown(protocol)

                report_text = f"Human+ Lab-to-Protocol Report\n\n{protocol}"
                st.download_button(
                    label="πŸ“₯ Download Report",
                    data=report_text,
                    file_name="Human+_Protocol_Report.txt",
                    mime="text/plain"
                )

            except Exception as e:
                st.error(f"Gagal generate protocol: {str(e)}")

# Footer
st.divider()
st.caption("""
**Privacy Note**: Aplikasi ini 100% stateless. Tidak ada data yang disimpan di server.  
Dibuat untuk Human+ Bali β€’ From Injured β†’ Optimized
""")