- ({
- title: `${event.categoryLabel} · ${event.title}`,
- subtitle: `${event.sourceName} | 직접성 ${formatScore(event.directnessScore)} | 시장확인 ${formatScore(event.marketConfirmationScore)}`,
- meta: event.publishedAtLabel
- }))}
+ eyebrow="Run Pipeline"
+ title="버튼으로 즉시 수집 / 분석 실행"
+ description="터미널 명령 없이 바로 작업을 요청할 수 있습니다. 전체 파이프라인 또는 개별 단계만 실행 가능합니다."
/>
+
-
-
-
+ );
+ } catch (error) {
+ const message = error instanceof Error ? error.message : "알 수 없는 오류";
+
+ return (
+
+ );
+ }
}
diff --git a/apps/web/app/layout.tsx b/apps/web/app/layout.tsx
index 1ce210f..9e24774 100644
--- a/apps/web/app/layout.tsx
+++ b/apps/web/app/layout.tsx
@@ -5,7 +5,7 @@ import { AppShell } from "@/components/layout/app-shell";
export const metadata: Metadata = {
title: "K-테마 리서치 허브",
description:
- "미국 이벤트를 기반으로 다음 영업일 한국 시장 테마와 주도주를 예측하는 개인용 이벤트 드리븐 리서치 플랫폼"
+ "미국 이벤트를 기반으로 다음 영업일 한국 증시 테마와 주도주를 추론하는 개인용 크로스마켓 리서치 플랫폼"
};
export default function RootLayout({
diff --git a/apps/web/components/layout/app-shell.tsx b/apps/web/components/layout/app-shell.tsx
index b7a1bb6..b5c869d 100644
--- a/apps/web/components/layout/app-shell.tsx
+++ b/apps/web/components/layout/app-shell.tsx
@@ -28,15 +28,15 @@ export function AppShell({ children }: { children: React.ReactNode }) {
Personal Research OS
-
+
미국 이벤트에서
내일 한국장 힌트를 찾습니다
- 미국 정책, 기업, 매크로 이벤트를 수집하고 실제 시장 반응으로 검증한 뒤
- 한국 시장의 다음 영업일 테마와 주도주 후보를 압축합니다.
+ 미국 정책, 기업, 매크로 이벤트를 수집하고 실제 시장 반응으로 검증한 뒤 한국 시장의 다음 영업일 테마와
+ 주도주 후보를 빠르게 점검합니다.
- 프리마켓, 장중, 마감 후 점검 흐름을 하나의 작업 체인으로 연결합니다.
+ 프리마켓, 장중, 마감 후 흐름을 하나의 작업 체인으로 연결합니다.
diff --git a/apps/web/components/shared/job-controls.tsx b/apps/web/components/shared/job-controls.tsx
index 04ee169..3d2f56a 100644
--- a/apps/web/components/shared/job-controls.tsx
+++ b/apps/web/components/shared/job-controls.tsx
@@ -33,30 +33,30 @@ const jobConfig: Record<
refresh: {
path: "/jobs/refresh",
label: "수집 후 분석 실행",
- description: "원문 수집부터 이벤트/미국 반응/한국장 번역까지 한 번에 실행합니다.",
- tone: "bg-[#1ed760] text-[#121212] hover:bg-[#3be477]",
+ description: "원문 수집부터 이벤트, 미국 반응, 한국장 번역까지 한 번에 실행합니다.",
+ tone: "bg-[#1ed760] text-[#121212] hover:bg-[#3be477]"
},
ingest: {
path: "/jobs/ingest",
label: "수집만 실행",
- description: "공식 소스와 피드를 다시 읽어 원문 문서를 적재합니다.",
- tone: "bg-[color:var(--bg-deep)] text-[color:var(--text)] hover:bg-[#2a2a2a]",
+ description: "공식 소스와 뉴스 피드를 다시 읽어 원문 문서를 적재합니다.",
+ tone: "bg-[color:var(--bg-deep)] text-[color:var(--text)] hover:bg-[#2a2a2a]"
},
analyze: {
path: "/jobs/analyze",
label: "분석만 실행",
- description: "이미 수집된 문서를 이벤트/시장 반응/한국 테마로 변환합니다.",
- tone: "bg-[color:var(--surface-white)] text-[color:var(--text)] hover:bg-[#313131]",
- },
+ description: "이미 수집된 문서를 이벤트, 시장 반응, 한국 테마로 변환합니다.",
+ tone: "bg-[color:var(--surface-white)] text-[color:var(--text)] hover:bg-[#313131]"
+ }
};
async function triggerJob(kind: JobKind) {
const response = await fetch(`${API_BASE_URL}${jobConfig[kind].path}`, {
method: "POST",
headers: {
- "Content-Type": "application/json",
+ "Content-Type": "application/json"
},
- body: JSON.stringify({}),
+ body: JSON.stringify({})
});
if (!response.ok) {
@@ -68,7 +68,7 @@ async function triggerJob(kind: JobKind) {
async function fetchRecentJobs() {
const response = await fetch(`${API_BASE_URL}/jobs/recent`, {
- cache: "no-store",
+ cache: "no-store"
});
if (!response.ok) {
@@ -110,7 +110,7 @@ export function JobControls({ compact = false }: { compact?: boolean }) {
const router = useRouter();
const [isPending, startTransition] = useTransition();
const [message, setMessage] = useState(
- "워커가 실행 중이면 버튼으로 바로 수집/분석 파이프라인을 시작할 수 있습니다.",
+ "워커가 실행 중이면 버튼으로 바로 수집/분석 파이프라인을 시작할 수 있습니다."
);
const [jobs, setJobs] = useState([]);
const [isPolling, setIsPolling] = useState(true);
@@ -125,9 +125,7 @@ export function JobControls({ compact = false }: { compact?: boolean }) {
router.refresh();
}
} catch (error) {
- setMessage(
- error instanceof Error ? error.message : "작업 상태를 불러오지 못했습니다.",
- );
+ setMessage(error instanceof Error ? error.message : "작업 상태를 불러오지 못했습니다.");
}
};
@@ -150,7 +148,7 @@ export function JobControls({ compact = false }: { compact?: boolean }) {
try {
const result = await triggerJob(kind);
setMessage(
- `${jobConfig[kind].label} 요청을 큐에 넣었습니다. jobId: ${result.jobId ?? "-"} / 진행 상태는 아래 패널에서 자동 갱신됩니다.`,
+ `${jobConfig[kind].label} 요청이 접수됐습니다. jobId: ${result.jobId ?? "-"} / 아래 패널에서 자동 갱신됩니다.`
);
setIsPolling(true);
await loadJobs();
@@ -161,7 +159,7 @@ export function JobControls({ compact = false }: { compact?: boolean }) {
setMessage(
error instanceof Error
? `${error.message}. API, Redis, 워커가 실행 중인지 확인해 주세요.`
- : "작업 실행에 실패했습니다.",
+ : "작업 실행에 실패했습니다."
);
}
});
@@ -211,7 +209,7 @@ export function JobControls({ compact = false }: { compact?: boolean }) {