dev-yuje commited on
Commit
1bc1479
·
1 Parent(s): 80ff70e

refactor: 4:6 레이아웃 비율 적용, 키워드 배지 형태 변환 및 뉴스 피드 카드 전체 클릭 링크 연동

Browse files
Files changed (1) hide show
  1. app.py +68 -79
app.py CHANGED
@@ -184,19 +184,16 @@ def get_db_stats() -> Dict[str, Any]:
184
 
185
  def build_stats_html(stats: Dict[str, Any]) -> str:
186
  """조회된 지식 그래프 통계 정보들을 바탕으로 미려하고 컴팩트한 대시보드용 HTML을 생성합니다."""
187
- # 1. 기술 HTML 생성
188
- tech_html: str = ""
189
  for t in stats.get("techs_list", []):
190
- tech_html += f"""
191
- <div class="definition-item">
192
- <span class="definition-name">💡 {t['name']}</span>
193
- <span class="definition-desc">{t['desc']}</span>
194
- </div>
195
  """
196
- if not tech_html:
197
- tech_html = '<div style="font-size:12px; color:#94a3b8;">등록된 기술이 없습니다.</div>'
198
 
199
- # 2. 최근 기사 리스트 HTML 생성 (최대 3개)
200
  news_list_html: str = ""
201
  for a in stats.get("recent_articles", []):
202
  title = a["title"]
@@ -204,10 +201,12 @@ def build_stats_html(stats: Dict[str, Any]) -> str:
204
  target = 'target="_blank"' if url != "#" else ""
205
  date_str = str(a['date'])[:10] if a['date'] else ""
206
  news_list_html += f"""
207
- <div class="news-item">
208
- <a class="news-title" href="{url}" {target}>{title}</a>
209
- <div class="news-meta">🗓️ {date_str}</div>
210
- </div>
 
 
211
  """
212
  if not news_list_html:
213
  news_list_html = '<div style="font-size:12px; color:#94a3b8;">최근 수집된 기사가 없습니다.</div>'
@@ -235,9 +234,9 @@ def build_stats_html(stats: Dict[str, Any]) -> str:
235
  </div>
236
  </div>
237
 
238
- <div class="section-subtitle">💡 핵심 AI 기술 사전</div>
239
- <div class="definition-list">
240
- {tech_html}
241
  </div>
242
 
243
  <div class="section-subtitle">📰 최신 뉴스 피드</div>
@@ -348,59 +347,38 @@ body {
348
  color: #94a3b8;
349
  }
350
 
351
- /* 주요 기술 정의 리스트 글래스모피즘 스타일 */
352
- .definition-list {
353
  display: flex;
354
- flex-direction: column;
355
- gap: 6px;
356
  margin-bottom: 12px;
357
  }
358
- .definition-item {
359
- background: rgba(255, 255, 255, 0.55);
360
- border: 1px solid rgba(196, 195, 236, 0.35);
361
- border-radius: 6px;
362
- padding: 8px 10px;
363
- display: flex;
364
- flex-direction: column;
365
- gap: 2px;
366
- box-shadow: 0 1px 2px rgba(88, 89, 125, 0.01);
367
- transition: all 0.2s ease;
368
- }
369
- .definition-item:hover {
370
- background: rgba(255, 255, 255, 0.85);
371
- border-color: rgba(91, 91, 127, 0.45);
372
- }
373
- .dark .definition-item {
374
- background: rgba(30, 41, 59, 0.5);
375
- border-color: rgba(129, 140, 248, 0.15);
376
- }
377
- .dark .definition-item:hover {
378
- background: rgba(30, 41, 59, 0.8);
379
- border-color: rgba(129, 140, 248, 0.4);
380
  }
381
- .definition-name {
382
- font-size: 13px !important;
383
- font-weight: 800;
384
- color: #5b5b7f; /* 퍼플 포인트 */
385
- display: flex;
386
- align-items: center;
387
- gap: 4px;
388
  }
389
- .dark .definition-name {
 
 
390
  color: #c4c3ec;
391
  }
392
- .definition-desc {
393
- font-size: 11px !important;
394
- color: #47464e;
395
- line-height: 1.4;
396
- }
397
- .dark .definition-desc {
398
- color: #cbd5e1;
399
- }
400
 
