diff --git a/src/components/Intro/ABTestResults.tsx b/src/components/Intro/ABTestResults.tsx index f540b18..8c256df 100644 --- a/src/components/Intro/ABTestResults.tsx +++ b/src/components/Intro/ABTestResults.tsx @@ -16,19 +16,10 @@ export default function ABTestResults() { whileInView={{ opacity: 1, y: 0 }} viewport={{ once: true }} transition={{ duration: 0.8, delay: 0.2 }} - className="text-3xl font-bold text-gray-900 mb-4" + className="text-3xl font-bold text-gray-900 mb-8" > - ๐Ÿ“Š LLM ๊ธฐ๋ฐ˜ ๊ธˆ์œต ๋ถ„์„ ๋ชจ๋ธ A/B ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ + LLM ๊ธฐ๋ฐ˜ ๊ธˆ์œต ๋ถ„์„ ๋ชจ๋ธ A/B ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ - - CoT/RAG ๋ฐฉ์‹์˜ ์šฐ์ˆ˜์„ฑ์„ ๊ณผํ•™์ ์œผ๋กœ ๊ฒ€์ฆํ–ˆ์Šต๋‹ˆ๋‹ค - {/* ๊ธฐ์ˆ  ์Šคํƒ ๋น„๊ต */} -
+

Baseline

-
+
GPT-4o
-
๊ธฐ๋ณธ ๋ชจ๋ธ
+
๊ธฐ๋ณธ ๋ชจ๋ธ
-
+

Ours

-
- GPT-4o -
-
- + BarbellAI ๊ธฐ์ˆ  -
-
+
Chain of Thought + RAG
+
+ ๊ธฐ๋ฐ˜ ๋ชจ๋ธ: GPT-4o +
@@ -81,24 +69,24 @@ export default function ABTestResults() { whileInView={{ opacity: 1, y: 0 }} viewport={{ once: true }} transition={{ duration: 0.8, delay: 0.1 }} - className="bg-white rounded-xl p-8 shadow-lg mb-8" + className="bg-white rounded-xl p-8 shadow-lg mb-4" >

๐ŸŽฏ ์ข…ํ•ฉ ๋ถ„์„ ํ‰๊ฐ€ (๊นŠ์ด+๊ทผ๊ฑฐ+ํ†ต์ฐฐ๋ ฅ ํ‰๊ท )

-
-
- 6.92 -
-
CoT/RAG ํ‰๊ท  ์ ์ˆ˜
-
5.25
Baseline ํ‰๊ท  ์ ์ˆ˜
+
+
+ 6.92 +
+
CoT/RAG ํ‰๊ท  ์ ์ˆ˜
+
@@ -206,26 +194,6 @@ export default function ABTestResults() {
- - {/* ๊ฒฐ๋ก  */} - -
-

๐Ÿ† ๊ฒฐ๋ก 

-

- CoT/RAG ๋ฐฉ์‹์ด ๋ชจ๋“  ํ‰๊ฐ€ ํ•ญ๋ชฉ์—์„œ ํ†ต๊ณ„์ ์œผ๋กœ ์œ ์˜๋ฏธํ•˜๊ฒŒ ์šฐ์ˆ˜ํ•œ - ์„ฑ๋Šฅ์„ ๋ณด์—ฌ์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค. -
- p < 0.001 ์ˆ˜์ค€์—์„œ - ํ†ต๊ณ„์  ์œ ์˜์„ฑ์„ ํ™•์ธํ–ˆ์Šต๋‹ˆ๋‹ค. -

-
-
diff --git a/src/components/chat/ChatWindow.tsx b/src/components/chat/ChatWindow.tsx index 0ada267..8d0007f 100644 --- a/src/components/chat/ChatWindow.tsx +++ b/src/components/chat/ChatWindow.tsx @@ -174,7 +174,8 @@ export default function ChatWindow({ isOpen, onClose }: ChatWindowProps) { }; const handleSubmit = useCallback(() => { - if (!message.trim() || isCreating) return; + if (((message ?? "").trim().length === 0 && !newsInfo) || isCreating) + return; // ๋‰ด์Šค ์ •๋ณด๊ฐ€ ์žˆ์œผ๋ฉด ๋ฉ”์‹œ์ง€์— ํฌํ•จ let finalMessage = message; @@ -212,10 +213,22 @@ ${newsInfo.impact ? `์˜ํ–ฅ๋„: ${newsInfo.impact === "positive" ? "๊ธ์ •" : ne return; } - // ํ˜„์žฌ ์ฑ„ํŒ…๋ฐฉ์ด ์—†์œผ๋ฉด ์ƒˆ๋กœ ์ƒ์„ฑ + // ํ˜„์žฌ ์ฑ„ํŒ…๋ฐฉ์ด ์—†์œผ๋ฉด ์ƒˆ๋กœ ์ƒ์„ฑ ํ›„ ์ฆ‰์‹œ ์ „์†ก const title = generateKoreanTimestamp(); - createChatWithCallback(title); - setPendingMessage(finalMessage); // ์ƒˆ๋กœ ์ƒ์„ฑ๋œ ์ฑ„ํŒ…๋ฐฉ์œผ๋กœ ๋ฉ”์‹œ์ง€ ์ „์†ก์„ ์œ„ํ•ด pendingMessage ์„ค์ • + createChatWithCallback( + title, + (newId) => { + setCurrentChatId(newId); + sendMessage({ chatId: newId, message: finalMessage }); + setMessage(""); + setNewsInfo(null); + setShowHistory(false); + }, + () => { + // ์‹คํŒจ ์‹œ ๊ธฐ์กด fallback ๋กœ์ง ์œ ์ง€ + setPendingMessage(finalMessage); + } + ); }, [ message, isCreating, diff --git a/src/components/stockDetail/TechnicalAnalysis.tsx b/src/components/stockDetail/TechnicalAnalysis.tsx index 2640924..396528f 100644 --- a/src/components/stockDetail/TechnicalAnalysis.tsx +++ b/src/components/stockDetail/TechnicalAnalysis.tsx @@ -83,13 +83,27 @@ export default function TechnicalAnalysis({ const volaData = analysis?.volatility_analysis_data; const volData = analysis?.volume_analysis_data; - const isAnalysisLoading = !analysis; - // ๊ธฐ์ˆ ์  ์ง€ํ‘œ ๊ฐ’ (์‹ค์ œ ๋ฐ์ดํ„ฐ ์‚ฌ์šฉ) const rsi = mainData?.rsi?.value ?? 28.4; + const macdValue: number | undefined = + typeof mainData?.macd?.value === "number" ? mainData.macd.value : undefined; + const macdDerivedStatus = + macdValue === undefined + ? undefined + : macdValue > 0 + ? "์ƒ์Šน" + : macdValue < 0 + ? "ํ•˜๋ฝ" + : "์ค‘๋ฆฝ"; + const macdStatusText = mainData?.macd?.status ?? macdDerivedStatus; const dailyRange = volaData?.volatility?.value?.volatility_percent ?? 2.8; const avgVolatility = - volaData?.volatility?.value?.avg_volatility_percent ?? 2.8; + volaData?.volatility?.value?.avg50_volatility_percent ?? + volaData?.volatility?.value?.avg_volatility_percent ?? + 2.8; + const volatilityAnalysisFromApi = volaData?.volatility?.analysis as + | string + | undefined; const currentVolume = toNum(volData?.volume?.value?.volume) || 15234567; const avgVolume = toNum(volData?.volume?.value?.avg_volume_20) || 12456789; const volumeRatio = ( @@ -98,8 +112,6 @@ export default function TechnicalAnalysis({ ).toFixed(1); const mfiValue = typeof volData?.mfi?.value === "number" ? volData.mfi.value : undefined; - const obvValue = toNum(volData?.obv?.value?.obv); - const obvMa20Value = toNum(volData?.obv?.value?.obv_ma20); const tabs = [ { id: "summary", label: "์ฃผ์š” ์ง€ํ‘œ", icon: Target }, @@ -207,16 +219,16 @@ export default function TechnicalAnalysis({ >
- ์ข…ํ•ฉ ์‹ ํ˜ธ + MACD
{_isTechnicalLoading ? ( ) : ( - (mainData?.macd?.status ?? "์ค‘๋ฆฝ") + (macdStatusText ?? "์ค‘๋ฆฝ") )}
{hoveredKey === "summary_total" && ( @@ -274,7 +286,10 @@ export default function TechnicalAnalysis({
{hoveredKey === "vol_daily" && (
-
์ผ์ผ ๋ณ€๋™์„ฑ ๊ด€๋ จ ์ฐธ๊ณ  ๊ฐ’
+
+ {volatilityAnalysisFromApi ?? + "๋ถ„์„ ์ •๋ณด๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค."} +
)} @@ -302,7 +317,10 @@ export default function TechnicalAnalysis({
{hoveredKey === "vol_avg" && (
-
RVI: - | ATR: -
+
+ {volatilityAnalysisFromApi ?? + "๋ถ„์„ ์ •๋ณด๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค."} +
)} @@ -477,10 +495,6 @@ export default function TechnicalAnalysis({ {hoveredKey === "vol_obv_card" && (
-
- {obvValue ? obvValue.toLocaleString() : "-"} / MA20{" "} - {obvMa20Value ? obvMa20Value.toLocaleString() : "-"} -
{volData?.obv?.analysis ?? "OBV ๋ถ„์„ ์ •๋ณด"}
diff --git a/src/components/stockDetail/chart/PriceVolumeChart.tsx b/src/components/stockDetail/chart/PriceVolumeChart.tsx index 1e3c1ca..a0a15f1 100644 --- a/src/components/stockDetail/chart/PriceVolumeChart.tsx +++ b/src/components/stockDetail/chart/PriceVolumeChart.tsx @@ -72,7 +72,8 @@ const PriceVolumeChart = memo(function PriceVolumeChart({ const { createChart, CandlestickSeries, HistogramSeries, LineSeries } = tv as any; - chart = createChart(tvContainerRef.current, { + const container = tvContainerRef.current; + chart = createChart(container, { layout: { textColor: "#334155", background: { type: "solid", color: "#ffffff" }, @@ -170,6 +171,34 @@ const PriceVolumeChart = memo(function PriceVolumeChart({ } chart.timeScale().fitContent(); + + // ๋ฐ˜์‘ํ˜•: ResizeObserver๋กœ ์ปจํ…Œ์ด๋„ˆ ํฌ๊ธฐ ๋ณ€๊ฒฝ ์‹œ ์ฐจํŠธ ๋ฆฌ์‚ฌ์ด์ฆˆ + let resizeObserver: ResizeObserver | null = null; + if (typeof ResizeObserver !== "undefined") { + resizeObserver = new ResizeObserver(() => { + try { + const { clientWidth, clientHeight } = container; + if (clientWidth && clientHeight) { + chart.applyOptions({ + width: clientWidth, + height: clientHeight, + }); + } + } catch {} + }); + resizeObserver.observe(container); + } else { + // ํด๋ฐฑ: ์œˆ๋„์šฐ ๋ฆฌ์‚ฌ์ด์ฆˆ + const onResize = () => { + try { + const { clientWidth, clientHeight } = container; + chart.applyOptions({ width: clientWidth, height: clientHeight }); + } catch {} + }; + window.addEventListener("resize", onResize); + // cleanup์—์„œ ์ œ๊ฑฐ + (chart as any).__onResize = onResize; + } setTvReady(true); setTvFailed(false); clearTimeout(timeoutId); // ์„ฑ๊ณต ์‹œ ํƒ€์ž„์•„์›ƒ ํด๋ฆฌ์–ด @@ -188,6 +217,13 @@ const PriceVolumeChart = memo(function PriceVolumeChart({ chart.remove?.(); } catch {} } + // ๋ฆฌ์‚ฌ์ด์ฆˆ ์˜ต์ €๋ฒ„/๋ฆฌ์Šค๋„ˆ ์ •๋ฆฌ + try { + const onResize = (chart as any)?.__onResize; + if (onResize) { + window.removeEventListener("resize", onResize); + } + } catch {} }; }, [data, showMA, tvReady, tvFailed]); @@ -301,7 +337,7 @@ const PriceVolumeChart = memo(function PriceVolumeChart({ duration: 500, // ๋ถ€๋“œ๋Ÿฌ์šด ์ „ํ™˜์„ ์œ„ํ•œ ์• ๋‹ˆ๋ฉ”์ด์…˜ easing: "easeInOutCubic", }, - reflow: false, // ๋ฆฌํ”Œ๋กœ์šฐ ๋น„ํ™œ์„ฑํ™” + reflow: true, // ์ปจํ…Œ์ด๋„ˆ ํฌ๊ธฐ ๋ณ€ํ™”์— ๋ฐ˜์‘ }, accessibility: { enabled: false }, title: { text: "" }, @@ -428,8 +464,16 @@ const PriceVolumeChart = memo(function PriceVolumeChart({ return (
); } @@ -437,7 +481,17 @@ const PriceVolumeChart = memo(function PriceVolumeChart({ // TradingView ์‹คํŒจ ์‹œ Highcharts ์‚ฌ์šฉ if (tvFailed && ready) { return ( -
+
0 && !tvReady && !tvFailed) { return ( -
+
+ {/* ์ฃผ๊ฐ€์˜ˆ์ธก ๊ณผ์ • & ์„ฑ๋Šฅ ์„น์…˜ */} +
+
+ + ์ฃผ๊ฐ€์˜ˆ์ธก ๊ณผ์ •๊ณผ ์„ฑ๋Šฅ + + + {/* ํƒ€์ž„๋ผ์ธ */} + + {[ + { + icon: , + title: "์‹ค์‹œ๊ฐ„ ๋‰ด์Šค ์ˆ˜์ง‘", + desc: "์ฆ๊ถŒ์‚ฌ HTS์—์„œ ์ œ๊ณต๋˜๋Š” ๋‰ด์Šค ์‚ฌ์ดํŠธ๋ฅผ ๋Œ€์ƒ์œผ๋กœ ์‹ค์‹œ๊ฐ„ ์ˆ˜์ง‘", + }, + { + icon: , + title: "ํ‚ค์›Œ๋“œ ๊ธฐ๋ฐ˜ ํšŒ์‚ฌ ๋งค์นญ", + desc: "ํ‚ค์›Œ๋“œ ๊ธฐ๋ฐ˜ ์ ‘๊ทผ์œผ๋กœ ๋‰ด์Šค-๊ธฐ์—… ์ž๋™ ๋งคํ•‘", + }, + { + icon: , + title: "BALLFiN๋งŒ์˜ CoTยทRAG ๊ธฐ์ˆ ๋กœ Impact Score ์‚ฐ์ถœ", + desc: "๋‰ด์Šค ์ค‘์š”๋„๋ฅผ ์ •๊ตํ•˜๊ฒŒ ํ‰๊ฐ€", + }, + { + icon: , + title: "LLM ๊ธฐ๋ฐ˜ ์ฃผ๊ฐ€์˜ˆ์ธก", + desc: "Impact Score, ๋‰ด์Šค/์ฐจํŠธ ๋ถ„์„, few-shot learning์„ ๊ฒฐํ•ฉ", + }, + ].map((step, idx) => ( + + + {step.icon} + +
+
+ + {String(idx + 1).padStart(2, "0")} + +

+ {step.title} +

+
+

{step.desc}

+
+
+ ))} +
+ + {/* ์„ฑ๋Šฅ ์นด๋“œ */} +
+ +

๊ฒฐ๊ณผ ์ˆ˜์ต๋ฅ 

+

254%

+

์ „๋žต ๋ฐฑํ…Œ์ŠคํŠธ ๊ธฐ์ค€

+
+ +

๊ฑฐ๋ž˜ ์Šน๋ฅ 

+

63%

+

์‹คํ—˜ ํ™˜๊ฒฝ ๋‚ด ๊ธฐ์ค€

+
+
+
+
+ {/* ์‹œ์ž‘ํ•˜๊ธฐ ์„น์…˜ */}
diff --git a/src/pages/stock/StockDetailPage.tsx b/src/pages/stock/StockDetailPage.tsx index 7fd34bb..8730c08 100644 --- a/src/pages/stock/StockDetailPage.tsx +++ b/src/pages/stock/StockDetailPage.tsx @@ -17,6 +17,7 @@ import { getNewsByCompany } from "@/api/news"; import StockChartPrice from "@/components/stockDetail/chart"; import RelatedCompanies from "@/components/stockDetail/RelatedCompanies"; import Pagination from "@/components/news/Pagination"; +import BotButton from "@/components/chat/BotButton"; interface StockDetail { id: number; @@ -107,7 +108,6 @@ export default function StockDetailPage() { }; // ์„น์…˜๋ณ„ ๋กœ๋”ฉ ์ƒํƒœ - const [isHeaderLoading, setIsHeaderLoading] = useState(true); const [isChartLoading, setIsChartLoading] = useState(true); const [isTechnicalLoading, setIsTechnicalLoading] = useState(true); const isNewsLoading = news.length === 0; @@ -193,7 +193,7 @@ export default function StockDetailPage() { if (!cancelled) { setStock(mapped); - setIsHeaderLoading(false); + console.log("1๋‹จ๊ณ„: Stock Header ๋กœ๋”ฉ ์™„๋ฃŒ"); } @@ -329,7 +329,6 @@ export default function StockDetailPage() { }, } as StockDetail; setStock(fallback); - setIsHeaderLoading(false); const mockHistoricalData: HistoricalData[] = Array.from( { length: 30 }, @@ -416,8 +415,8 @@ export default function StockDetailPage() { {/* ๋ฉ”์ธ ์ฝ˜ํ…์ธ  */}
{/* ์ฐจํŠธ์™€ ๊ธฐ์ˆ ์  ๋ถ„์„ ์˜์—ญ */} -
-
+
+
{/* ๊ธฐ์ˆ ์  ๋ถ„์„ ์‚ฌ์ด๋“œ๋ฐ” */} -
+
+
); } diff --git a/vite.config.ts b/vite.config.ts index efb5713..31ff676 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -32,6 +32,7 @@ export default defineConfig(({ mode }) => { hmr: { overlay: false, }, + allowedHosts: ["phrstudio.iptime.org"], proxy: { "^/api/.*": { target: process.env.VITE_API_BASE_URL,