File size: 9,185 Bytes
067a67a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f154785
067a67a
 
f154785
067a67a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f154785
 
067a67a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5f8d363
 
 
 
 
 
 
 
 
 
 
 
 
 
067a67a
 
f154785
067a67a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f154785
 
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
209
210
211
212
213
214
215
216
217
218
219
220
221
222
import os
import urllib.request
import nest_asyncio
import gradio as gr
import openai
from llama_parse import LlamaParse
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader, Settings
from llama_index.llms.openai import OpenAI
from llama_index.embeddings.openai import OpenAIEmbedding

# Apply nest_asyncio to allow nested event loops in a synchronous environment
nest_asyncio.apply()

# --- 1. ENVIRONMENT & API INITIALIZATION ---
OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY")
LLAMA_CLOUD_API_KEY = os.environ.get("LLAMA_CLOUD_API_KEY")

if not OPENAI_API_KEY or not LLAMA_CLOUD_API_KEY:
    raise ValueError("Missing essential API credentials. Ensure OPENAI_API_KEY and LLAMA_CLOUD_API_KEY are configured in Secrets.")

openai.api_key = OPENAI_API_KEY
os.environ["LLAMA_CLOUD_API_KEY"] = LLAMA_CLOUD_API_KEY

# --- 2. DATA ACQUISITION & PARSING ---
pdf_path = "apple_10k.pdf"
url = "https://s2.q4cdn.com/470004039/files/doc_financials/2021/q4/_10-K-2021-(As-Filed).pdf"

if not os.path.exists(pdf_path):
    print("Downloading Apple 10-K report...")
    urllib.request.urlretrieve(url, pdf_path)

print("Parsing document via LlamaParse...")
parser = LlamaParse(result_type="markdown")
document = parser.load_data(pdf_path)

with open("apple_10k.md", "w", encoding="utf-8") as f:
    f.write(document[0].text)

# FIXED: Changed deprecated 'parsing_instruction' to 'system_prompt'
documents_with_instruction = LlamaParse(
    result_type="markdown",
    system_prompt="This is the Apple annual report. Make sure the language is English, if not translate it to English."
).load_data(pdf_path)

with open("apple_10k_instructions.md", "w", encoding="utf-8") as f:
    f.write(documents_with_instruction[0].text)

# --- 3. LLAMAINDEX RAG CONFIGURATION ---
Settings.llm = OpenAI(
    model="gpt-5-nano",
    temperature=0.1,
    system_prompt=(
        "You are an expert financial analyst. Your task is to answer questions strictly "
        "and accurately based on the provided Apple 10-K report. Provide concise answers, "
        "directly referencing sections or figures from the report where possible. "
        "If the information is not explicitly present in the document, "
        "clearly state that the answer cannot be found in the provided text."
    )
)
Settings.embed_model = OpenAIEmbedding(model="text-embedding-3-small")
Settings.chunk_size = 512

reader = SimpleDirectoryReader(input_files=["apple_10k.md", "apple_10k_instructions.md"])
docs = reader.load_data()
index = VectorStoreIndex.from_documents(docs)
query_engine = index.as_query_engine(similarity_top_k=5)

print("LlamaIndex Knowledge Base initialized.")

# --- 4. GRADIO INTERFACE CONFIGURATION ---
custom_css = """
.gradio-container {
    font-family: 'Inter', 'Helvetica Neue', sans-serif !important;
    background: linear-gradient(135deg, #f3f4f6 0%, #e5e7eb 100%);
}
.sidebar-panel {
    background: white;
    border-radius: 16px;
    padding: 20px;
    box-shadow: 0 10px 25px rgba(0,0,0,0.05);
    border: 1px solid #e5e7eb;
}
.gradient-text {
    background: linear-gradient(90deg, #1d4ed8, #9333ea);
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
    font-weight: 800;
}
.stat-card {
    background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
    color: white;
    padding: 20px;
    border-radius: 12px;
    margin-bottom: 15px;
    text-align: center;
    box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}
.action-button {
    background: linear-gradient(90deg, #3b82f6, #2563eb);
    border: none;
    color: white;
    font-weight: bold;
}
"""

def chat_with_financial_analyst(user_message, history):
    if not user_message.strip():
        return "", history

    history.append({"role": "user", "content": user_message})
    history.append({"role": "assistant", "content": "Calculating financials..."})
    yield "", history

    try:
        response = query_engine.query(user_message)
        final_answer = str(response)
    except Exception as e:
        final_answer = f"⚠️ **Error querying the document:** {str(e)}"

    history[-1]["content"] = final_answer
    yield "", history

def load_quick_prompt(prompt, history):
    for text, hist in chat_with_financial_analyst(prompt, history):
        pass
    return "", hist