401
- /* 최근 뉴스 타임라인 스크롤바 스타일 */
402
  .news-feed-container {
403
- max-height: 140px;
404
  overflow-y: auto;
405
  border: 1px solid rgba(196, 195, 236, 0.35);
406
  border-radius: 6px;
@@ -426,36 +404,47 @@ body {
426
  background: rgba(196, 195, 236, 0.3);
427
  }
428
 
429
- .news-item {
430
- border-left: 3px solid #5b5b7f; /* 퍼플 포인트 */
431
- padding-left: 8px;
432
  margin-bottom: 8px;
433
- position: relative;
434
  }
435
- .news-item:last-child {
436
  margin-bottom: 0;
437
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
438
  .news-title {
439
  font-size: 12px !important;
440
  font-weight: 600;
441
  color: #1b1c1a;
442
- text-decoration: none;
443
  line-height: 1.4;
444
- display: block;
445
  white-space: nowrap;
446
  overflow: hidden;
447
  text-overflow: ellipsis;
448
  }
449
- .news-title:hover {
450
- color: #5b5b7f;
451
- text-decoration: underline;
452
- }
453
  .dark .news-title {
454
  color: #cbd5e1;
455
  }
456
- .dark .news-title:hover {
457
- color: #c4c3ec;
458
- }
459
  .news-meta {
460
  font-size: 10px !important;
461
  color: #94a3b8;
@@ -634,14 +623,14 @@ with gr.Blocks(**blocks_kwargs) as demo:
634
  """)
635
 
636
  with gr.Row():
637
- # 2. 왼쪽 컬럼: 사이드바 (대시보드 및 하단 메뉴) - 반반 (50/50) split을 위해 scale=1 설정
638
- with gr.Column(scale=1, min_width=450):
639
  stats_data = get_db_stats()
640
  stats_html = build_stats_html(stats_data)
641
  gr.HTML(stats_html)
642
 
643
- # 3. 오른쪽 컬럼: 메인 챗봇 에어리어 - 반반 (50/50) split을 위해 scale=1 설정
644
- with gr.Column(scale=1, min_width=450):
645
  # 메인 타이틀 (챗봇 영역 상단 중앙)
646
  gr.HTML("""
647
  <div style="text-align: center; padding: 10px 0 20px 0;">
 
184
 
185
  def build_stats_html(stats: Dict[str, Any]) -> str:
186
  """조회된 지식 그래프 통계 정보들을 바탕으로 미려하고 컴팩트한 대시보드용 HTML을 생성합니다."""
187
+ # 1. 최신 키워드 배지 HTML 생성 (둥근 네모 형태)
188
+ keyword_html: str = ""
189
  for t in stats.get("techs_list", []):
190
+ keyword_html += f"""
191
+ <span class="keyword-badge"># {t['name']}</span>
 
 
 
192
  """
193
+ if not keyword_html:
194
+ keyword_html = '<div style="font-size:12px; color:#94a3b8;">등록된 키워드가 없습니다.</div>'
195
 
196
+ # 2. 최근 기사 리스트 HTML 생성 (최대 3개) - 전체 영역 클릭 시 이동하도록 a 태그로 래핑
197
  news_list_html: str = ""
198
  for a in stats.get("recent_articles", []):
199
  title = a["title"]
 
201
  target = 'target="_blank"' if url != "#" else ""
202
  date_str = str(a['date'])[:10] if a['date'] else ""
203
  news_list_html += f"""
204
+ <a class="news-item-link" href="{url}" {target}>
205
+ <div class="news-item">
206
+ <div class="news-title">{title}</div>
207
+ <div class="news-meta">🗓️ {date_str}</div>
208
+ </div>
209
+ </a>
210
  """
211
  if not news_list_html:
212
  news_list_html = '<div style="font-size:12px; color:#94a3b8;">최근 수집된 기사가 없습니다.</div>'
 
234
  </div>
235
  </div>
236
 
237
+ <div class="section-subtitle">💡 최신 뉴스 키워드</div>
238
+ <div class="keyword-container">
239
+ {keyword_html}
240
  </div>
241
 
242
  <div class="section-subtitle">📰 최신 뉴스 피드</div>
 
347
  color: #94a3b8;
348
  }
349
 
350
+ /* 최신 뉴스 키워드 컨테이너 둥근 배지 스타일 */
351
+ .keyword-container {
352
  display: flex;
353
+ flex-wrap: wrap;
354
+ gap: 8px;
355
  margin-bottom: 12px;
356
  }
357
+ .keyword-badge {
358
+ display: inline-block;
359
+ background: rgba(196, 195, 236, 0.2);
360
+ border: 1px solid rgba(196, 195, 236, 0.55);
361
+ border-radius: 8px; /* 이전처럼 약간 둥근 네모 */
362
+ padding: 6px 12px;
363
+ font-size: 11px !important;
364
+ font-weight: 700;
365
+ color: #5b5b7f;
366
+ box-shadow: 0 1px 3px rgba(88, 89, 125, 0.02);
367
+ transition: all 0.2s ease-in-out;
 
 
 
 
 
 
 
 
 
 
 
368
  }
369
+ .keyword-badge:hover {
370
+ background: rgba(196, 195, 236, 0.35);
371
+ transform: scale(1.03);
 
 
 
 
372
  }
373
+ .dark .keyword-badge {
374
+ background: rgba(129, 140, 248, 0.12);
375
+ border-color: rgba(129, 140, 248, 0.25);
376
  color: #c4c3ec;
377
  }
 
 
 
 
 
 
 
 
378
 
379
+ /* 최근 뉴스 피드 클릭 가능한 카드 레이아웃 */
380
  .news-feed-container {
381
+ max-height: 150px;
382
  overflow-y: auto;
383
  border: 1px solid rgba(196, 195, 236, 0.35);
384
  border-radius: 6px;
 
404
  background: rgba(196, 195, 236, 0.3);
405
  }
406
 
407
+ .news-item-link {
408
+ text-decoration: none;
409
+ display: block;
410
  margin-bottom: 8px;
 
411
  }
412
+ .news-item-link:last-child {
413
  margin-bottom: 0;
414
  }
415
+ .news-item {
416
+ border-left: 3px solid #5b5b7f; /* 퍼플 포인트 */
417
+ padding: 8px 10px;
418
+ background: rgba(255, 255, 255, 0.4);
419
+ border-radius: 0 6px 6px 0;
420
+ transition: all 0.2s ease-in-out;
421
+ cursor: pointer;
422
+ }
423
+ .news-item-link:hover .news-item {
424
+ background: rgba(255, 255, 255, 0.85);
425
+ border-left-color: #434466;
426
+ transform: translateX(3px);
427
+ box-shadow: 0 2px 6px rgba(91, 91, 127, 0.08);
428
+ }
429
+ .dark .news-item {
430
+ background: rgba(30, 41, 59, 0.3);
431
+ }
432
+ .dark .news-item-link:hover .news-item {
433
+ background: rgba(30, 41, 59, 0.65);
434
+ border-left-color: rgba(129, 140, 248, 0.6);
435
+ }
436
  .news-title {
437
  font-size: 12px !important;
438
  font-weight: 600;
439
  color: #1b1c1a;
 
440
  line-height: 1.4;
 
441
  white-space: nowrap;
442
  overflow: hidden;
443
  text-overflow: ellipsis;
444
  }
 
 
 
 
445
  .dark .news-title {
446
  color: #cbd5e1;
447
  }
 
 
 
448
  .news-meta {
449
  font-size: 10px !important;
450
  color: #94a3b8;
 
623
  """)
624
 
625
  with gr.Row():
626
+ # 2. 왼쪽 컬럼: 사이드바 (대시보드 및 하단 메뉴) - 4:6 split을 위해 scale=4 설정
627
+ with gr.Column(scale=4, min_width=350):
628
  stats_data = get_db_stats()
629
  stats_html = build_stats_html(stats_data)
630
  gr.HTML(stats_html)
631
 
632
+ # 3. 오른쪽 컬럼: 메인 챗봇 에어리어 - 4:6 split을 위해 scale=6 설정
633
+ with gr.Column(scale=6, min_width=500):
634
  # 메인 타이틀 (챗봇 영역 상단 중앙)
635
  gr.HTML("""
636
  <div style="text-align: center; padding: 10px 0 20px 0;">