From 4536a48e40c1d7d67f35dace5fd6a47cfd9b5ebf Mon Sep 17 00:00:00 2001 From: Haewon Kam Date: Thu, 6 Aug 2026 11:10:50 +0900 Subject: [PATCH] =?UTF-8?q?[fix]=20landing:=20=EC=A0=9C=EC=8B=9C=EA=B0=80?= =?UTF-8?q?=EB=A5=BC=20=EB=B0=94=EA=BF=94=EB=8F=84=20=EC=97=90=EC=9D=B4?= =?UTF-8?q?=EC=A0=84=ED=8A=B8=20=EC=9D=91=EC=88=98=EA=B0=80=20=EA=B7=B8?= =?UTF-8?q?=EB=8C=80=EB=A1=9C=EC=9D=B4=EB=8D=98=20=EB=AC=B8=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 공급사 모드에서 슬라이더를 움직여도 첫 응수의 문장과 숫자가 똑같았다. 원인은 두 겹이다. - 근거 문장이 offer 를 참조하지 않았다. cardsUsed === 0 분기가 marketLow 만 읊었다. - CONCESSION[0] = 0 이라 부르는 값(ask)도 앵커로 고정이다. 둘이 겹쳐서, 얼마를 제시하든 화면이 전혀 반응하지 않는 것처럼 보였다. 값 고수는 유지한다 — 앵커를 지키는 게 이 에이전트의 성격이고, 제품이 파는 신뢰가 거기서 나온다. 대신 무엇에 대한 응수인지는 매번 달라지게 했다. 정가 이상 정가 그대로는 검토가 어렵습니다 + 시장 최저가 제시 최저가 위 제시가가 최저가보다 얼마 높은지 구체 수치로 최저가 이하 최저가 선까지 내려온 것을 인정 + 기준까지 남은 격차 입력란 숫자에 콤마 누락도 같이 고쳤다. 화면의 다른 금액은 전부 won() 인데 여기만 1018800 으로 나와 같은 값으로 안 읽혔다. 확정 시 콤마를 넣고 파싱할 때 벗긴다. Co-Authored-By: Claude Opus 5 --- .../components/sections/negotiation-demo.tsx | 14 ++++++++------ landing/app/lib/negotiation-sim.ts | 18 +++++++++++++++--- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/landing/app/components/sections/negotiation-demo.tsx b/landing/app/components/sections/negotiation-demo.tsx index 84c0ee3..7ad9b02 100644 --- a/landing/app/components/sections/negotiation-demo.tsx +++ b/landing/app/components/sections/negotiation-demo.tsx @@ -41,7 +41,7 @@ export function NegotiationDemo() { const [offer, setOffer] = useState(DEMO_ITEMS[0].listPrice) /* 입력란의 원문. offer 와 따로 두는 이유는 타이핑 도중 상태다 — 숫자 하나로 묶으면 "1" 을 치는 순간 범위 밖이라 튕겨서 사용자와 싸우게 된다. 확정은 blur/Enter 에서 한다. */ - const [offerText, setOfferText] = useState(String(DEMO_ITEMS[0].listPrice)) + const [offerText, setOfferText] = useState(won(DEMO_ITEMS[0].listPrice)) const [finalPrice, setFinalPrice] = useState(DEMO_ITEMS[0].listPrice) const logRef = useRef(null) @@ -69,7 +69,7 @@ export function NegotiationDemo() { setFinalPrice(nextItem.listPrice) const start = nextRole === "seller" ? nextItem.listPrice : nextItem.marketLow setOffer(start) - setOfferText(String(start)) + setOfferText(won(start)) } const pickRole = (r: Role) => { @@ -104,7 +104,7 @@ export function NegotiationDemo() { setCardsUsed((c) => c + 1) // 에이전트가 부른 값에서 다시 시작. 입력란도 같이 갱신해야 슬라이더와 숫자가 어긋나지 않는다. setOffer(res.price) - setOfferText(String(res.price)) + setOfferText(won(res.price)) } }, 620) timers.current.push(id) @@ -147,7 +147,9 @@ export function NegotiationDemo() { if (inputLocked) return const next = Number.isFinite(raw) && raw > 0 ? round10(Math.min(sliderMax, Math.max(sliderMin, raw))) : offer setOffer(next) - setOfferText(String(next)) + /* 확정된 값은 콤마를 넣어 보여준다 — 화면의 다른 금액이 전부 won() 이라 + 여기만 1018800 으로 나오면 같은 값으로 안 읽힌다. 타이핑 중에는 숫자만 남긴다. */ + setOfferText(won(next)) } return ( @@ -280,11 +282,11 @@ export function NegotiationDemo() { aria-label={role === "seller" ? "제시 단가 직접 입력" : "목표 단가 직접 입력"} value={offerText} onChange={(e) => setOfferText(e.target.value.replace(/[^\d]/g, ""))} - onBlur={() => commitOffer(Number(offerText))} + onBlur={() => commitOffer(Number(offerText.replace(/[^\d]/g, "")))} onKeyDown={(e) => { if (e.key === "Enter") { e.preventDefault() - commitOffer(Number(offerText)) + commitOffer(Number(offerText.replace(/[^\d]/g, ""))) } }} disabled={inputLocked} diff --git a/landing/app/lib/negotiation-sim.ts b/landing/app/lib/negotiation-sim.ts index ab9f32d..51bff6b 100644 --- a/landing/app/lib/negotiation-sim.ts +++ b/landing/app/lib/negotiation-sim.ts @@ -114,10 +114,22 @@ export function respondToOffer( const ask = round10(item.anchor + (offer - item.anchor) * CONCESSION[cardsUsed]) const lever = LEVERS[cardsUsed] + /* 근거는 반드시 상대가 제시한 값을 물고 있어야 한다. + 예전엔 첫 카드에서 marketLow 만 읊어서, 얼마를 제시하든 같은 문장이 나왔다. + CONCESSION[0] = 0 이라 부르는 값(ask)까지 앵커로 고정이라, 슬라이더를 움직여도 + 화면이 전혀 반응하지 않는 것처럼 보였다. 값은 고수하되(그게 이 에이전트의 성격이다) + 무엇에 대한 응수인지는 매번 달라져야 한다. */ + const gap = offer - ask const reason = - cardsUsed === 0 - ? `같은 사양 인터넷 최저가가 ${won(item.marketLow)}원입니다.` - : `${won(offer)}원과 저희 기준 사이가 아직 ${won(offer - ask)}원 남았습니다.` + cardsUsed > 0 + ? `${won(offer)}원과 저희 기준 사이가 아직 ${won(gap)}원 남았습니다.` + : offer >= item.listPrice + ? `정가 그대로는 검토가 어렵습니다. 같은 사양 인터넷 최저가가 ${won(item.marketLow)}원입니다.` + : offer > item.marketLow + ? `${won(offer)}원은 같은 사양 인터넷 최저가 ${won(item.marketLow)}원보다 ${won( + offer - item.marketLow, + )}원 높습니다.` + : `${won(offer)}원이면 시장 최저가 선까지 내려오셨습니다. 다만 이번 견적 기준까지 ${won(gap)}원 남았습니다.` return { outcome: "running",