Sulaiman8 commited on
Commit
d401264
·
verified ·
1 Parent(s): 5f4d22b

Update ui/gradio_interface.py

Browse files
Files changed (1) hide show
  1. ui/gradio_interface.py +361 -361
ui/gradio_interface.py CHANGED
@@ -1,362 +1,362 @@
1
- from langchain_core.messages import SystemMessage, HumanMessage
2
- from typing import List, Dict, Any
3
- import time
4
- import gradio as gr
5
- from data import debug_print,all_card_names,all_card_lookup,eligibility_lookup,df
6
- from nodes.intent import get_pretty_state_string
7
- from langgraph_pipeline import run_langgraph_pipeline,utility_app
8
-
9
- #Gradio UI with the fucntion calls to invoke the graphs and pass the user inputs
10
- custom_css="""
11
- #compare_output_markdown, #full_compare_output_markdown, #top_card_markdown {
12
- min-height: 100px;
13
- border: 1px solid #e0e0e0;
14
- padding: 10px;
15
- overflow-y: auto;
16
- }
17
-
18
- #left_column_box, #right_column_box {
19
- padding: 16px !important; /* Adds 16px of space INSIDE the bordered box */
20
- }
21
- """
22
-
23
- with gr.Blocks(title="Agentic Credit Card Recommender", css=custom_css, theme=gr.themes.Soft()) as demo:
24
- gr.Markdown("""
25
- # Credit Card Recommender
26
- Discover and receive personalized recommendations for credit cards based on your needs and preferences
27
- """)
28
- with gr.Tab("Get Recommendations"):
29
-
30
- with gr.Row():
31
- query_input = gr.Textbox(
32
- label="What kind of card are you looking for?",
33
- placeholder="Best cards for online shopping with fuel benefits",
34
- elem_id="query-textbox",
35
- scale=2
36
- )
37
-
38
- preferences = gr.Dropdown(
39
- choices=["Cashback", "Travel", "Fuel", "Airport Lounge access", "Railways", "Dining", "Online Spends", "Grocery"],
40
- multiselect=True,
41
- label="Select Preferred Card Categories",
42
- scale=1
43
- )
44
-
45
- with gr.Accordion("Eligibility & Fee Filters", open=False):
46
- with gr.Row():
47
- income = gr.Slider(minimum=1, maximum=60, step=1, label="Annual Income (LPA)")
48
- cibil = gr.Slider(minimum=300, maximum=900, step=10, label="CIBIL Score")
49
- age = gr.Slider(minimum=18, maximum=75, step=1, label="Age")
50
-
51
- with gr.Row():
52
- with gr.Group():
53
- gr.Markdown("<p style='text-align:center;'>Preferred Joining Fee (₹)</p>")
54
- with gr.Row():
55
- min_joining_fee = gr.Number(label="Min", value=0)
56
- max_joining_fee = gr.Number(label="Max", value=150000)
57
- with gr.Group():
58
- gr.Markdown("<p style='text-align:center;'>Preferred Annual Fee (₹)</p>")
59
- with gr.Row():
60
- min_annual_fee = gr.Number(label="Min", value=0)
61
- max_annual_fee = gr.Number(label="Max", value=150000)
62
-
63
- with gr.Row():
64
- use_eligibility = gr.Checkbox(label="Apply Eligibility Filter", value=False)
65
- fd_checkbox = gr.Checkbox(label="Beginner / Student", value=False)
66
- cobrand_checkbox = gr.Checkbox(label="Include Co-branded Cards", value=True)
67
-
68
- run_button = gr.Button("Recommend Cards", variant='primary')
69
-
70
- top_card_recommendation = gr.Markdown(value="", elem_id="top_card_markdown")
71
- with gr.Row(visible=True) as results_container:
72
- with gr.Column():
73
- with gr.Group(elem_id="left_column_box"):
74
- recommendation_heading = gr.Markdown("### Top-Ranked Cards")
75
- card_table_markdown = gr.Markdown()
76
-
77
- with gr.Column():
78
- with gr.Group(elem_id="right_column_box"):
79
- card_links_heading = gr.Markdown("### 🔗 Issuer Links")
80
- card_links_html = gr.HTML()
81
-
82
-
83
- with gr.Column(visible=False) as chat_container:
84
- with gr.Accordion("💬 Ask Follow-up Questions", open=True):
85
- chatbox = gr.Chatbot(type="messages", label="Chat")
86
- followup_input = gr.Textbox(label="Enter Your question", placeholder="Compare the lounge access benefits of card X and card Y")
87
- followup_submit = gr.Button("Submit", variant="primary")
88
-
89
-
90
- with gr.Tab("Compare Cards"):
91
- with gr.Column(visible=False) as compare_recommended_cards_container:
92
- gr.Markdown("## Compare Recommended Cards")
93
- compare_checkboxes = gr.CheckboxGroup(choices=[], label="Select 2 or more cards to compare", info="Pick from the recommended list to see a comparison.")
94
- compare_btn = gr.Button("Compare Selected Cards", variant='primary')
95
- compare_output = gr.Markdown(value="", elem_id="compare_output_markdown")
96
- gr.Markdown("---")
97
-
98
- gr.Markdown("## Compare Any Cards")
99
- full_compare_dropdown = gr.Dropdown(choices=all_card_names, multiselect=True, label="Select 2 or more cards", info="Compare any cards from the entire database.")
100
- full_compare_btn = gr.Button("Compare Selected Cards", variant='primary')
101
- full_compare_output = gr.Markdown(value="", elem_id="full_compare_output_markdown")
102
-
103
-
104
- def format_to_markdown(top_card_out, top_card_description_out):
105
- debug_print("UI", f"Formatting top card output to markdown")
106
- if not top_card_out and top_card_description_out:
107
- message_block = "\n".join(f"- {desc}" for desc in top_card_description_out if desc)
108
- return f"### Note\n\n{message_block}\n\n"
109
-
110
- top_card_recommendation = f"### Best card: {top_card_out}\n\n"
111
- if top_card_description_out:
112
- for desc in top_card_description_out:
113
- if isinstance(desc, str):
114
- desc = desc.strip()
115
- if desc:
116
- top_card_recommendation += f"- {desc}\n"
117
-
118
- return top_card_recommendation
119
-
120
- def format_rows_to_markdown_table(card_rows):
121
- """Converts a list of card data into a markdown table string."""
122
- if not card_rows:
123
- return ""
124
-
125
- markdown_table = "| Card Name | Joining Fee | Annual Fee |\n"
126
- markdown_table += "|---|---|---|\n"
127
-
128
- for row in card_rows:
129
- name = row[0]
130
- joining_fee = row[1]
131
- annual_fee = row[2]
132
- markdown_table += f"| {name} | {joining_fee} | {annual_fee} |\n"
133
-
134
- return markdown_table
135
-
136
- #main function that invokes the graph
137
- async def recommend(query, preferences, fd_intent, include_cobranded, income, cibil, age, min_joining_fee, max_joining_fee, min_annual_fee, max_annual_fee, use_eligibility):
138
- debug_print("UI", f"recommend called with query: '{query}'")
139
- debug_print("UI", f"Preferences: {preferences}")
140
- debug_print("UI", f"FD intent: {fd_intent}, Include cobranded: {include_cobranded}")
141
-
142
- global chat_history
143
- chat_history = []
144
- global messages
145
- messages = []
146
- preferences_text=""
147
- if preferences:
148
- preferences_text = "User selected preferences: " + ", ".join(preferences) + "."
149
-
150
- query = query.strip() if query else ""
151
-
152
- if not query:
153
- error_message = "Please enter a valid query."
154
- debug_print("UI", f"recommend function error: {error_message}")
155
- return error_message, gr.update(visible=True), gr.update(value=None), gr.update(visible=False), gr.update(visible=False), gr.update(value=None), gr.update(visible=False), [], {}, "", gr.update(visible=False),{}
156
-
157
- try:
158
- debug_print("UI", f"Calling run_langgraph_pipeline")
159
- top_card_out, top_card_description_out, card_rows_out, card_names_out, card_lookup_out,card_links = await run_langgraph_pipeline(
160
- query,
161
- preferences_text,
162
- query_intent=fd_intent,
163
- include_cobranded=include_cobranded,
164
- use_eligibility=use_eligibility,
165
- income=income,
166
- cibil=cibil,
167
- age=age,
168
- min_joining_fee=min_joining_fee,
169
- max_joining_fee=max_joining_fee,
170
- min_annual_fee=min_annual_fee,
171
- max_annual_fee=max_annual_fee
172
- )
173
-
174
- debug_print("UI", f"Pipeline returned {len(card_rows_out)} card rows")
175
-
176
- recommendation_visible = bool(top_card_out) or bool(top_card_description_out)
177
- df_visible = bool(card_rows_out)
178
- chat_container_visible = gr.update(visible=True if card_rows_out else False)
179
-
180
- top_card_md = format_to_markdown(top_card_out, top_card_description_out)
181
- card_table_md = format_rows_to_markdown_table(card_rows_out)
182
- debug_print("UI", f"recommend function completed successfully")
183
-
184
- initial_context = {
185
- "query": query,
186
- "top_cards": card_names_out[:5],
187
- "recommendation_summary": top_card_md
188
- }
189
- card_links_section = "<div style='padding: 10px; border: 1px solid #444; border-radius: 8px;'>"
190
-
191
- for name, link in zip(card_names_out, card_links):
192
- card_links_section += f"<div style='margin-bottom: 8px;'><a href='{link}' target='_blank'>{name}</a></div>"
193
-
194
- card_links_section += "</div>"
195
-
196
- return top_card_md, gr.update(visible=recommendation_visible), card_table_md, gr.update(visible=df_visible), gr.update(visible=df_visible), card_names_out, card_lookup_out, query, chat_container_visible, initial_context,gr.update(value=card_links_section, visible=True), gr.update(value="### 🔗 Issuer Links", visible=True)
197
-
198
- except Exception as e:
199
- error_message = f"Error during recommendation: {str(e)}"
200
- debug_print("ERROR", f"recommend function error: {str(e)}")
201
- return error_message, gr.update(visible=False), gr.update(value=None), gr.update(visible=False), gr.update(visible=False), [], {}, "", gr.update(visible=False),{}, gr.update(visible=False),gr.update(visible=False)
202
-
203
- #for comparison
204
- async def compare_cards_via_graph(selected_cards, card_lookup):
205
- state = {
206
- "trigger_compare": True,
207
- "trigger_chat": False,
208
- "selected_cards": selected_cards,
209
- "card_lookup": card_lookup
210
- }
211
- result = await utility_app.ainvoke(state)
212
- return result.get("comparison_result", "")
213
-
214
- async def compare_cards_wrapper(selected_cards):
215
- return await compare_cards_via_graph(selected_cards, all_card_lookup)
216
-
217
- #for chat
218
- async def chat_with_agent(user_message: str, chat_history: List, messages: List, initial_context: Dict[str, Any]):
219
- debug_print("UI", f"Entering chat_with_agent with user_message: '{user_message}'")
220
-
221
- # Preparing the initial context message if this is the first question
222
- if not messages:
223
-
224
- user_query = initial_context.get("query", "No query provided")
225
- top_cards = initial_context.get("top_cards", [])
226
- context = "\n".join(top_cards) if top_cards else "No top cards available"
227
-
228
- recommended_summary = initial_context.get("recommendation_summary", "No recommendations available")
229
- recommended_card_info = ""
230
- other_card_info = ""
231
-
232
- for card_name in top_cards:
233
- description = all_card_lookup.get(card_name, "Description not found.")
234
- eligibility = eligibility_lookup.get(card_name, "No eligibility or fee information available.")
235
-
236
- card_block = f"""Card: {card_name}
237
- Description: {description}
238
-
239
- Eligibility & Fees:
240
- {eligibility}
241
-
242
- ---
243
- """
244
- if card_name in recommended_summary:
245
- recommended_card_info += card_block
246
- else:
247
- other_card_info += card_block
248
-
249
- # System prompt with context for chat node
250
- system_message = f"""
251
- **Your Role: Secure Credit Card Expert**
252
- You are a helpful and secure AI assistant. Your goal is to provide a comprehensive answer to the user's question using all available information.
253
-
254
- Your knowledge base includes the following and <Newly_Fetched_Information> section if it is provided:
255
- <Initial_Context>
256
- <User_Requirements>{user_query}</User_Requirements>
257
- <Top_Ranked_Cards_For_User_Query>{context}</Top_Ranked_Cards_For_User_Query>
258
- <Recommended_Card_Info>
259
- <Best_Recommended_Card>{recommended_card_info}</Best_Recommended_Card>
260
- <Other_Recommended_Cards>{other_card_info}</Other_Recommended_Cards>
261
- </Recommended_Card_Info>
262
- </Initial_Context>
263
-
264
- **CRITICAL RULES:**
265
- 1. **Synthesize All Information:** Base your answer on the `<Initial_Context>` and any `<Newly_Fetched_Information>` provided. If the user asks for a comparison, use details from both.
266
- 2. **Make a Recommendation:** If asked "which is best?" or "which is suitable?", analyze all available card info against the `<User_Requirements>` and provide a direct, reasoned recommendation.
267
- 3. **Be Direct:** Do not mention your internal processes. Just provide the final answer to the user.
268
-
269
- **RESPONSE STYLE AND TONE:**
270
- - **Be Direct and Concise:** Get straight to the point. Do not explain your internal thought process.
271
- - **For Factual Questions (like "what are the fees?"):** Provide a direct, simple sentence as the answer.
272
- - **For Recommendation Questions (like "which is best for me?"):** Start your response with the name of the recommended card, followed by a brief, bulleted list of the key features that make it the best fit for the user's requirements.
273
- """
274
-
275
- # print(f"system message: {system_message}")
276
- messages = [SystemMessage(content=system_message)]
277
-
278
- else:
279
- debug_print("UI", "Subsequent turn: using existing message history.")
280
- top_cards = initial_context.get("top_cards", [])
281
- user_query = initial_context.get("query", "No query provided")
282
-
283
- current_turn_messages = messages + [HumanMessage(content=user_message)]
284
-
285
- # Defining the state for the utility app
286
- state = {
287
- "messages": current_turn_messages,
288
- "trigger_chat": True,
289
- "trigger_compare": False,
290
- "card_names": top_cards,
291
- }
292
-
293
- # invoking the graph
294
- response_state = await utility_app.ainvoke(state)
295
-
296
- updated_messages_from_agent = response_state.get("messages", [])
297
- debug_print("UI", f"Chat agent response state: {get_pretty_state_string(updated_messages_from_agent)}")
298
- final_response = updated_messages_from_agent[-1].content
299
- chat_history.append({"role": "user", "content": user_message})
300
- chat_history.append({"role": "assistant", "content": final_response})
301
- return chat_history, updated_messages_from_agent, ""
302
-
303
- card_names_state = gr.State([])
304
- card_lookup_state = gr.State()
305
- chat_history = gr.State([])
306
- query = gr.State("")
307
- initial_chat_context = gr.State({})
308
- messages= gr.State([])
309
-
310
- run_button.click(
311
- fn=recommend,
312
- inputs=[
313
- query_input, preferences, fd_checkbox, cobrand_checkbox,
314
- income, cibil, age,
315
- min_joining_fee, max_joining_fee,
316
- min_annual_fee, max_annual_fee,
317
- use_eligibility
318
- ],
319
- outputs=[
320
- top_card_recommendation, top_card_recommendation,
321
- card_table_markdown, card_table_markdown, recommendation_heading,
322
- card_names_state,
323
- card_lookup_state,
324
- query, chat_container, initial_chat_context,card_links_html, card_links_heading
325
- ],
326
- concurrency_limit=20
327
- ).then(
328
- fn=lambda card_names: (gr.update(choices=card_names if card_names else [], value=[]), gr.update(visible=True if card_names else False)),
329
- inputs=card_names_state,
330
- outputs=[compare_checkboxes, compare_recommended_cards_container],
331
- concurrency_limit=20
332
- ).then(
333
- fn=lambda: ([], [], [], gr.update(value=""), gr.update(value="")),
334
- outputs=[chat_history, chatbox, messages, compare_output, full_compare_output], # Clear both compare outputs
335
- concurrency_limit=20
336
- )
337
-
338
- compare_btn.click(
339
- fn=compare_cards_via_graph,
340
- inputs=[compare_checkboxes, card_lookup_state],
341
- outputs=compare_output,
342
- show_progress=True,
343
- concurrency_limit=20
344
- )
345
-
346
- full_compare_btn.click(
347
- fn=compare_cards_wrapper,
348
- inputs=[full_compare_dropdown],
349
- outputs=full_compare_output,
350
- show_progress=True,
351
- concurrency_limit=20
352
- )
353
-
354
-
355
- followup_submit.click(
356
- fn=chat_with_agent,
357
- inputs=[followup_input, chatbox, messages, initial_chat_context],
358
- outputs=[chatbox, messages, followup_input],
359
- concurrency_limit=20
360
- )
361
- debug_print("APP", f"Credit Card Recommender Agent initialized with {len(df)} cards")
362
  debug_print("APP", f"Launching Gradio UI at {time.strftime('%Y-%m-%d %H:%M:%S')}")
 
