From 557165db118bd6da0b6b551d8aa3d94720ed05dc Mon Sep 17 00:00:00 2001 From: Hyungi Ahn Date: Mon, 6 Apr 2026 15:08:50 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EB=89=B4=EC=8A=A4=20=ED=95=84=ED=84=B0?= =?UTF-8?q?=20=ED=8A=B8=EB=A6=AC=20(=EC=8B=A0=EB=AC=B8=EC=82=AC=20?= =?UTF-8?q?=E2=86=92=20=EB=B6=84=EC=95=BC)=20+=20ai=5Fsummary=20=EC=9A=B0?= =?UTF-8?q?=EC=84=A0=20=ED=91=9C=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 좌측 필터: 신문사 펼침 → 분야별 필터 (News/경향신문/문화) - API: source 파라미터 '신문사' 또는 '신문사/분야' 지원 - 리스트: ai_summary 있으면 우선, 없으면 extracted_text fallback Co-Authored-By: Claude Opus 4.6 (1M context) --- app/api/news.py | 9 ++++-- frontend/src/routes/news/+page.svelte | 40 ++++++++++++++++++++------- 2 files changed, 37 insertions(+), 12 deletions(-) diff --git a/app/api/news.py b/app/api/news.py index 6da312a..3823870 100644 --- a/app/api/news.py +++ b/app/api/news.py @@ -4,7 +4,7 @@ from typing import Annotated from fastapi import APIRouter, Depends, HTTPException from pydantic import BaseModel -from sqlalchemy import select +from sqlalchemy import String, select from sqlalchemy.ext.asyncio import AsyncSession from core.auth import get_current_user @@ -116,7 +116,12 @@ async def list_articles( Document.deleted_at == None, ) if source: - query = query.where(Document.ai_sub_group == source) + if '/' in source: + # 신문사/분야 형태 → ai_tags로 필터 + query = query.where(Document.ai_tags.cast(String).contains(source)) + else: + # 신문사만 → ai_sub_group + query = query.where(Document.ai_sub_group == source) if unread_only: query = query.where(Document.is_read == False) diff --git a/frontend/src/routes/news/+page.svelte b/frontend/src/routes/news/+page.svelte index d2df3c0..434d4d0 100644 --- a/frontend/src/routes/news/+page.svelte +++ b/frontend/src/routes/news/+page.svelte @@ -20,18 +20,23 @@ let selectedArticle = $state(null); let filterSource = $state(''); let showUnreadOnly = $state(false); - let sources = $state([]); + let sourceTree = $state({}); // { 경향신문: ['문화', '사회'], NYT: ['World'] } let currentPage = $state(1); let noteEditing = $state(false); let noteText = $state(''); + let expandedSources = $state({}); onMount(async () => { try { const srcData = await api('/news/sources'); - // 신문사별 유니크 - const names = new Set(); - srcData.forEach(s => names.add(s.name.split(' ')[0])); - sources = [...names]; + const tree = {}; + srcData.forEach(s => { + const paper = s.name.split(' ')[0]; + const cat = s.category || ''; + if (!tree[paper]) tree[paper] = []; + if (cat && !tree[paper].includes(cat)) tree[paper].push(cat); + }); + sourceTree = tree; } catch (e) {} loadArticles(); }); @@ -127,11 +132,26 @@ class="w-full text-left px-2 py-1.5 rounded text-sm mb-1 {filterSource === '' ? 'bg-[var(--accent)]/15 text-[var(--accent)]' : 'text-[var(--text-dim)] hover:bg-[var(--surface)]'}" >📰 전체 - {#each sources as src} - + {#each Object.entries(sourceTree) as [paper, categories]} +
+ + {#if expandedSources[paper] && categories.length > 0} + {#each categories as cat} + + {/each} + {/if} +
{/each}