# FIXED: Removed 'css=custom_css' from gr.Blocks() (Moved to app.launch())
with gr.Blocks(fill_width=True) as app:
    with gr.Row():
        gr.HTML("""
        <div style='text-align: center; padding: 20px 0;'>
            <img src='https://upload.wikimedia.org/wikipedia/commons/f/fa/Apple_logo_black.svg' width='50' style='margin: 0 auto 10px auto;'/>
            <h1 class='gradient-text' style='margin: 0; font-size: 2.5em;'>Apple Intelligence: 10-K Financial Oracle</h1>
            <p style='color: #64748b; font-size: 1.1em;'>Powered by LlamaIndex, OpenAI, and LlamaParse</p>
        </div>
        """)

    with gr.Row():
        with gr.Column(scale=1, elem_classes="sidebar-panel"):
            gr.HTML("<h3 style='color: #0f172a; margin-top: 0;'>πŸ“Š Document Context</h3>")
            
            gr.Image(
                value="https://images.unsplash.com/photo-1611974789855-9c2a0a7236a3?q=80&w=1000&auto=format&fit=crop",
                show_label=False,
                container=False,
                height=180
            )

            gr.HTML("""
            <div class='stat-card' style='margin-top: 15px;'>
                <h4 style='margin: 0; color: #94a3b8;'>Active Document</h4>
                <h2 style='margin: 5px 0; color: white;'>AAPL 2021 10-K</h2>
                <p style='margin: 0; font-size: 0.8em; color: #38bdf8;'>Vector Indexed & Parsed</p>
            </div>
            """)

            # FIX: Explicit inline styling with !important flags prevents system dark-mode overrides
            gr.HTML(
                """
                <div style='color: #1e293b; font-size: 0.95em; line-height: 1.6;'>
                   <strong style='color: #0f172a; font-size: 1.1em; display: block; margin-bottom: 8px;'>Capabilities:</strong>
                   <ul style='padding-left: 20px; margin-top: 5px; list-style-type: none;'>
                       <li style='color: #1e293b !important; margin-bottom: 6px;'>πŸ“ˆ Extracts exact revenue and profit figures.</li>
                       <li style='color: #1e293b !important; margin-bottom: 6px;'>⚠️ Analyzes corporate risk factors.</li>
                       <li style='color: #1e293b !important; margin-bottom: 6px;'>πŸ“Š Interprets 5-year cumulative return charts.</li>
                       <li style='color: #1e293b !important; margin-bottom: 6px;'>🌐 Translates complex financial jargon.</li>
                   </ul>
                </div>
                """
            )

        with gr.Column(scale=3):
            # FIXED: Removed type="messages" (This is what caused your fatal error)
            chatbot = gr.Chatbot(
                label="Financial Expert AI",
                height=550,
                avatar_images=(
                    "https://cdn-icons-png.flaticon.com/512/3135/3135715.png",
                    "https://cdn-icons-png.flaticon.com/512/12108/12108151.png"
                )
            )

            with gr.Row():
                msg_input = gr.Textbox(
                    show_label=False,
                    placeholder="Ask about Apple's net sales, iPhone revenue, or operational risks...",
                    container=False,
                    scale=4
                )
                submit_btn = gr.Button("Analyze ⚑", variant="primary", scale=1, elem_classes="action-button")

            gr.Markdown("### ⚑ Quick Actions")
            with gr.Row():
                btn_revenue = gr.Button("πŸ’° Summarize Stock Classes", size="sm")
                btn_risk = gr.Button("⚠️ Filing Regulations", size="sm")
                btn_chart = gr.Button("πŸ“ˆ Explain Context", size="sm")
                btn_clear = gr.Button("πŸ—‘οΈ Clear Chat", size="sm", variant="secondary")

    # Event binding workflows
    msg_input.submit(fn=chat_with_financial_analyst, inputs=[msg_input, chatbot], outputs=[msg_input, chatbot])
    submit_btn.click(fn=chat_with_financial_analyst, inputs=[msg_input, chatbot], outputs=[msg_input, chatbot])

    btn_revenue.click(
        fn=lambda history: load_quick_prompt("Summarize the common stocks and notes listed in the report.", history),
        inputs=[chatbot],
        outputs=[msg_input, chatbot]
    )
    btn_risk.click(
        fn=lambda history: load_quick_prompt("What does 'Annual Report pursuant to Section 13 or 15(d) of the Securities Exchange Act of 1934' signify?", history),
        inputs=[chatbot],
        outputs=[msg_input, chatbot]
    )
    btn_chart.click(
        fn=lambda history: load_quick_prompt("Does the document contain numbers validating the 5-Year Cumulative Total Return chart?", history),
        inputs=[chatbot],
        outputs=[msg_input, chatbot]
    )
    btn_clear.click(lambda: [], None, chatbot, queue=False)

# FIXED: Passed BOTH theme and css inside the app.launch() method
app.launch(theme=gr.themes.Ocean(primary_hue="blue", neutral_hue="slate"), css=custom_css)