Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 18 additions & 50 deletions src/components/Intro/ABTestResults.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,53 +16,41 @@ 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 ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.8, delay: 0.4 }}
className="text-lg text-gray-600 mb-8"
>
CoT/RAG ๋ฐฉ์‹์˜ ์šฐ์ˆ˜์„ฑ์„ ๊ณผํ•™์ ์œผ๋กœ ๊ฒ€์ฆํ–ˆ์Šต๋‹ˆ๋‹ค
</motion.p>

{/* ๊ธฐ์ˆ  ์Šคํƒ ๋น„๊ต */}
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.8, delay: 0.6 }}
className="grid grid-cols-1 md:grid-cols-2 gap-6 max-w-4xl mx-auto mb-12"
className="grid grid-cols-1 md:grid-cols-2 gap-6 max-w-4xl mx-auto mb-8"
>
<div className="bg-white rounded-xl p-6 shadow-lg">
<div className="bg-white rounded-xl p-6 shadow-lg opacity-90">
<h4 className="text-lg font-bold text-gray-500 mb-4 text-center">
Baseline
</h4>
<div className="text-center">
<div className="text-2xl font-bold text-gray-500 mb-2">
<div className="text-xl font-bold text-gray-500 mb-1">
GPT-4o
</div>
<div className="text-sm text-gray-600">๊ธฐ๋ณธ ๋ชจ๋ธ</div>
<div className="text-xs text-gray-500">๊ธฐ๋ณธ ๋ชจ๋ธ</div>
</div>
</div>
<div className="bg-white rounded-xl p-6 shadow-lg border-2 border-[#0A5C2B]">
<div className="bg-gradient-to-br from-white to-emerald-50 rounded-xl p-6 shadow-2xl border-2 border-[#0A5C2B] ring-4 ring-[#0A5C2B]/10">
<h4 className="text-lg font-bold text-[#0A5C2B] mb-4 text-center">
Ours
</h4>
<div className="text-center">
<div className="text-2xl font-bold text-[#0A5C2B] mb-2">
GPT-4o
</div>
<div className="text-sm text-gray-600 mb-2">
+ BarbellAI ๊ธฐ์ˆ 
</div>
<div className="text-xs text-[#0A5C2B] font-medium">
<div className="text-2xl font-extrabold text-[#0A5C2B] tracking-tight">
Chain of Thought + RAG
</div>
<div className="text-xs text-gray-500 mt-2">
๊ธฐ๋ฐ˜ ๋ชจ๋ธ: GPT-4o
</div>
</div>
</div>
</motion.div>
Expand All @@ -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"
>
<h3 className="text-xl font-bold text-[#0A5C2B] mb-6 text-center">
๐ŸŽฏ ์ข…ํ•ฉ ๋ถ„์„ ํ‰๊ฐ€ (๊นŠ์ด+๊ทผ๊ฑฐ+ํ†ต์ฐฐ๋ ฅ ํ‰๊ท )
</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="text-center">
<div className="text-3xl font-bold text-[#0A5C2B] mb-2">
6.92
</div>
<div className="text-sm text-gray-600">CoT/RAG ํ‰๊ท  ์ ์ˆ˜</div>
</div>
<div className="text-center">
<div className="text-3xl font-bold text-gray-500 mb-2">
5.25
</div>
<div className="text-sm text-gray-600">Baseline ํ‰๊ท  ์ ์ˆ˜</div>
</div>
<div className="text-center">
<div className="text-3xl font-bold text-[#0A5C2B] mb-2">
6.92
</div>
<div className="text-sm text-gray-600">CoT/RAG ํ‰๊ท  ์ ์ˆ˜</div>
</div>
</div>
<div className="mt-6 text-center">
<div className="text-2xl font-bold text-green-600 mb-2">
Expand Down Expand Up @@ -206,26 +194,6 @@ export default function ABTestResults() {
</div>
</motion.div>
</div>

{/* ๊ฒฐ๋ก  */}
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.8, delay: 0.5 }}
className="mt-8 text-center"
>
<div className="bg-[#0A5C2B] text-white rounded-xl p-6">
<h4 className="text-xl font-bold mb-2">๐Ÿ† ๊ฒฐ๋ก </h4>
<p className="text-white/90">
CoT/RAG ๋ฐฉ์‹์ด ๋ชจ๋“  ํ‰๊ฐ€ ํ•ญ๋ชฉ์—์„œ ํ†ต๊ณ„์ ์œผ๋กœ ์œ ์˜๋ฏธํ•˜๊ฒŒ ์šฐ์ˆ˜ํ•œ
์„ฑ๋Šฅ์„ ๋ณด์—ฌ์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค.
<br />
<span className="font-semibold">p &lt; 0.001</span> ์ˆ˜์ค€์—์„œ
ํ†ต๊ณ„์  ์œ ์˜์„ฑ์„ ํ™•์ธํ–ˆ์Šต๋‹ˆ๋‹ค.
</p>
</div>
</motion.div>
</motion.div>
</div>
</div>
Expand Down
21 changes: 17 additions & 4 deletions src/components/chat/ChatWindow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down
42 changes: 28 additions & 14 deletions src/components/stockDetail/TechnicalAnalysis.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 = (
Expand All @@ -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 },
Expand Down Expand Up @@ -207,16 +219,16 @@ export default function TechnicalAnalysis({
>
<div className="flex items-center mb-3">
<span className="text-sm font-medium text-gray-700">
์ข…ํ•ฉ ์‹ ํ˜ธ
MACD
</span>
</div>
<div
className={`text-lg font-bold ${statusColor(mainData?.macd?.status)}`}
className={`text-lg font-bold ${statusColor(macdStatusText)}`}
>
{_isTechnicalLoading ? (
<SkeletonText widthClass="w-20" />
) : (
(mainData?.macd?.status ?? "์ค‘๋ฆฝ")
(macdStatusText ?? "์ค‘๋ฆฝ")
)}
</div>
{hoveredKey === "summary_total" && (
Expand Down Expand Up @@ -274,7 +286,10 @@ export default function TechnicalAnalysis({
</div>
{hoveredKey === "vol_daily" && (
<div className="absolute z-50 pointer-events-none top-full left-0 right-0 mt-2 p-3 bg-gray-800 text-white text-xs rounded-lg shadow-lg">
<div>์ผ์ผ ๋ณ€๋™์„ฑ ๊ด€๋ จ ์ฐธ๊ณ  ๊ฐ’</div>
<div>
{volatilityAnalysisFromApi ??
"๋ถ„์„ ์ •๋ณด๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค."}
</div>
<div className="absolute bottom-full left-4 w-0 h-0 border-l-4 border-r-4 border-b-4 border-transparent border-b-gray-800"></div>
</div>
)}
Expand Down Expand Up @@ -302,7 +317,10 @@ export default function TechnicalAnalysis({
</div>
{hoveredKey === "vol_avg" && (
<div className="absolute z-50 pointer-events-none top-full left-0 right-0 mt-2 p-3 bg-gray-800 text-white text-xs rounded-lg shadow-lg">
<div>RVI: - | ATR: -</div>
<div>
{volatilityAnalysisFromApi ??
"๋ถ„์„ ์ •๋ณด๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค."}
</div>
<div className="absolute bottom-full left-4 w-0 h-0 border-l-4 border-r-4 border-b-4 border-transparent border-b-gray-800"></div>
</div>
)}
Expand Down Expand Up @@ -477,10 +495,6 @@ export default function TechnicalAnalysis({
</div>
{hoveredKey === "vol_obv_card" && (
<div className="absolute z-50 pointer-events-none top-full left-0 right-0 mt-2 p-3 bg-gray-800 text-white text-xs rounded-lg shadow-lg">
<div>
{obvValue ? obvValue.toLocaleString() : "-"} / MA20{" "}
{obvMa20Value ? obvMa20Value.toLocaleString() : "-"}
</div>
<div>{volData?.obv?.analysis ?? "OBV ๋ถ„์„ ์ •๋ณด"}</div>
<div className="absolute bottom-full left-4 w-0 h-0 border-l-4 border-r-4 border-b-4 border-transparent border-b-gray-800"></div>
</div>
Expand Down
76 changes: 70 additions & 6 deletions src/components/stockDetail/chart/PriceVolumeChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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" },
Expand Down Expand Up @@ -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); // ์„ฑ๊ณต ์‹œ ํƒ€์ž„์•„์›ƒ ํด๋ฆฌ์–ด
Expand All @@ -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]);

Expand Down Expand Up @@ -301,7 +337,7 @@ const PriceVolumeChart = memo(function PriceVolumeChart({
duration: 500, // ๋ถ€๋“œ๋Ÿฌ์šด ์ „ํ™˜์„ ์œ„ํ•œ ์• ๋‹ˆ๋ฉ”์ด์…˜
easing: "easeInOutCubic",
},
reflow: false, // ๋ฆฌํ”Œ๋กœ์šฐ ๋น„ํ™œ์„ฑํ™”
reflow: true, // ์ปจํ…Œ์ด๋„ˆ ํฌ๊ธฐ ๋ณ€ํ™”์— ๋ฐ˜์‘
},
accessibility: { enabled: false },
title: { text: "" },
Expand Down Expand Up @@ -428,16 +464,34 @@ const PriceVolumeChart = memo(function PriceVolumeChart({
return (
<div
ref={tvContainerRef}
className="h-[500px] w-full transition-all duration-300 ease-in-out"
style={{ opacity: 1 }}
className="w-full transition-all duration-300 ease-in-out"
style={{
opacity: 1,
height:
typeof window !== "undefined" && window.innerWidth < 640
? 360
: typeof window !== "undefined" && window.innerWidth < 1024
? 420
: 500,
}}
/>
);
}

// TradingView ์‹คํŒจ ์‹œ Highcharts ์‚ฌ์šฉ
if (tvFailed && ready) {
return (
<div className="transition-all duration-300 ease-in-out">
<div
className="transition-all duration-300 ease-in-out w-full overflow-hidden"
style={{
height:
typeof window !== "undefined" && window.innerWidth < 640
? 360
: typeof window !== "undefined" && window.innerWidth < 1024
? 420
: 500,
}}
>
<HighchartsReact
key={`${timeRange}-${ohlcData.length}`}
highcharts={Highcharts}
Expand All @@ -451,7 +505,17 @@ const PriceVolumeChart = memo(function PriceVolumeChart({
// TradingView ๋กœ๋”ฉ ์ค‘์ด์ง€๋งŒ ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ์œผ๋ฉด Highcharts๋กœ ํด๋ฐฑ
if (data.length > 0 && !tvReady && !tvFailed) {
return (
<div className="transition-all duration-300 ease-in-out">
<div
className="transition-all duration-300 ease-in-out w-full overflow-hidden"
style={{
height:
typeof window !== "undefined" && window.innerWidth < 640
? 360
: typeof window !== "undefined" && window.innerWidth < 1024
? 420
: 500,
}}
>
<HighchartsReact
key={`${timeRange}-${ohlcData.length}`}
highcharts={Highcharts}
Expand Down
Loading