JC321 commited on
Commit
b23a732
·
verified ·
1 Parent(s): 674fe71

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +158 -158
app.py CHANGED
@@ -5,7 +5,7 @@ import re
5
  import pandas as pd
6
  # from dotenv import load_dotenv
7
 
8
- # # Load environment variables from .env file
9
  # load_dotenv()
10
  # from EasyFinancialAgent.chat import query_company
11
  from EasyFinancialAgent.chat_direct import advanced_search_company_detailed, search_and_format, search_company_direct, chatbot_response, format_search_result_for_display, format_search_result
@@ -22,7 +22,7 @@ from service.report_tools import build_financial_metrics_three_year_data, calcul
22
  from service.three_year_table_tool import build_table_format
23
  from service.three_year_tool import process_financial_data_with_metadata
24
  from service.tool_processor import get_stock_price
25
- # ✅ Import cache managers
26
  from service.report_cache_manager import ReportCacheManager
27
  from service.financial_data_cache_manager import FinancialDataCacheManager
28
 
@@ -38,7 +38,7 @@ my_companies = [
38
  {'company_name': 'Tesla', 'stock_code': 'TSLA', "cik": "0001318605"},
39
  {'company_name': 'AMD', 'stock_code': 'AMD', "cik": "0000002488"},
40
  {'company_name': 'Microsoft', 'stock_code': 'MSFT', "cik": "0000789019"}
41
- # Global variable to store company mapping relationships
42
  # JavaScript代码用于读取和存储数据
43
  js_code = """
44
  function handleStorage(operation, key, value) {
@@ -49,7 +49,7 @@ function handleStorage(operation, key, value) {
49
  let storedValue = localStorage.getItem(key);
50
  if (storedValue === null) {
51
  return `未找到键: ${key}`;
52
- # Global variable to store company mapping relationships
53
  return `读取到: ${key} = ${storedValue}`;
54
  } else if (operation === 'clear') {
55
  localStorage.removeItem(key);
@@ -57,11 +57,11 @@ function handleStorage(operation, key, value) {
57
  } else if (operation === 'clearAll') {
58
  localStorage.clear();
59
  return '已清除所有数据';
60
- # Global variable to store company mapping relationships
61
- # Global variable to store company mapping relationships
62
- # Global variable to store company mapping relationships
63
  custom_css = """
64
- /* Match all classes starting with gradio-container- */
65
  div[class^="gradio-container-"],
66
  div[class*=" gradio-container-"] {
67
  -webkit-text-size-adjust: 100% !important;
@@ -69,7 +69,7 @@ div[class*=" gradio-container-"] {
69
  font-family: unset !important;
70
  -moz-tab-size: 4 !important;
71
  tab-size: 4 !important;
72
- # Global variable to store company mapping relationships
73
 
74
  .company-list-container {
75
  background-color: white;
@@ -79,14 +79,14 @@ div[class*=" gradio-container-"] {
79
  border: 1px solid #e5e7eb;
80
  box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
81
  width: 100%;
82
- # Global variable to store company mapping relationships
83
 
84
- /* Hide radio buttons */
85
  .company-list-container input[type="radio"] {
86
  display: none;
87
- # Global variable to store company mapping relationships
88
 
89
- /* Custom option style - compact */
90
  .company-list-container label {
91
  display: block;
92
  padding: 0.5rem 0.75rem;
@@ -102,7 +102,7 @@ div[class*=" gradio-container-"] {
102
  box-sizing: border-box;
103
  position: relative;
104
  padding-right: 2rem;
105
- # Global variable to store company mapping relationships
106
 
107
  /* ✅ 为每个公司选项添加删除按钮 */
108
  .company-list-container label::after {
@@ -125,23 +125,23 @@ div[class*=" gradio-container-"] {
125
  transition: opacity 0.2s;
126
  cursor: pointer;
127
  line-height: 1;
128
- # Global variable to store company mapping relationships
129
 
130
  .company-list-container label:hover::after {
131
  opacity: 1;
132
- # Global variable to store company mapping relationships
133
 
134
  .company-list-container label::after:hover {
135
  background: #9ca3af;
136
- # Global variable to store company mapping relationships
137
 
138
- /* Hover effect */
139
  .company-list-container label:hover {
140
  background-color: #f3f4f6;
141
  border-color: #d1d5db;
142
- # Global variable to store company mapping relationships
143
 
144
- /* Selected effect - use blue */
145
  .company-list-container input[type="radio"]:checked + span {
146
  background: #3b82f6 !important;
147
  color: white !important;
@@ -152,34 +152,34 @@ div[class*=" gradio-container-"] {
152
  padding: 0.75rem 1rem;
153
  margin: -0.75rem -1rem;
154
  border-radius: 0.375rem;
155
- # Global variable to store company mapping relationships
156
 
157
  .company-list-container span {
158
  display: block;
159
  padding: 0;
160
  border-radius: 0.375rem;
161
  width: 100%;
162
- # Global variable to store company mapping relationships
163
 
164
  label.selected {
165
  background: #3b82f6 !important;
166
  color: white !important;
167
- # Global variable to store company mapping relationships
168
 
169
- /* Ensure only one option per line */
170
  .company-list-container .wrap {
171
  display: block !important;
172
- # Global variable to store company mapping relationships
173
 
174
  .company-list-container .wrap li {
175
  display: block !important;
176
  width: 100% !important;
177
- # Global variable to store company mapping relationships
178
 
179
- /* ✅ Search box style - with built-in icon */
180
  .company-input-search {
181
  position: relative;
182
- # Global variable to store company mapping relationships
183
 
184
  .company-input-search input,
185
  .company-input-search textarea {
@@ -192,47 +192,47 @@ label.selected {
192
  border-radius: 8px !important;
193
  font-size: 14px !important;
194
  transition: all 0.2s !important;
195
- # Global variable to store company mapping relationships
196
 
197
  .company-input-search input:focus,
198
  .company-input-search textarea:focus {
199
  border-color: #667eea !important;
200
  box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1) !important;
201
  outline: none !important;
202
- # Global variable to store company mapping relationships
203
 
204
  /* ✅ 对齐Tabs和ChatInterface的横线 */
205
  .tabs {
206
  border-bottom: 1px solid #e5e7eb !important;
207
- # Global variable to store company mapping relationships
208
 
209
  .chatbot {
210
  border-top: 1px solid #e5e7eb !important;
211
- # Global variable to store company mapping relationships
212
 
213
  /* 确俟tab内容和chatbot的padding一致 */
214
  .tab-item {
215
  padding: 16px !important;
216
- # Global variable to store company mapping relationships
217
 
218
  /* ✅ 为三个板块添加间距 */
219
  #select-company-section {
220
  margin-bottom: 20px !important;
221
- # Global variable to store company mapping relationships
222
 
223
  .report-news-box {
224
  margin-top: 20px !important;
225
- # Global variable to store company mapping relationships
226
 
227
  .report-list-box,
228
  .news-list-box {
229
  margin-bottom: 16px !important;
230
- # Global variable to store company mapping relationships
231
 
232
  /* ✅ 对齐ChatInterface标题和Tabs标签栏 */
233
  .chatbot .wrap {
234
  margin-top: 0 !important;
235
- # Global variable to store company mapping relationships
236
 
237
  /* ChatInterface标题样式 */
238
  .chatbot > .wrap > .head {
@@ -242,7 +242,7 @@ label.selected {
242
  padding: 8px 16px !important;
243
  border-bottom: 1px solid #e5e7eb !important;
244
  background: #f9fafb !important;
245
- # Global variable to store company mapping relationships
246
 
247
  /* ✅ 缩小Easy Financial AI Assistant标题 - 更精致 */
248
  .chatbot > .wrap > .head h1,
@@ -253,7 +253,7 @@ label.selected {
253
  font-weight: 500 !important;
254
  color: #6b7280 !important;
255
  letter-spacing: 0.3px !important;
256
- # Global variable to store company mapping relationships
257
 
258
  /* Tabs标签栏样式 */
259
  .tab-container {
@@ -262,28 +262,28 @@ label.selected {
262
  align-items: center !important;
263
  border-bottom: 1px solid #e5e7eb !important;
264
  background: #f9fafb !important;
265
- # Global variable to store company mapping relationships
266
 
267
  /* ✅ 移除Tabs底部横线 */
268
  .tabs {
269
  border-bottom: none !important;
270
- # Global variable to store company mapping relationships
271
- # Global variable to store company mapping relationships
272
 
273
- # Global variable to store company mapping relationships
274
  companies_map = {}
275
 
276
- # ✅ Global variable to cache search results (including complete information like CIK)
277
  search_result_cache = {}
278
 
279
- # Get stock code by company name function
280
  def get_stock_code_by_company_name(company_name):
281
- """Get stock code by company name"""
282
  if company_name in companies_map and "CODE" in companies_map[company_name]:
283
  return companies_map[company_name]["CODE"]
284
- return "" # Default return
285
 
286
- # Create a simple function to get company list
287
  def get_company_list_choices():
288
  choices = []
289
  print(f"Getting init add company list choices...{get_companys_state}")
@@ -304,23 +304,23 @@ def get_company_list_choices():
304
 
305
  # Sidebar service functions
306
 
307
- # Function to handle company click event
308
  def handle_company_click(company_name):
309
- """Handle company click event, check if already in database, if not add it, then refresh company list"""
310
  print(f"Handling click for company: {company_name}")
311
 
312
- # 1. Check if already in database
313
  if not check_company_exists(my_companies, company_name):
314
- # 2. If not in database, add it
315
- # Get stock code (if available)
316
  stock_code = companies_map.get(company_name, {}).get("CODE", "Unknown")
317
  print(f"Inserting company {company_name} with code {stock_code}")
318
 
319
- # Add company to database
320
  # success = insert_company(company_name, stock_code)
321
  my_companies.append({"company_name": company_name, "stock_code": stock_code})
322
- print(f"Successfully inserted company: {company_name}") # Direct update companies_map instead of reloading entire mapping
323
- # Direct update companies_map instead of reloading entire mapping
324
  companies_map[company_name] = {"NAME": company_name, "CODE": stock_code}
325
  # 使用Gradio的成功提示
326
  gr.Info(f"Successfully added company: {company_name}")
@@ -339,39 +339,39 @@ def get_company_list_html(selected_company=""):
339
  # 从数据库获取所有公司
340
  # companies_data = get_companys()
341
  companies_data = my_companies
342
- # Check if this is an error message
343
  if isinstance(companies_data, str):
344
- if "query execution failed" in companies_data:
345
- return "<div class='text-red-500'>Failed to get company list</div>"
346
  else:
347
- # If it's a string but not an error message, may need special handling
348
  return ""
349
 
350
- # Check if it's DataFrame and empty
351
  if not isinstance(companies_data, pd.DataFrame) or companies_data.empty:
352
  return ""
353
 
354
- # Generate HTML list
355
  html_items = []
356
  for _, row in companies_data.iterrows():
357
  company_name = row.get('company_name', 'Unknown')
358
- # Add different style classes based on selection status
359
  css_class = "company-item"
360
  if company_name == selected_company:
361
  css_class += " selected-company"
362
- # Use button element to ensure clickability
363
  html_items.append(f'<button class="{css_class}" data-company="{company_name}" style="width:100%; text-align:left; border:none; background:none;">{company_name}</button>')
364
 
365
  return "\n".join(html_items)
366
  except Exception as e:
367
- return f"<div class='text-red-500'>Failed to generate company list: {str(e)}</div>"
368
 
369
  def initialize_company_list(selected_company=""):
370
  return get_company_list_html(selected_company)
371
 
372
  def refresh_company_list(selected_company=""):
373
- """Refresh company list, return latest HTML content with loading effect"""
374
- # First return loading status
375
  loading_html = '''
376
  <div style="display: flex; justify-content: center; align-items: center; height: 100px;">
377
  <div class="loading-spinner" style="width: 24px; height: 24px; border: 3px solid #f3f3f3; border-top: 3px solid #3498db; border-radius: 50%; animation: spin 1s linear infinite;"></div>
@@ -379,26 +379,26 @@ def refresh_company_list(selected_company=""):
379
  @keyframes spin {
380
  0% { transform: rotate(0deg); }
381
  100% { transform: rotate(360deg); }
382
- # Global variable to store company mapping relationships
383
  </style>
384
  </div>
385
- # Global variable to store company mapping relationships
386
  yield loading_html
387
 
388
- # Then return actual data
389
  yield get_company_list_html(selected_company)
390
 
391
- # New function: handle company selection event
392
  def select_company(company_name):
393
- """Handle company selection event, update global state and return updated company list"""
394
- # Update global variable
395
  g.SELECT_COMPANY = company_name if company_name else ""
396
- # For Radio component, we just need to return updated options list
397
  try:
398
  # companies_data = get_companys()
399
  companies_data = my_companies
400
  if isinstance(companies_data, list) and len(companies_data) > 0:
401
- # my_companies is a list of objects [{company_name: '', stock_code: ''}, ...]
402
  choices = [str(item.get('company_name', 'Unknown')) for item in companies_data]
403
  elif isinstance(companies_data, pd.DataFrame) and not companies_data.empty:
404
  choices = [str(row.get('company_name', 'Unknown')) for _, row in companies_data.iterrows()]
@@ -409,7 +409,7 @@ def select_company(company_name):
409
  return gr.update(choices=choices, value=company_name)
410
 
411
  def initialize_companies_map():
412
- """Initialize companies_map dictionary"""
413
  global companies_map
414
  companies_map = {} # 清空之前的映射
415
 
@@ -428,15 +428,15 @@ def initialize_companies_map():
428
  { "NAME": "Tesla", "CODE": "TSLA" },
429
  { "NAME": "AMD", "CODE": "AMD" },
430
  { "NAME": "Microsoft", "CODE": "MSFT" },
431
- # Global variable to store company mapping relationships
432
 
433
- # Add predefined companies to mapping
434
  for company in predefined_companies:
435
  companies_map[company["NAME"]] = {"NAME": company["NAME"], "CODE": company["CODE"]}
436
 
437
  # print(f"Predefined companies added: {len(predefined_companies)}")
438
 
439
- # Get company data from database
440
  # companies_data = get_companys()
441
  companies_data = my_companies
442
  # companies_data = window.cachedCompanies or []
@@ -444,7 +444,7 @@ def initialize_companies_map():
444
 
445
  print(f"Companies data from DB: {companies_data}")
446
 
447
- # If there is company data in database, add to mapping (dedup)
448
  if isinstance(companies_data, pd.DataFrame) and not companies_data.empty:
449
  print(f"Adding {len(companies_data)} companies from database")
450
  for _, row in companies_data.iterrows():
@@ -517,7 +517,7 @@ def update_company_choices(user_input: str):
517
  align-items: center;
518
  pointer-events: all;
519
  animation: messageFadeIn 0.3s ease-in-out;
520
- # Global variable to store company mapping relationships
521
  <div style="
522
  width: 16px;
523
  height: 16px;
@@ -534,10 +534,10 @@ def update_company_choices(user_input: str):
534
  if (msg) {{
535
  msg.style.animation = 'messageFadeOut 0.3s ease-in-out';
536
  setTimeout(function() {{ msg.remove(); }}, 3000);
537
- # Global variable to store company mapping relationships
538
  }}, 3000);
539
  </script>
540
- # Global variable to store company mapping relationships
541
  yield gr.update(choices=["No results found"], visible=True), gr.update(visible=True, value=error_html)
542
  else:
543
  # 第三次:更新为真实结果
@@ -591,7 +591,7 @@ def add_company(selected, current_list):
591
  "company_name": company_name,
592
  "stock_code": stock_code,
593
  "cik": cik # ✅ 保存CIK用于后续重复判断
594
- # Global variable to store company mapping relationships
595
 
596
  # ✅ 同时更新 companies_map,确保其他板块能获取到股票代码
597
  companies_map[company_name] = {"NAME": company_name, "CODE": stock_code}
@@ -678,7 +678,7 @@ def update_report_section(selected_company, report_data, stock_code):
678
  try:
679
  # prmpt = f"""
680
 
681
- # Global variable to store company mapping relationships
682
  stock_code = get_stock_code_by_company_name(selected_company)
683
  # result = get_report_data(stock_code)
684
  # print(f"get_report_data=====================: {result}")
@@ -721,16 +721,16 @@ def update_report_section(selected_company, report_data, stock_code):
721
  margin: 8px 0;
722
  width: 100%;
723
  transition: all 0.2s;
724
- # Global variable to store company mapping relationships
725
  .report-toggle-btn:hover {
726
  background: #e5e7eb;
727
- # Global variable to store company mapping relationships
728
  .report-extra {
729
  display: none;
730
- # Global variable to store company mapping relationships
731
  .report-extra.show {
732
  display: block;
733
- # Global variable to store company mapping relationships
734
  </style>'''
735
 
736
  # 显示前5个
@@ -747,7 +747,7 @@ def update_report_section(selected_company, report_data, stock_code):
747
  </svg>
748
  </div>
749
  </div>
750
- # Global variable to store company mapping relationships
751
 
752
  # 剩余的放在可折叠区域
753
  if total_reports > show_limit:
@@ -765,7 +765,7 @@ def update_report_section(selected_company, report_data, stock_code):
765
  </svg>
766
  </div>
767
  </div>
768
- # Global variable to store company mapping relationships
769
  html_content += '</div>'
770
 
771
  # 添加展开/收起按钮
@@ -777,7 +777,7 @@ def update_report_section(selected_company, report_data, stock_code):
777
  }} else {{
778
  extra.classList.add('show');
779
  this.innerHTML = '↑ Show Less';
780
- # Global variable to store company mapping relationships
781
  ">↓ Show All ({total_reports} reports)</div>'''
782
 
783
  html_content += '</div>'
@@ -831,16 +831,16 @@ def update_news_section(selected_company):
831
  margin: 8px 0;
832
  width: 100%;
833
  transition: all 0.2s;
834
- # Global variable to store company mapping relationships
835
  .news-toggle-btn:hover {
836
  background: #e5e7eb;
837
- # Global variable to store company mapping relationships
838
  .news-extra {
839
  display: none;
840
- # Global variable to store company mapping relationships
841
  .news-extra.show {
842
  display: block;
843
- # Global variable to store company mapping relationships
844
  </style>'''
845
 
846
  from datetime import datetime
@@ -857,7 +857,7 @@ def update_news_section(selected_company):
857
  <span class="text-gray-800">{news['headline']}</span>
858
  </div>
859
  </div>
860
- # Global variable to store company mapping relationships
861
 
862
  # 剩余的放在可折叠区域
863
  if total_news > show_limit:
@@ -873,7 +873,7 @@ def update_news_section(selected_company):
873
  <span class="text-gray-800">{news['headline']}</span>
874
  </div>
875
  </div>
876
- # Global variable to store company mapping relationships
877
  news_html += '</div>'
878
 
879
  # 添加展开/收起按钮
@@ -885,7 +885,7 @@ def update_news_section(selected_company):
885
  }} else {{
886
  extra.classList.add('show');
887
  this.innerHTML = '↑ Show Less';
888
- # Global variable to store company mapping relationships
889
  ">↓ Show All ({total_news} news)</div>'''
890
 
891
  news_html += '</div>'
@@ -913,7 +913,7 @@ def create_header():
913
  <path d="M44 11v27c0 3.314-8.954 6-20 6S4 41.314 4 38V11"></path>
914
  <path d="M44 29c0 3.314-8.954 6-20 6S4 32.314 4 29m40-9c0 3.314-8.954 6-20 6S4 23.314 4 20"></path>
915
  <ellipse cx="24" cy="10" rx="20" ry="6"></ellipse>
916
- # First return loading status
917
  </svg>
918
  <span class="logo-title">Easy Financial Report Dashboard</span>
919
  </div>
@@ -931,7 +931,7 @@ def create_company_list(get_companys_state):
931
  companies_data = my_companies
932
  print(f"创建公司列表组件 - Companies data: {companies_data}")
933
  if isinstance(companies_data, list) and len(companies_data) > 0:
934
- # my_companies is a list of objects [{company_name: '', stock_code: ''}, ...]
935
  choices = [str(item.get('company_name', 'Unknown')) for item in companies_data]
936
  elif isinstance(companies_data, pd.DataFrame) and not companies_data.empty:
937
  choices = [str(row.get('company_name', 'Unknown')) for _, row in companies_data.iterrows()]
@@ -953,7 +953,7 @@ def create_company_list(get_companys_state):
953
  elem_classes=["company-list-container"],
954
  container=False, # 不显示外部容器边框
955
  visible=True
956
- # Global variable to store company mapping relationships
957
 
958
  return company_list
959
 
@@ -965,14 +965,14 @@ def create_company_selector():
965
  placeholder=" Name, ticker, or CIK", # 留出空间给图标
966
  elem_classes=["company-input-search"],
967
  container=False
968
- # Global variable to store company mapping relationships
969
 
970
  # 状态消息显示区域
971
  status_message = gr.HTML(
972
- # Global variable to store company mapping relationships
973
  elem_classes=["status-message"],
974
  visible=False
975
- # Global variable to store company mapping relationships
976
 
977
  # 弹窗选择列表
978
  company_modal = gr.Radio(
@@ -980,7 +980,7 @@ def create_company_selector():
980
  choices=[],
981
  visible=False,
982
  elem_classes=["company-modal"]
983
- # Global variable to store company mapping relationships
984
 
985
  return company_input, status_message, company_modal
986
 
@@ -1008,7 +1008,7 @@ def create_news_section():
1008
  return news_display
1009
 
1010
  def format_financial_metrics(data: dict, prev_data: dict = None) -> list: # pyright: ignore[reportArgumentType]
1011
- # Global variable to store company mapping relationships
1012
  将原始财务数据转换为 financial_metrics 格式。
1013
 
1014
  Args:
@@ -1017,7 +1017,7 @@ def format_financial_metrics(data: dict, prev_data: dict = None) -> list: # pyr
1017
 
1018
  Returns:
1019
  list[dict]: 符合 financial_metrics 格式的列表
1020
- # Global variable to store company mapping relationships
1021
 
1022
  def format_currency(value: float) -> str:
1023
  """将数字格式化为 $XB / $XM / $XK"""
@@ -1041,37 +1041,37 @@ def format_financial_metrics(data: dict, prev_data: dict = None) -> list: # pyr
1041
 
1042
  # 定义指标映射
1043
  metrics_config = [
1044
- # Global variable to store company mapping relationships
1045
  "key": "total_revenue",
1046
  "label": "Total Revenue",
1047
  "is_currency": True,
1048
  "eps_like": False
1049
- # Global variable to store company mapping relationships
1050
- # Global variable to store company mapping relationships
1051
  "key": "net_income",
1052
  "label": "Net Income",
1053
  "is_currency": True,
1054
  "eps_like": False
1055
- # Global variable to store company mapping relationships
1056
- # Global variable to store company mapping relationships
1057
  "key": "earnings_per_share",
1058
  "label": "Earnings Per Share",
1059
  "is_currency": False, # EPS 不用 B/M 单位
1060
  "eps_like": True
1061
- # Global variable to store company mapping relationships
1062
- # Global variable to store company mapping relationships
1063
  "key": "operating_expenses",
1064
  "label": "Operating Expenses",
1065
  "is_currency": True,
1066
  "eps_like": False
1067
- # Global variable to store company mapping relationships
1068
- # Global variable to store company mapping relationships
1069
  "key": "operating_cash_flow",
1070
  "label": "Cash Flow",
1071
  "is_currency": True,
1072
  "eps_like": False
1073
- # Global variable to store company mapping relationships
1074
- # Global variable to store company mapping relationships
1075
 
1076
  result = []
1077
  for item in metrics_config:
@@ -1101,7 +1101,7 @@ def format_financial_metrics(data: dict, prev_data: dict = None) -> list: # pyr
1101
  "value": value_str,
1102
  "change": change_str,
1103
  "color": color
1104
- # Global variable to store company mapping relationships
1105
 
1106
  return result
1107
 
@@ -1123,7 +1123,7 @@ def create_sidebar():
1123
  margin-bottom: 16px;">
1124
  <h3 style="color: white; margin: 0; font-size: 16px; font-weight: 600;">Select Company</h3>
1125
  </div>
1126
- # ✅ Global variable to cache search results (including complete information like CIK)
1127
  with gr.Column():
1128
  company_list = create_company_list(get_companys_state)
1129
 
@@ -1134,7 +1134,7 @@ def create_sidebar():
1134
  # fn=create_company_list(True),
1135
  # inputs=[],
1136
  # outputs=[company_list, status_message]
1137
- return "" # Default return
1138
 
1139
  # 创建公司选择器
1140
  company_input, status_message, company_modal = create_company_selector()
@@ -1144,13 +1144,13 @@ def create_sidebar():
1144
  fn=update_company_choices,
1145
  inputs=[company_input],
1146
  outputs=[company_modal, status_message]
1147
- # Global variable to store company mapping relationships
1148
 
1149
  company_modal.change(
1150
  fn=add_company,
1151
  inputs=[company_modal, company_list],
1152
  outputs=[company_modal, company_list, status_message]
1153
- # Global variable to store company mapping relationships
1154
 
1155
  # 创建公司按钮组件
1156
  # # company_buttons = create_company_buttons()
@@ -1166,7 +1166,7 @@ def create_sidebar():
1166
  # # companies_data = get_companys()
1167
  # companies_data = my_companies
1168
  # if isinstance(companies_data, list) and len(companies_data) > 0:
1169
- # # my_companies is a list of objects [{company_name: '', stock_code: ''}, ...]
1170
  # updated_choices = [str(item.get('company_name', 'Unknown')) for item in companies_data]
1171
  # elif isinstance(companies_data, pd.DataFrame) and not companies_data.empty:
1172
  # updated_choices = [str(row.get('company_name', 'Unknown')) for _, row in companies_data.iterrows()]
@@ -1185,7 +1185,7 @@ def create_sidebar():
1185
  # fn=make_click_handler(company_name),
1186
  # inputs=[],
1187
  # outputs=[company_list]
1188
- return "" # Default return
1189
 
1190
  # 创建一个容器来容纳报告部分,初始时隐藏
1191
  with gr.Group(elem_classes=["report-news-box"]) as report_section_group:
@@ -1197,7 +1197,7 @@ def create_sidebar():
1197
  # 处理公司选择事件
1198
  def select_company_handler(company_name):
1199
  """处理公司选择事件的处理器"""
1200
- # Update global variable
1201
  g.SELECT_COMPANY = company_name if company_name else ""
1202
 
1203
  # 更新报告部分的内容
@@ -1216,7 +1216,7 @@ def create_sidebar():
1216
  fn=select_company_handler,
1217
  inputs=[company_list],
1218
  outputs=[report_section_group, report_display, news_display]
1219
- # Global variable to store company mapping relationships
1220
 
1221
  # 返回公司列表组件和报告部分组件
1222
  return company_list, report_section_group, report_display, news_display
@@ -1296,7 +1296,7 @@ def build_income_table(table_data):
1296
  {table_rows}
1297
  </table>
1298
  </div>
1299
- # Global variable to store company mapping relationships
1300
  return html
1301
  def create_metrics_dashboard():
1302
  """创建指标仪表板组件"""
@@ -1308,7 +1308,7 @@ def create_metrics_dashboard():
1308
  padding: 1.25rem;
1309
  min-height: 250px !important;
1310
  text-align: center;
1311
- # Global variable to store company mapping relationships
1312
 
1313
  # 构建左侧卡片
1314
  def build_stock_card():
@@ -1368,7 +1368,7 @@ def create_metrics_dashboard():
1368
  <div style="font-size: 14px; color: #555;">Prev Close</div><div style="font-size: 14px; font-weight: 500; text-align: center;">{prev_close_val}</div>
1369
  </div>
1370
  </div>
1371
- # Global variable to store company mapping relationships
1372
 
1373
  return html
1374
 
@@ -1382,7 +1382,7 @@ def create_metrics_dashboard():
1382
  {"label": "Earnings Per Share", "value": "N/A", "change": "N/A", "color": "grey"},
1383
  {"label": "Operating Expenses", "value": "N/A", "change": "N/A", "color": "grey"},
1384
  {"label": "Cash Flow", "value": "N/A", "change": "N/A", "color": "grey"}
1385
- # Global variable to store company mapping relationships
1386
  income_statement = {
1387
  "list_data": [
1388
  ["Category", "N/A/FY", "N/A/FY", "N/A/FY"],
@@ -1391,9 +1391,9 @@ def create_metrics_dashboard():
1391
  ["Earnings Per Share", "N/A", "N/A", "N/A"],
1392
  ["Operating Expenses", "N/A", "N/A", "N/A"],
1393
  ["Cash Flow", "N/A", "N/A", "N/A"]
1394
- # Global variable to store company mapping relationships
1395
  "yoy_rates": []
1396
- # Global variable to store company mapping relationships
1397
  yearly_data = 'N/A'
1398
  # 增长变化的 HTML 字符(箭头+百分比)
1399
  def render_change(change: str, color: str):
@@ -1423,7 +1423,7 @@ def create_metrics_dashboard():
1423
  <div style="font-size: 14px; color: #555;">{item['label']}</div>
1424
  <div style="font-size: 16px; font-weight: 500; color: #333;">{item['value']} {change_html}</div>
1425
  </div>
1426
- # Global variable to store company mapping relationships
1427
 
1428
  html = f"""
1429
  <div style="min-width: 300px;max-width: 450px;height: 300px !important;border: 1px solid #e0e0e0; border-radius: 8px; padding: 16px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); font-family: 'Segoe UI', sans-serif;">
@@ -1440,7 +1440,7 @@ def create_metrics_dashboard():
1440
  </div>
1441
  {metrics_html}
1442
  </div>
1443
- # Global variable to store company mapping relationships
1444
  return html
1445
 
1446
 
@@ -1591,7 +1591,7 @@ def update_metrics_dashboard(company_name):
1591
  <div style="font-size: 14px; color: #555;">Prev Close</div><div style="font-size: 14px; font-weight: 500; text-align: center;">{prev_close_val}</div>
1592
  </div>
1593
  </div>
1594
- # Global variable to store company mapping relationships
1595
  # <div style="font-size: 14px; color: #555;">Vol</div><div style="font-size: 14px; font-weight: 500; text-align: center;">{volume_display}</div>
1596
 
1597
  return html
@@ -1609,7 +1609,7 @@ def update_metrics_dashboard(company_name):
1609
  <div style="font-size: 14px; color: #555;">{item['label']}</div>
1610
  <div style="font-size: 16px; font-weight: 500; color: #333;">{item['value']} {change_html}</div>
1611
  </div>
1612
- # Global variable to store company mapping relationships
1613
 
1614
  html = f"""
1615
  <div style="width: 450px;height: 300px !important;border: 1px solid #e0e0e0; border-radius: 8px; padding: 16px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); font-family: 'Segoe UI', sans-serif;">
@@ -1626,7 +1626,7 @@ def update_metrics_dashboard(company_name):
1626
  </div>
1627
  {metrics_html}
1628
  </div>
1629
- # Global variable to store company mapping relationships
1630
  return html
1631
  # 返回三个HTML组件的内容
1632
  return build_stock_card(company_info), build_financial_metrics(yearly_data), build_income_table(table_data)
@@ -1734,7 +1734,7 @@ def create_chat_panel():
1734
  # # {"role": "assistant", "content": "Revenue trend for GlobalTech Inc.:\n\nQ4 2024: $2.53B (+8.2%)\nQ1 2025: $2.61B (+9.8%)\nQ2 2025: $2.71B (+11.6%)\nQ3 2025: $2.84B (+12.4%)"},
1735
  # # {"role": "assistant", "content": "Revenue trend for GlobalTech Inc.:\n\nQ4 2024: $2.53B (+8.2%)\nQ1 2025: $2.61B (+9.8%)\nQ2 2025: $2.71B (+11.6%)\nQ3 2025: $2.84B (+12.4%)"},
1736
  # # {"role": "assistant", "content": "Revenue trend for GlobalTech Inc.:\n\nQ4 2024: $2.53B (+8.2%)\nQ1 2025: $2.61B (+9.8%)\nQ2 2025: $2.71B (+11.6%)\nQ3 2025: $2.84B (+12.4%)"}
1737
- return "" # Default return
1738
  # type="messages",
1739
  # # elem_classes=["min-h-0", "overflow-y-auto", "space-y-4", "chat-content-box"],
1740
  # show_label=False,
@@ -1742,7 +1742,7 @@ def create_chat_panel():
1742
  # show_copy_button=True,
1743
  # height=400,
1744
  # container=False,
1745
- # Global variable to store company mapping relationships
1746
 
1747
  # # 输入区域
1748
  # with gr.Row(elem_classes=["border-t", "border-gray-200", "gap-2"]):
@@ -1753,13 +1753,13 @@ def create_chat_panel():
1753
  # lines=1,
1754
  # submit_btn=True,
1755
  # container=False,
1756
- return "" # Default return
1757
  # msg.submit(
1758
  # chat_bot,
1759
  # [msg, chatbot],
1760
  # [msg, chatbot],
1761
  # queue=True,
1762
- return "" # Default return
1763
 
1764
  # def load_css_files(css_dir, filenames):
1765
  # css_content = ""
@@ -1796,7 +1796,7 @@ def main():
1796
  os.path.join(css_dir, "main.css"),
1797
  os.path.join(css_dir, "components.css"),
1798
  os.path.join(css_dir, "layout.css")
1799
- # Global variable to store company mapping relationships
1800
  # css_dir = "path/to/your/css/folder" # 替换为你的实际路径
1801
  # 自动定位 css 文件夹(与 app.py 同级)
1802
  # BASE_DIR = os.path.dirname(os.path.abspath(__file__))
@@ -1852,7 +1852,7 @@ def main():
1852
  # fn=lambda company: f"# Investment Suggestions for {company}" if company else "# Please select a company",
1853
  # inputs=[selected_company_state],
1854
  # outputs=[company_display]
1855
- # Global variable to store company mapping relationships
1856
 
1857
  # 当选中的公司改变时,重新加载tab内容
1858
  def update_tab_content(company):
@@ -1872,7 +1872,7 @@ def main():
1872
  fn=update_tab_content,
1873
  inputs=[selected_company_state],
1874
  outputs=[tab_content],
1875
- # Global variable to store company mapping relationships
1876
  with gr.TabItem("Analysis Report", elem_classes=["tab-item"]):
1877
  # 创建一个用于显示公司名称的组件
1878
  # analysis_company_display = gr.Markdown("# Please select a company")
@@ -1884,7 +1884,7 @@ def main():
1884
  # fn=lambda company: f"# Analysis Report for {company}" if company else "# Please select a company",
1885
  # inputs=[selected_company_state],
1886
  # outputs=[analysis_company_display]
1887
- # Global variable to store company mapping relationships
1888
 
1889
  # 当选中的公司改变时,重新加载tab内容
1890
  def update_analysis_tab_content(company):
@@ -1904,7 +1904,7 @@ def main():
1904
  fn=update_analysis_tab_content,
1905
  inputs=[selected_company_state],
1906
  outputs=[analysis_tab_content]
1907
- # Global variable to store company mapping relationships
1908
  # with gr.TabItem("Comparison", elem_classes=["tab-item"]):
1909
  # create_tab_content("comparison")
1910
  with gr.Column(scale=2, min_width=400):
@@ -1917,9 +1917,9 @@ def main():
1917
  additional_inputs=[
1918
  gr.State(value=""), # CRITICAL: Store session URL across turns (hidden from UI)
1919
  gr.State(value={}) # CRITICAL: Store agent context across turns (hidden from UI)
1920
- # Global variable to store company mapping relationships
1921
  additional_inputs_accordion=gr.Accordion(label="Settings", open=False, visible=False), # Hide the accordion completely
1922
- # Global variable to store company mapping relationships
1923
 
1924
  # 在页面加载时设置默认选中的公司并加载数据
1925
  def load_default_company():
@@ -1962,13 +1962,13 @@ def main():
1962
  gr.update(choices=choices, value=default_company),
1963
  suggestion_result,
1964
  report_result
1965
- # Global variable to store company mapping relationships
1966
  return (
1967
- "# Global variable to store company mapping relationships
1968
  gr.update(choices=choices),
1969
  "<div style='padding: 20px; text-align: center; color: #666;'>Please select a company</div>",
1970
  "<div style='padding: 20px; text-align: center; color: #666;'>Please select a company</div>"
1971
- # Global variable to store company mapping relationships
1972
 
1973
  demo.load(
1974
  fn=load_default_company,
@@ -1979,9 +1979,9 @@ def main():
1979
  tab_content,
1980
  analysis_tab_content
1981
  # ✅ Financial Metrics组件不在这里输出,由selected_company_state.change事件触发更新
1982
- # Global variable to store company mapping relationships
1983
  concurrency_limit=None,
1984
- # Global variable to store company mapping relationships
1985
 
1986
  # 绑定公司选择事件到状态更新
1987
  # 注意:这里需要确保create_sidebar中没有重复绑定相同的事件
@@ -1990,7 +1990,7 @@ def main():
1990
  inputs=[company_list_component],
1991
  outputs=[selected_company_state],
1992
  concurrency_limit=None
1993
- # Global variable to store company mapping relationships
1994
 
1995
  # 绑定公司选择事件到指标仪表板更新
1996
  def update_metrics_dashboard_wrapper(company_name):
@@ -2010,7 +2010,7 @@ def main():
2010
  <p>Error loading financial data: {str(e)}</p>
2011
  <p>Please try again later.</p>
2012
  </div>
2013
- # Global variable to store company mapping relationships
2014
  yield error_html, error_html, error_html
2015
  else:
2016
  # 如果没有选择公司,返回空内容
@@ -2022,7 +2022,7 @@ def main():
2022
  inputs=[selected_company_state],
2023
  outputs=list(metrics_dashboard_components),
2024
  concurrency_limit=None
2025
- # Global variable to store company mapping relationships
2026
 
2027
  return demo
2028
 
 
5
  import pandas as pd
6
  # from dotenv import load_dotenv
7
 
8
+ # # 加载.env文件中的环境变量
9
  # load_dotenv()
10
  # from EasyFinancialAgent.chat import query_company
11
  from EasyFinancialAgent.chat_direct import advanced_search_company_detailed, search_and_format, search_company_direct, chatbot_response, format_search_result_for_display, format_search_result
 
22
  from service.three_year_table_tool import build_table_format
23
  from service.three_year_tool import process_financial_data_with_metadata
24
  from service.tool_processor import get_stock_price
25
+ # ✅ 导入缓存管理器
26
  from service.report_cache_manager import ReportCacheManager
27
  from service.financial_data_cache_manager import FinancialDataCacheManager
28
 
 
38
  {'company_name': 'Tesla', 'stock_code': 'TSLA', "cik": "0001318605"},
39
  {'company_name': 'AMD', 'stock_code': 'AMD', "cik": "0000002488"},
40
  {'company_name': 'Microsoft', 'stock_code': 'MSFT', "cik": "0000789019"}
41
+ ]
42
  # JavaScript代码用于读取和存储数据
43
  js_code = """
44
  function handleStorage(operation, key, value) {
 
49
  let storedValue = localStorage.getItem(key);
50
  if (storedValue === null) {
51
  return `未找到键: ${key}`;
52
+ }
53
  return `读取到: ${key} = ${storedValue}`;
54
  } else if (operation === 'clear') {
55
  localStorage.removeItem(key);
 
57
  } else if (operation === 'clearAll') {
58
  localStorage.clear();
59
  return '已清除所有数据';
60
+ }
61
+ }
62
+ """
63
  custom_css = """
64
+ /* 匹配所有以 gradio-container- 开头的类 */
65
  div[class^="gradio-container-"],
66
  div[class*=" gradio-container-"] {
67
  -webkit-text-size-adjust: 100% !important;
 
69
  font-family: unset !important;
70
  -moz-tab-size: 4 !important;
71
  tab-size: 4 !important;
72
+ }
73
 
74
  .company-list-container {
75
  background-color: white;
 
79
  border: 1px solid #e5e7eb;
80
  box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
81
  width: 100%;
82
+ }
83
 
84
+ /* 隐藏单选框 */
85
  .company-list-container input[type="radio"] {
86
  display: none;
87
+ }
88
 
89
+ /* 自定义选项样式 - 小而精致 */
90
  .company-list-container label {
91
  display: block;
92
  padding: 0.5rem 0.75rem;
 
102
  box-sizing: border-box;
103
  position: relative;
104
  padding-right: 2rem;
105
+ }
106
 
107
  /* ✅ 为每个公司选项添加删除按钮 */
108
  .company-list-container label::after {
 
125
  transition: opacity 0.2s;
126
  cursor: pointer;
127
  line-height: 1;
128
+ }
129
 
130
  .company-list-container label:hover::after {
131
  opacity: 1;
132
+ }
133
 
134
  .company-list-container label::after:hover {
135
  background: #9ca3af;
136
+ }
137
 
138
+ /* 悬停效果 */
139
  .company-list-container label:hover {
140
  background-color: #f3f4f6;
141
  border-color: #d1d5db;
142
+ }
143
 
144
+ /* 选中效果 - 使用蓝色 */
145
  .company-list-container input[type="radio"]:checked + span {
146
  background: #3b82f6 !important;
147
  color: white !important;
 
152
  padding: 0.75rem 1rem;
153
  margin: -0.75rem -1rem;
154
  border-radius: 0.375rem;
155
+ }
156
 
157
  .company-list-container span {
158
  display: block;
159
  padding: 0;
160
  border-radius: 0.375rem;
161
  width: 100%;
162
+ }
163
 
164
  label.selected {
165
  background: #3b82f6 !important;
166
  color: white !important;
167
+ }
168
 
169
+ /* 确保每行只有一个选项 */
170
  .company-list-container .wrap {
171
  display: block !important;
172
+ }
173
 
174
  .company-list-container .wrap li {
175
  display: block !important;
176
  width: 100% !important;
177
+ }
178
 
179
+ /* ✅ 搜索框样式 - 带内置图标 */
180
  .company-input-search {
181
  position: relative;
182
+ }
183
 
184
  .company-input-search input,
185
  .company-input-search textarea {
 
192
  border-radius: 8px !important;
193
  font-size: 14px !important;
194
  transition: all 0.2s !important;
195
+ }
196
 
197
  .company-input-search input:focus,
198
  .company-input-search textarea:focus {
199
  border-color: #667eea !important;
200
  box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1) !important;
201
  outline: none !important;
202
+ }
203
 
204
  /* ✅ 对齐Tabs和ChatInterface的横线 */
205
  .tabs {
206
  border-bottom: 1px solid #e5e7eb !important;
207
+ }
208
 
209
  .chatbot {
210
  border-top: 1px solid #e5e7eb !important;
211
+ }
212
 
213
  /* 确俟tab内容和chatbot的padding一致 */
214
  .tab-item {
215
  padding: 16px !important;
216
+ }
217
 
218
  /* ✅ 为三个板块添加间距 */
219
  #select-company-section {
220
  margin-bottom: 20px !important;
221
+ }
222
 
223
  .report-news-box {
224
  margin-top: 20px !important;
225
+ }
226
 
227
  .report-list-box,
228
  .news-list-box {
229
  margin-bottom: 16px !important;
230
+ }
231
 
232
  /* ✅ 对齐ChatInterface标题和Tabs标签栏 */
233
  .chatbot .wrap {
234
  margin-top: 0 !important;
235
+ }
236
 
237
  /* ChatInterface标题样式 */
238
  .chatbot > .wrap > .head {
 
242
  padding: 8px 16px !important;
243
  border-bottom: 1px solid #e5e7eb !important;
244
  background: #f9fafb !important;
245
+ }
246
 
247
  /* ✅ 缩小Easy Financial AI Assistant标题 - 更精致 */
248
  .chatbot > .wrap > .head h1,
 
253
  font-weight: 500 !important;
254
  color: #6b7280 !important;
255
  letter-spacing: 0.3px !important;
256
+ }
257
 
258
  /* Tabs标签栏样式 */
259
  .tab-container {
 
262
  align-items: center !important;
263
  border-bottom: 1px solid #e5e7eb !important;
264
  background: #f9fafb !important;
265
+ }
266
 
267
  /* ✅ 移除Tabs底部横线 */
268
  .tabs {
269
  border-bottom: none !important;
270
+ }
271
+ """
272
 
273
+ # 全局变量用于存储公司映射关系
274
  companies_map = {}
275
 
276
+ # ✅ 全局变量用于缓存搜索结果(包含CIK等完整信息)
277
  search_result_cache = {}
278
 
279
+ # 根据公司名称获取股票代码的函数
280
  def get_stock_code_by_company_name(company_name):
281
+ """根据公司名称获取股票代码"""
282
  if company_name in companies_map and "CODE" in companies_map[company_name]:
283
  return companies_map[company_name]["CODE"]
284
+ return "" # 默认返回
285
 
286
+ # 创建一个简单的函数来获取公司列表
287
  def get_company_list_choices():
288
  choices = []
289
  print(f"Getting init add company list choices...{get_companys_state}")
 
304
 
305
  # Sidebar service functions
306
 
307
+ # 处理公司点击事件的函数
308
  def handle_company_click(company_name):
309
+ """处理公司点击事件,先判断是否已经入库,如果没有则进行入库操作,然后刷新公司列表"""
310
  print(f"Handling click for company: {company_name}")
311
 
312
+ # 1. 判断是否已经入库
313
  if not check_company_exists(my_companies, company_name):
314
+ # 2. 如果没有入库,则进行入库操作
315
+ # 获取股票代码(如果有的话)
316
  stock_code = companies_map.get(company_name, {}).get("CODE", "Unknown")
317
  print(f"Inserting company {company_name} with code {stock_code}")
318
 
319
+ # 插入公司到数据库
320
  # success = insert_company(company_name, stock_code)
321
  my_companies.append({"company_name": company_name, "stock_code": stock_code})
322
+ print(f"Successfully inserted company: {company_name}") # 直接更新companies_map,而不是重新加载整个映射
323
+ # 直接更新companies_map,而不是重新加载整个映射
324
  companies_map[company_name] = {"NAME": company_name, "CODE": stock_code}
325
  # 使用Gradio的成功提示
326
  gr.Info(f"Successfully added company: {company_name}")
 
339
  # 从数据库获取所有公司
340
  # companies_data = get_companys()
341
  companies_data = my_companies
342
+ # 检查是否为错误信息
343
  if isinstance(companies_data, str):
344
+ if "查询执行失败" in companies_data:
345
+ return "<div class='text-red-500'>获取公司列表失败</div>"
346
  else:
347
+ # 如果是字符串但不是错误信息,可能需要特殊处理
348
  return ""
349
 
350
+ # 检查是否为DataFrame且为空
351
  if not isinstance(companies_data, pd.DataFrame) or companies_data.empty:
352
  return ""
353
 
354
+ # 生成HTML列表
355
  html_items = []
356
  for _, row in companies_data.iterrows():
357
  company_name = row.get('company_name', 'Unknown')
358
+ # 根据是否选中添加不同的样式类
359
  css_class = "company-item"
360
  if company_name == selected_company:
361
  css_class += " selected-company"
362
+ # 使用button元素来确保可点击性
363
  html_items.append(f'<button class="{css_class}" data-company="{company_name}" style="width:100%; text-align:left; border:none; background:none;">{company_name}</button>')
364
 
365
  return "\n".join(html_items)
366
  except Exception as e:
367
+ return f"<div class='text-red-500'>生成公司列表失败: {str(e)}</div>"
368
 
369
  def initialize_company_list(selected_company=""):
370
  return get_company_list_html(selected_company)
371
 
372
  def refresh_company_list(selected_company=""):
373
+ """刷新公司列表,返回最新的HTML内容,带loading效果"""
374
+ # 先返回loading状态
375
  loading_html = '''
376
  <div style="display: flex; justify-content: center; align-items: center; height: 100px;">
377
  <div class="loading-spinner" style="width: 24px; height: 24px; border: 3px solid #f3f3f3; border-top: 3px solid #3498db; border-radius: 50%; animation: spin 1s linear infinite;"></div>
 
379
  @keyframes spin {
380
  0% { transform: rotate(0deg); }
381
  100% { transform: rotate(360deg); }
382
+ }
383
  </style>
384
  </div>
385
+ '''
386
  yield loading_html
387
 
388
+ # 然后返回实际的数据
389
  yield get_company_list_html(selected_company)
390
 
391
+ # 新增函数:处理公司选择事件
392
  def select_company(company_name):
393
+ """处理公司选择事件,更新全局状态并返回更新后的公司列表"""
394
+ # 更新全局变量
395
  g.SELECT_COMPANY = company_name if company_name else ""
396
+ # 对于Radio组件,我们只需要返回更新后的选项列表
397
  try:
398
  # companies_data = get_companys()
399
  companies_data = my_companies
400
  if isinstance(companies_data, list) and len(companies_data) > 0:
401
+ # my_companies 是对象列表 [{company_name: '', stock_code: ''}, ...]
402
  choices = [str(item.get('company_name', 'Unknown')) for item in companies_data]
403
  elif isinstance(companies_data, pd.DataFrame) and not companies_data.empty:
404
  choices = [str(row.get('company_name', 'Unknown')) for _, row in companies_data.iterrows()]
 
409
  return gr.update(choices=choices, value=company_name)
410
 
411
  def initialize_companies_map():
412
+ """初始化 companies_map 字典"""
413
  global companies_map
414
  companies_map = {} # 清空之前的映射
415
 
 
428
  { "NAME": "Tesla", "CODE": "TSLA" },
429
  { "NAME": "AMD", "CODE": "AMD" },
430
  { "NAME": "Microsoft", "CODE": "MSFT" },
431
+ ]
432
 
433
+ # 将预定义公司添加到映射中
434
  for company in predefined_companies:
435
  companies_map[company["NAME"]] = {"NAME": company["NAME"], "CODE": company["CODE"]}
436
 
437
  # print(f"Predefined companies added: {len(predefined_companies)}")
438
 
439
+ # 从数据库获取公司数据
440
  # companies_data = get_companys()
441
  companies_data = my_companies
442
  # companies_data = window.cachedCompanies or []
 
444
 
445
  print(f"Companies data from DB: {companies_data}")
446
 
447
+ # 如果数据库中有公司数据,则添加到映射中(去重)
448
  if isinstance(companies_data, pd.DataFrame) and not companies_data.empty:
449
  print(f"Adding {len(companies_data)} companies from database")
450
  for _, row in companies_data.iterrows():
 
517
  align-items: center;
518
  pointer-events: all;
519
  animation: messageFadeIn 0.3s ease-in-out;
520
+ ">
521
  <div style="
522
  width: 16px;
523
  height: 16px;
 
534
  if (msg) {{
535
  msg.style.animation = 'messageFadeOut 0.3s ease-in-out';
536
  setTimeout(function() {{ msg.remove(); }}, 3000);
537
+ }}
538
  }}, 3000);
539
  </script>
540
+ '''
541
  yield gr.update(choices=["No results found"], visible=True), gr.update(visible=True, value=error_html)
542
  else:
543
  # 第三次:更新为真实结果
 
591
  "company_name": company_name,
592
  "stock_code": stock_code,
593
  "cik": cik # ✅ 保存CIK用于后续重复判断
594
+ })
595
 
596
  # ✅ 同时更新 companies_map,确保其他板块能获取到股票代码
597
  companies_map[company_name] = {"NAME": company_name, "CODE": stock_code}
 
678
  try:
679
  # prmpt = f"""
680
 
681
+ # """
682
  stock_code = get_stock_code_by_company_name(selected_company)
683
  # result = get_report_data(stock_code)
684
  # print(f"get_report_data=====================: {result}")
 
721
  margin: 8px 0;
722
  width: 100%;
723
  transition: all 0.2s;
724
+ }
725
  .report-toggle-btn:hover {
726
  background: #e5e7eb;
727
+ }
728
  .report-extra {
729
  display: none;
730
+ }
731
  .report-extra.show {
732
  display: block;
733
+ }
734
  </style>'''
735
 
736
  # 显示前5个
 
747
  </svg>
748
  </div>
749
  </div>
750
+ '''
751
 
752
  # 剩余的放在可折叠区域
753
  if total_reports > show_limit:
 
765
  </svg>
766
  </div>
767
  </div>
768
+ '''
769
  html_content += '</div>'
770
 
771
  # 添加展开/收起按钮
 
777
  }} else {{
778
  extra.classList.add('show');
779
  this.innerHTML = '↑ Show Less';
780
+ }}
781
  ">↓ Show All ({total_reports} reports)</div>'''
782
 
783
  html_content += '</div>'
 
831
  margin: 8px 0;
832
  width: 100%;
833
  transition: all 0.2s;
834
+ }
835
  .news-toggle-btn:hover {
836
  background: #e5e7eb;
837
+ }
838
  .news-extra {
839
  display: none;
840
+ }
841
  .news-extra.show {
842
  display: block;
843
+ }
844
  </style>'''
845
 
846
  from datetime import datetime
 
857
  <span class="text-gray-800">{news['headline']}</span>
858
  </div>
859
  </div>
860
+ '''
861
 
862
  # 剩余的放在可折叠区域
863
  if total_news > show_limit:
 
873
  <span class="text-gray-800">{news['headline']}</span>
874
  </div>
875
  </div>
876
+ '''
877
  news_html += '</div>'
878
 
879
  # 添加展开/收起按钮
 
885
  }} else {{
886
  extra.classList.add('show');
887
  this.innerHTML = '↑ Show Less';
888
+ }}
889
  ">↓ Show All ({total_news} news)</div>'''
890
 
891
  news_html += '</div>'
 
913
  <path d="M44 11v27c0 3.314-8.954 6-20 6S4 41.314 4 38V11"></path>
914
  <path d="M44 29c0 3.314-8.954 6-20 6S4 32.314 4 29m40-9c0 3.314-8.954 6-20 6S4 23.314 4 20"></path>
915
  <ellipse cx="24" cy="10" rx="20" ry="6"></ellipse>
916
+ </g>
917
  </svg>
918
  <span class="logo-title">Easy Financial Report Dashboard</span>
919
  </div>
 
931
  companies_data = my_companies
932
  print(f"创建公司列表组件 - Companies data: {companies_data}")
933
  if isinstance(companies_data, list) and len(companies_data) > 0:
934
+ # my_companies 是对象列表 [{company_name: '', stock_code: ''}, ...]
935
  choices = [str(item.get('company_name', 'Unknown')) for item in companies_data]
936
  elif isinstance(companies_data, pd.DataFrame) and not companies_data.empty:
937
  choices = [str(row.get('company_name', 'Unknown')) for _, row in companies_data.iterrows()]
 
953
  elem_classes=["company-list-container"],
954
  container=False, # 不显示外部容器边框
955
  visible=True
956
+ )
957
 
958
  return company_list
959
 
 
965
  placeholder=" Name, ticker, or CIK", # 留出空间给图标
966
  elem_classes=["company-input-search"],
967
  container=False
968
+ )
969
 
970
  # 状态消息显示区域
971
  status_message = gr.HTML(
972
+ "",
973
  elem_classes=["status-message"],
974
  visible=False
975
+ )
976
 
977
  # 弹窗选择列表
978
  company_modal = gr.Radio(
 
980
  choices=[],
981
  visible=False,
982
  elem_classes=["company-modal"]
983
+ )
984
 
985
  return company_input, status_message, company_modal
986
 
 
1008
  return news_display
1009
 
1010
  def format_financial_metrics(data: dict, prev_data: dict = None) -> list: # pyright: ignore[reportArgumentType]
1011
+ """
1012
  将原始财务数据转换为 financial_metrics 格式。
1013
 
1014
  Args:
 
1017
 
1018
  Returns:
1019
  list[dict]: 符合 financial_metrics 格式的列表
1020
+ """
1021
 
1022
  def format_currency(value: float) -> str:
1023
  """将数字格式化为 $XB / $XM / $XK"""
 
1041
 
1042
  # 定义指标映射
1043
  metrics_config = [
1044
+ {
1045
  "key": "total_revenue",
1046
  "label": "Total Revenue",
1047
  "is_currency": True,
1048
  "eps_like": False
1049
+ },
1050
+ {
1051
  "key": "net_income",
1052
  "label": "Net Income",
1053
  "is_currency": True,
1054
  "eps_like": False
1055
+ },
1056
+ {
1057
  "key": "earnings_per_share",
1058
  "label": "Earnings Per Share",
1059
  "is_currency": False, # EPS 不用 B/M 单位
1060
  "eps_like": True
1061
+ },
1062
+ {
1063
  "key": "operating_expenses",
1064
  "label": "Operating Expenses",
1065
  "is_currency": True,
1066
  "eps_like": False
1067
+ },
1068
+ {
1069
  "key": "operating_cash_flow",
1070
  "label": "Cash Flow",
1071
  "is_currency": True,
1072
  "eps_like": False
1073
+ }
1074
+ ]
1075
 
1076
  result = []
1077
  for item in metrics_config:
 
1101
  "value": value_str,
1102
  "change": change_str,
1103
  "color": color
1104
+ })
1105
 
1106
  return result
1107
 
 
1123
  margin-bottom: 16px;">
1124
  <h3 style="color: white; margin: 0; font-size: 16px; font-weight: 600;">Select Company</h3>
1125
  </div>
1126
+ """)
1127
  with gr.Column():
1128
  company_list = create_company_list(get_companys_state)
1129
 
 
1134
  # fn=create_company_list(True),
1135
  # inputs=[],
1136
  # outputs=[company_list, status_message]
1137
+ # )
1138
 
1139
  # 创建公司选择器
1140
  company_input, status_message, company_modal = create_company_selector()
 
1144
  fn=update_company_choices,
1145
  inputs=[company_input],
1146
  outputs=[company_modal, status_message]
1147
+ )
1148
 
1149
  company_modal.change(
1150
  fn=add_company,
1151
  inputs=[company_modal, company_list],
1152
  outputs=[company_modal, company_list, status_message]
1153
+ )
1154
 
1155
  # 创建公司按钮组件
1156
  # # company_buttons = create_company_buttons()
 
1166
  # # companies_data = get_companys()
1167
  # companies_data = my_companies
1168
  # if isinstance(companies_data, list) and len(companies_data) > 0:
1169
+ # # my_companies 是对象列表 [{company_name: '', stock_code: ''}, ...]
1170
  # updated_choices = [str(item.get('company_name', 'Unknown')) for item in companies_data]
1171
  # elif isinstance(companies_data, pd.DataFrame) and not companies_data.empty:
1172
  # updated_choices = [str(row.get('company_name', 'Unknown')) for _, row in companies_data.iterrows()]
 
1185
  # fn=make_click_handler(company_name),
1186
  # inputs=[],
1187
  # outputs=[company_list]
1188
+ # )
1189
 
1190
  # 创建一个容器来容纳报告部分,初始时隐藏
1191
  with gr.Group(elem_classes=["report-news-box"]) as report_section_group:
 
1197
  # 处理公司选择事件
1198
  def select_company_handler(company_name):
1199
  """处理公司选择事件的处理器"""
1200
+ # 更新全局变量
1201
  g.SELECT_COMPANY = company_name if company_name else ""
1202
 
1203
  # 更新报告部分的内容
 
1216
  fn=select_company_handler,
1217
  inputs=[company_list],
1218
  outputs=[report_section_group, report_display, news_display]
1219
+ )
1220
 
1221
  # 返回公司列表组件和报告部分组件
1222
  return company_list, report_section_group, report_display, news_display
 
1296
  {table_rows}
1297
  </table>
1298
  </div>
1299
+ """
1300
  return html
1301
  def create_metrics_dashboard():
1302
  """创建指标仪表板组件"""
 
1308
  padding: 1.25rem;
1309
  min-height: 250px !important;
1310
  text-align: center;
1311
+ '''
1312
 
1313
  # 构建左侧卡片
1314
  def build_stock_card():
 
1368
  <div style="font-size: 14px; color: #555;">Prev Close</div><div style="font-size: 14px; font-weight: 500; text-align: center;">{prev_close_val}</div>
1369
  </div>
1370
  </div>
1371
+ """
1372
 
1373
  return html
1374
 
 
1382
  {"label": "Earnings Per Share", "value": "N/A", "change": "N/A", "color": "grey"},
1383
  {"label": "Operating Expenses", "value": "N/A", "change": "N/A", "color": "grey"},
1384
  {"label": "Cash Flow", "value": "N/A", "change": "N/A", "color": "grey"}
1385
+ ]
1386
  income_statement = {
1387
  "list_data": [
1388
  ["Category", "N/A/FY", "N/A/FY", "N/A/FY"],
 
1391
  ["Earnings Per Share", "N/A", "N/A", "N/A"],
1392
  ["Operating Expenses", "N/A", "N/A", "N/A"],
1393
  ["Cash Flow", "N/A", "N/A", "N/A"]
1394
+ ],
1395
  "yoy_rates": []
1396
+ }
1397
  yearly_data = 'N/A'
1398
  # 增长变化的 HTML 字符(箭头+百分比)
1399
  def render_change(change: str, color: str):
 
1423
  <div style="font-size: 14px; color: #555;">{item['label']}</div>
1424
  <div style="font-size: 16px; font-weight: 500; color: #333;">{item['value']} {change_html}</div>
1425
  </div>
1426
+ """
1427
 
1428
  html = f"""
1429
  <div style="min-width: 300px;max-width: 450px;height: 300px !important;border: 1px solid #e0e0e0; border-radius: 8px; padding: 16px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); font-family: 'Segoe UI', sans-serif;">
 
1440
  </div>
1441
  {metrics_html}
1442
  </div>
1443
+ """
1444
  return html
1445
 
1446
 
 
1591
  <div style="font-size: 14px; color: #555;">Prev Close</div><div style="font-size: 14px; font-weight: 500; text-align: center;">{prev_close_val}</div>
1592
  </div>
1593
  </div>
1594
+ """
1595
  # <div style="font-size: 14px; color: #555;">Vol</div><div style="font-size: 14px; font-weight: 500; text-align: center;">{volume_display}</div>
1596
 
1597
  return html
 
1609
  <div style="font-size: 14px; color: #555;">{item['label']}</div>
1610
  <div style="font-size: 16px; font-weight: 500; color: #333;">{item['value']} {change_html}</div>
1611
  </div>
1612
+ """
1613
 
1614
  html = f"""
1615
  <div style="width: 450px;height: 300px !important;border: 1px solid #e0e0e0; border-radius: 8px; padding: 16px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); font-family: 'Segoe UI', sans-serif;">
 
1626
  </div>
1627
  {metrics_html}
1628
  </div>
1629
+ """
1630
  return html
1631
  # 返回三个HTML组件的内容
1632
  return build_stock_card(company_info), build_financial_metrics(yearly_data), build_income_table(table_data)
 
1734
  # # {"role": "assistant", "content": "Revenue trend for GlobalTech Inc.:\n\nQ4 2024: $2.53B (+8.2%)\nQ1 2025: $2.61B (+9.8%)\nQ2 2025: $2.71B (+11.6%)\nQ3 2025: $2.84B (+12.4%)"},
1735
  # # {"role": "assistant", "content": "Revenue trend for GlobalTech Inc.:\n\nQ4 2024: $2.53B (+8.2%)\nQ1 2025: $2.61B (+9.8%)\nQ2 2025: $2.71B (+11.6%)\nQ3 2025: $2.84B (+12.4%)"},
1736
  # # {"role": "assistant", "content": "Revenue trend for GlobalTech Inc.:\n\nQ4 2024: $2.53B (+8.2%)\nQ1 2025: $2.61B (+9.8%)\nQ2 2025: $2.71B (+11.6%)\nQ3 2025: $2.84B (+12.4%)"}
1737
+ # ],
1738
  # type="messages",
1739
  # # elem_classes=["min-h-0", "overflow-y-auto", "space-y-4", "chat-content-box"],
1740
  # show_label=False,
 
1742
  # show_copy_button=True,
1743
  # height=400,
1744
  # container=False,
1745
+ # )
1746
 
1747
  # # 输入区域
1748
  # with gr.Row(elem_classes=["border-t", "border-gray-200", "gap-2"]):
 
1753
  # lines=1,
1754
  # submit_btn=True,
1755
  # container=False,
1756
+ # )
1757
  # msg.submit(
1758
  # chat_bot,
1759
  # [msg, chatbot],
1760
  # [msg, chatbot],
1761
  # queue=True,
1762
+ # )
1763
 
1764
  # def load_css_files(css_dir, filenames):
1765
  # css_content = ""
 
1796
  os.path.join(css_dir, "main.css"),
1797
  os.path.join(css_dir, "components.css"),
1798
  os.path.join(css_dir, "layout.css")
1799
+ ]
1800
  # css_dir = "path/to/your/css/folder" # 替换为你的实际路径
1801
  # 自动定位 css 文件夹(与 app.py 同级)
1802
  # BASE_DIR = os.path.dirname(os.path.abspath(__file__))
 
1852
  # fn=lambda company: f"# Investment Suggestions for {company}" if company else "# Please select a company",
1853
  # inputs=[selected_company_state],
1854
  # outputs=[company_display]
1855
+ # )
1856
 
1857
  # 当选中的公司改变时,重新加载tab内容
1858
  def update_tab_content(company):
 
1872
  fn=update_tab_content,
1873
  inputs=[selected_company_state],
1874
  outputs=[tab_content],
1875
+ )
1876
  with gr.TabItem("Analysis Report", elem_classes=["tab-item"]):
1877
  # 创建一个用于显示公司名称的组件
1878
  # analysis_company_display = gr.Markdown("# Please select a company")
 
1884
  # fn=lambda company: f"# Analysis Report for {company}" if company else "# Please select a company",
1885
  # inputs=[selected_company_state],
1886
  # outputs=[analysis_company_display]
1887
+ # )
1888
 
1889
  # 当选中的公司改变时,重新加载tab内容
1890
  def update_analysis_tab_content(company):
 
1904
  fn=update_analysis_tab_content,
1905
  inputs=[selected_company_state],
1906
  outputs=[analysis_tab_content]
1907
+ )
1908
  # with gr.TabItem("Comparison", elem_classes=["tab-item"]):
1909
  # create_tab_content("comparison")
1910
  with gr.Column(scale=2, min_width=400):
 
1917
  additional_inputs=[
1918
  gr.State(value=""), # CRITICAL: Store session URL across turns (hidden from UI)
1919
  gr.State(value={}) # CRITICAL: Store agent context across turns (hidden from UI)
1920
+ ],
1921
  additional_inputs_accordion=gr.Accordion(label="Settings", open=False, visible=False), # Hide the accordion completely
1922
+ )
1923
 
1924
  # 在页面加载时设置默认选中的公司并加载数据
1925
  def load_default_company():
 
1962
  gr.update(choices=choices, value=default_company),
1963
  suggestion_result,
1964
  report_result
1965
+ )
1966
  return (
1967
+ "",
1968
  gr.update(choices=choices),
1969
  "<div style='padding: 20px; text-align: center; color: #666;'>Please select a company</div>",
1970
  "<div style='padding: 20px; text-align: center; color: #666;'>Please select a company</div>"
1971
+ )
1972
 
1973
  demo.load(
1974
  fn=load_default_company,
 
1979
  tab_content,
1980
  analysis_tab_content
1981
  # ✅ Financial Metrics组件不在这里输出,由selected_company_state.change事件触发更新
1982
+ ],
1983
  concurrency_limit=None,
1984
+ )
1985
 
1986
  # 绑定公司选择事件到状态更新
1987
  # 注意:这里需要确保create_sidebar中没有重复绑定相同的事件
 
1990
  inputs=[company_list_component],
1991
  outputs=[selected_company_state],
1992
  concurrency_limit=None
1993
+ )
1994
 
1995
  # 绑定公司选择事件到指标仪表板更新
1996
  def update_metrics_dashboard_wrapper(company_name):
 
2010
  <p>Error loading financial data: {str(e)}</p>
2011
  <p>Please try again later.</p>
2012
  </div>
2013
+ '''
2014
  yield error_html, error_html, error_html
2015
  else:
2016
  # 如果没有选择公司,返回空内容
 
2022
  inputs=[selected_company_state],
2023
  outputs=list(metrics_dashboard_components),
2024
  concurrency_limit=None
2025
+ )
2026
 
2027
  return demo
2028