1
+ from langchain_core.messages import SystemMessage, HumanMessage
2
+ from typing import List, Dict, Any
3
+ import time
4
+ import gradio as gr
5
+ from data import debug_print,all_card_names,all_card_lookup,eligibility_lookup,df
6
+ from nodes.intent import get_pretty_state_string
7
+ from langgraph_pipeline import run_langgraph_pipeline,utility_app
8
+
9
+ #Gradio UI with the fucntion calls to invoke the graphs and pass the user inputs
10
+ custom_css="""
11
+ #compare_output_markdown, #full_compare_output_markdown, #top_card_markdown {
12
+ min-height: 100px;
13
+ border: 1px solid #e0e0e0;
14
+ padding: 10px;
15
+ overflow-y: auto;
16
+ }
17
+
18
+ #left_column_box, #right_column_box {
19
+ padding: 16px !important; /* Adds 16px of space INSIDE the bordered box */
20
+ }
21
+ """
22
+
23
+ with gr.Blocks(title="Agentic Credit Card Recommender", css=custom_css, theme=gr.themes.Soft()) as demo:
24
+ gr.Markdown("""
25
+ # Credit Card Recommender
26
+ Discover and receive personalized recommendations for credit cards based on your needs and preferences
27
+ """)
28
+ with gr.Tab("Get Recommendations"):
29
+
30
+ with gr.Row():
31
+ query_input = gr.Textbox(
32
+ label="What kind of card are you looking for?",
33
+ placeholder="Best cards for online shopping with fuel benefits",
34
+ elem_id="query-textbox",
35
+ scale=2
36
+ )
37
+
38
+ preferences = gr.Dropdown(
39
+ choices=["Cashback", "Travel", "Fuel", "Airport Lounge access", "Railways", "Dining", "Online Spends", "Grocery"],
40
+ multiselect=True,
41
+ label="Select Preferred Card Categories",
42
+ scale=1
43
+ )
44
+
45
+ with gr.Accordion("Eligibility & Fee Filters", open=False):
46
+ with gr.Row():
47
+ income = gr.Slider(minimum=1, maximum=60, step=1, label="Annual Income (LPA)")
48
+ cibil = gr.Slider(minimum=300, maximum=900, step=10, label="CIBIL Score")
49
+ age = gr.Slider(minimum=18, maximum=75, step=1, label="Age")
50
+
51
+ with gr.Row():
52
+ with gr.Group():
53
+ gr.Markdown("<p style='text-align:center;'>Preferred Joining Fee (₹)</p>")
54
+ with gr.Row():
55
+ min_joining_fee = gr.Number(label="Min", value=0)
56
+ max_joining_fee = gr.Number(label="Max", value=150000)
57
+ with gr.Group():
58
+ gr.Markdown("<p style='text-align:center;'>Preferred Annual Fee (₹)</p>")
59
+ with gr.Row():
60
+ min_annual_fee = gr.Number(label="Min", value=0)
61
+ max_annual_fee = gr.Number(label="Max", value=150000)
62
+
63
+ with gr.Row():
64
+ use_eligibility = gr.Checkbox(label="Apply Eligibility Filter", value=False)
65
+ fd_checkbox = gr.Checkbox(label="Beginner / Student", value=False)
66
+ cobrand_checkbox = gr.Checkbox(label="Include Co-branded Cards", value=True)
67
+
68
+ run_button = gr.Button("Recommend Cards", variant='primary')
69
+
70
+ top_card_recommendation = gr.Markdown(value="", elem_id="top_card_markdown")
71
+ with gr.Row(visible=True) as results_container:
72
+ with gr.Column():
73
+ with gr.Group(elem_id="left_column_box"):
74
+ recommendation_heading = gr.Markdown("### Top-Ranked Cards")
75
+ card_table_markdown = gr.Markdown()
76
+
77
+ with gr.Column():
78
+ with gr.Group(elem_id="right_column_box"):
79
+ card_links_heading = gr.Markdown("### 🔗 Issuer Links")
80
+ card_links_html = gr.HTML()
81
+
82
+
83
+ with gr.Column(visible=False) as chat_container:
84
+ with gr.Accordion("💬 Ask Follow-up Questions", open=True):
85
+ chatbox = gr.Chatbot(type="messages", label="Chat")
86
+ followup_input = gr.Textbox(label="Enter Your question", placeholder="Compare the lounge access benefits of card X and card Y")
87
+ followup_submit = gr.Button("Submit", variant="primary")
88
+
89
+
90
+ with gr.Tab("Compare Cards"):
91
+ with gr.Column(visible=False) as compare_recommended_cards_container:
92
+ gr.Markdown("## Compare Recommended Cards")
93
+ compare_checkboxes = gr.CheckboxGroup(choices=[], label="Select 2 or more cards to compare", info="Pick from the recommended list to see a comparison.")
94
+ compare_btn = gr.Button("Compare Selected Cards", variant='primary')
95
+ compare_output = gr.Markdown(value="", elem_id="compare_output_markdown")
96
+ gr.Markdown("---")
97
+
98
+ gr.Markdown("## Compare Any Cards")
99
+ full_compare_dropdown = gr.Dropdown(choices=all_card_names, multiselect=True, label="Select 2 or more cards", info="Compare any cards from the entire database.")
100
+ full_compare_btn = gr.Button("Compare Selected Cards", variant='primary')
101
+ full_compare_output = gr.Markdown(value="", elem_id="full_compare_output_markdown")
102
+
103
+
104
+ def format_to_markdown(top_card_out, top_card_description_out):
105
+ debug_print("UI", f"Formatting top card output to markdown")
106
+ if not top_card_out and top_card_description_out:
107
+ message_block = "\n".join(f"- {desc}" for desc in top_card_description_out if desc)
108
+ return f"### Note\n\n{message_block}\n\n"
109
+
110
+ top_card_recommendation = f"### Best card: {top_card_out}\n\n"
111
+ if top_card_description_out:
112
+ for desc in top_card_description_out:
113
+ if isinstance(desc, str):
114
+ desc = desc.strip()
115
+ if desc:
116
+ top_card_recommendation += f"- {desc}\n"
117
+
118
+ return top_card_recommendation
119
+
120
+ def format_rows_to_markdown_table(card_rows):
121
+ """Converts a list of card data into a markdown table string."""
122
+ if not card_rows:
123
+ return ""
124
+
125
+ markdown_table = "| Card Name | Joining Fee | Annual Fee |\n"
126
+ markdown_table += "|---|---|---|\n"
127
+
128
+ for row in card_rows:
129
+ name = row[0]
130
+ joining_fee = row[1]
131
+ annual_fee = row[2]
132
+ markdown_table += f"| {name} | {joining_fee} | {annual_fee} |\n"
133
+
134
+ return markdown_table
135
+
136
+ #main function that invokes the graph
137
+ async def recommend(query, preferences, fd_intent, include_cobranded, income, cibil, age, min_joining_fee, max_joining_fee, min_annual_fee, max_annual_fee, use_eligibility):
138
+ debug_print("UI", f"recommend called with query: '{query}'")
139
+ debug_print("UI", f"Preferences: {preferences}")
140
+ debug_print("UI", f"FD intent: {fd_intent}, Include cobranded: {include_cobranded}")
141
+
142
+ global chat_history
143
+ chat_history = []
144
+ global messages
145
+ messages = []
146
+ preferences_text=""
147
+ if preferences:
148
+ preferences_text = "User selected preferences: " + ", ".join(preferences) + "."
149
+
150
+ query = query.strip() if query else ""
151
+
152
+ if not query:
153
+ error_message = "Please enter a valid query."
154
+ debug_print("UI", f"recommend function error: {error_message}")
155
+ return error_message, gr.update(visible=True), gr.update(value=None), gr.update(visible=False), gr.update(visible=False), gr.update(value=None), gr.update(visible=False), [], {}, "", gr.update(visible=False),{}
156
+
157
+ try:
158
+ debug_print("UI", f"Calling run_langgraph_pipeline")
159
+ top_card_out, top_card_description_out, card_rows_out, card_names_out, card_lookup_out,card_links = await run_langgraph_pipeline(
160
+ query,
161
+ preferences_text,
162
+ query_intent=fd_intent,
163
+ include_cobranded=include_cobranded,
164
+ use_eligibility=use_eligibility,
165
+ income=income,
166
+ cibil=cibil,
167
+ age=age,
168
+ min_joining_fee=min_joining_fee,
169
+ max_joining_fee=max_joining_fee,
170
+ min_annual_fee=min_annual_fee,
171
+ max_annual_fee=max_annual_fee
172
+ )
173
+
174
+ debug_print("UI", f"Pipeline returned {len(card_rows_out)} card rows")
175
+
176
+ recommendation_visible = bool(top_card_out) or bool(top_card_description_out)
177
+ df_visible = bool(card_rows_out)
178
+ chat_container_visible = gr.update(visible=True if card_rows_out else False)
179
+
180
+ top_card_md = format_to_markdown(top_card_out, top_card_description_out)
181
+ card_table_md = format_rows_to_markdown_table(card_rows_out)
182
+ debug_print("UI", f"recommend function completed successfully")
183
+
184
+ initial_context = {
185
+ "query": query,
186
+ "top_cards": card_names_out[:5],
187
+ "recommendation_summary": top_card_md
188
+ }
189
+ card_links_section = "<div style='padding: 10px; border: 1px solid #444; border-radius: 8px;'>"
190
+
191
+ for name, link in zip(card_names_out, card_links):
192
+ card_links_section += f"<div style='margin-bottom: 8px;'><a href='{link}' target='_blank'>{name}</a></div>"
193
+
194
+ card_links_section += "</div>"
195
+
196
+ return top_card_md, gr.update(visible=recommendation_visible), card_table_md, gr.update(visible=df_visible), gr.update(visible=df_visible), card_names_out, card_lookup_out, query, chat_container_visible, initial_context,gr.update(value=card_links_section, visible=True), gr.update(value="### 🔗 Issuer Links", visible=True)
197
+
198
+ except Exception as e:
199
+ error_message = f"Error during recommendation: {str(e)}"
200
+ debug_print("ERROR", f"recommend function error: {str(e)}")
201
+ return gr.update(value=error_message, visible=True), gr.update(visible=False), gr.update(value=None), gr.update(visible=False), gr.update(visible=False), [], {}, "", gr.update(visible=False),{}, gr.update(visible=False),gr.update(visible=False)
202
+
203
+ #for comparison
204
+ async def compare_cards_via_graph(selected_cards, card_lookup):
205
+ state = {
206
+ "trigger_compare": True,
207
+ "trigger_chat": False,
208
+ "selected_cards": selected_cards,
209
+ "card_lookup": card_lookup
210
+ }
211
+ result = await utility_app.ainvoke(state)
212
+ return result.get("comparison_result", "")
213
+
214
+ async def compare_cards_wrapper(selected_cards):
215
+ return await compare_cards_via_graph(selected_cards, all_card_lookup)
216
+
217
+ #for chat
218
+ async def chat_with_agent(user_message: str, chat_history: List, messages: List, initial_context: Dict[str, Any]):
219
+ debug_print("UI", f"Entering chat_with_agent with user_message: '{user_message}'")
220
+
221
+ # Preparing the initial context message if this is the first question
222
+ if not messages:
223
+
224
+ user_query = initial_context.get("query", "No query provided")
225
+ top_cards = initial_context.get("top_cards", [])
226
+ context = "\n".join(top_cards) if top_cards else "No top cards available"
227
+
228
+ recommended_summary = initial_context.get("recommendation_summary", "No recommendations available")
229
+ recommended_card_info = ""
230
+ other_card_info = ""
231
+
232
+ for card_name in top_cards:
233
+ description = all_card_lookup.get(card_name, "Description not found.")
234
+ eligibility = eligibility_lookup.get(card_name, "No eligibility or fee information available.")
235
+
236
+ card_block = f"""Card: {card_name}
237
+ Description: {description}
238
+
239
+ Eligibility & Fees:
240
+ {eligibility}
241
+
242
+ ---
243
+ """
244
+ if card_name in recommended_summary:
245
+ recommended_card_info += card_block
246
+ else:
247
+ other_card_info += card_block
248
+
249
+ # System prompt with context for chat node
250
+ system_message = f"""
251
+ **Your Role: Secure Credit Card Expert**
252
+ You are a helpful and secure AI assistant. Your goal is to provide a comprehensive answer to the user's question using all available information.
253
+
254
+ Your knowledge base includes the following and <Newly_Fetched_Information> section if it is provided:
255
+ <Initial_Context>
256
+ <User_Requirements>{user_query}</User_Requirements>
257
+ <Top_Ranked_Cards_For_User_Query>{context}</Top_Ranked_Cards_For_User_Query>
258
+ <Recommended_Card_Info>
259
+ <Best_Recommended_Card>{recommended_card_info}</Best_Recommended_Card>
260
+ <Other_Recommended_Cards>{other_card_info}</Other_Recommended_Cards>
261
+ </Recommended_Card_Info>
262
+ </Initial_Context>
263
+
264
+ **CRITICAL RULES:**
265
+ 1. **Synthesize All Information:** Base your answer on the `<Initial_Context>` and any `<Newly_Fetched_Information>` provided. If the user asks for a comparison, use details from both.
266
+ 2. **Make a Recommendation:** If asked "which is best?" or "which is suitable?", analyze all available card info against the `<User_Requirements>` and provide a direct, reasoned recommendation.
267
+ 3. **Be Direct:** Do not mention your internal processes. Just provide the final answer to the user.
268
+
269
+ **RESPONSE STYLE AND TONE:**
270
+ - **Be Direct and Concise:** Get straight to the point. Do not explain your internal thought process.
271
+ - **For Factual Questions (like "what are the fees?"):** Provide a direct, simple sentence as the answer.
272
+ - **For Recommendation Questions (like "which is best for me?"):** Start your response with the name of the recommended card, followed by a brief, bulleted list of the key features that make it the best fit for the user's requirements.
273
+ """
274
+
275
+ # print(f"system message: {system_message}")
276
+ messages = [SystemMessage(content=system_message)]
277
+
278
+ else:
279
+ debug_print("UI", "Subsequent turn: using existing message history.")
280
+ top_cards = initial_context.get("top_cards", [])
281
+ user_query = initial_context.get("query", "No query provided")
282
+
283
+ current_turn_messages = messages + [HumanMessage(content=user_message)]
284
+
285
+ # Defining the state for the utility app
286
+ state = {
287
+ "messages": current_turn_messages,
288
+ "trigger_chat": True,
289
+ "trigger_compare": False,
290
+ "card_names": top_cards,
291
+ }
292
+
293
+ # invoking the graph
294
+ response_state = await utility_app.ainvoke(state)
295
+
296
+ updated_messages_from_agent = response_state.get("messages", [])
297
+ debug_print("UI", f"Chat agent response state: {get_pretty_state_string(updated_messages_from_agent)}")
298
+ final_response = updated_messages_from_agent[-1].content
299
+ chat_history.append({"role": "user", "content": user_message})
300
+ chat_history.append({"role": "assistant", "content": final_response})
301
+ return chat_history, updated_messages_from_agent, ""
302
+
303
+ card_names_state = gr.State([])
304
+ card_lookup_state = gr.State()
305
+ chat_history = gr.State([])
306
+ query = gr.State("")
307
+ initial_chat_context = gr.State({})
308
+ messages= gr.State([])
309
+
310
+ run_button.click(
311
+ fn=recommend,
312
+ inputs=[
313
+ query_input, preferences, fd_checkbox, cobrand_checkbox,
314
+ income, cibil, age,
315
+ min_joining_fee, max_joining_fee,
316
+ min_annual_fee, max_annual_fee,
317
+ use_eligibility
318
+ ],
319
+ outputs=[
320
+ top_card_recommendation, top_card_recommendation,
321
+ card_table_markdown, card_table_markdown, recommendation_heading,
322
+ card_names_state,
323
+ card_lookup_state,
324
+ query, chat_container, initial_chat_context,card_links_html, card_links_heading
325
+ ],
326
+ concurrency_limit=20
327
+ ).then(
328
+ fn=lambda card_names: (gr.update(choices=card_names if card_names else [], value=[]), gr.update(visible=True if card_names else False)),
329
+ inputs=card_names_state,
330
+ outputs=[compare_checkboxes, compare_recommended_cards_container],
331
+ concurrency_limit=20
332
+ ).then(
333
+ fn=lambda: ([], [], [], gr.update(value=""), gr.update(value="")),
334
+ outputs=[chat_history, chatbox, messages, compare_output, full_compare_output], # Clear both compare outputs
335
+ concurrency_limit=20
336
+ )
337
+
338
+ compare_btn.click(
339
+ fn=compare_cards_via_graph,
340
+ inputs=[compare_checkboxes, card_lookup_state],
341
+ outputs=compare_output,
342
+ show_progress=True,
343
+ concurrency_limit=20
344
+ )
345
+
346
+ full_compare_btn.click(
347
+ fn=compare_cards_wrapper,
348
+ inputs=[full_compare_dropdown],
349
+ outputs=full_compare_output,
350
+ show_progress=True,
351
+ concurrency_limit=20
352
+ )
353
+
354
+
355
+ followup_submit.click(
356
+ fn=chat_with_agent,
357
+ inputs=[followup_input, chatbox, messages, initial_chat_context],
358
+ outputs=[chatbox, messages, followup_input],
359
+ concurrency_limit=20
360
+ )
361
+ debug_print("APP", f"Credit Card Recommender Agent initialized with {len(df)} cards")
362
  debug_print("APP", f"Launching Gradio UI at {time.strftime('%Y-%m-%d %H:%M:%S')}")