[fix] landing: 제시가를 바꿔도 에이전트 응수가 그대로이던 문제

공급사 모드에서 슬라이더를 움직여도 첫 응수의 문장과 숫자가 똑같았다.

원인은 두 겹이다.
- 근거 문장이 offer 를 참조하지 않았다. cardsUsed === 0 분기가 marketLow 만 읊었다.
- CONCESSION[0] = 0 이라 부르는 값(ask)도 앵커로 고정이다.
둘이 겹쳐서, 얼마를 제시하든 화면이 전혀 반응하지 않는 것처럼 보였다.

값 고수는 유지한다 — 앵커를 지키는 게 이 에이전트의 성격이고, 제품이 파는 신뢰가
거기서 나온다. 대신 무엇에 대한 응수인지는 매번 달라지게 했다.

  정가 이상   정가 그대로는 검토가 어렵습니다 + 시장 최저가 제시
  최저가 위   제시가가 최저가보다 얼마 높은지 구체 수치로
  최저가 이하 최저가 선까지 내려온 것을 인정 + 기준까지 남은 격차

입력란 숫자에 콤마 누락도 같이 고쳤다. 화면의 다른 금액은 전부 won() 인데 여기만
1018800 으로 나와 같은 값으로 안 읽혔다. 확정 시 콤마를 넣고 파싱할 때 벗긴다.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Haewon Kam 2026-08-06 11:10:50 +09:00
parent ea1249429a
commit 4536a48e40
2 changed files with 23 additions and 9 deletions

View File

@ -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<HTMLDivElement>(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}

View File

@ -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",