feat(pua): 新增重复检测——LLM语义相似度判断,相似60%效果降级,相似80%强制翻车,额外扣HC惩罚
This commit is contained in:
@@ -21,7 +21,11 @@ interface PuaResult {
|
||||
title: string
|
||||
desc: string
|
||||
effect: EffectType
|
||||
cost?: number // 实际扣除的 HC(前端填写)
|
||||
cost?: number
|
||||
similarity?: number
|
||||
similarTo?: string
|
||||
isDuplicate?: boolean
|
||||
penaltyMultiplier?: number
|
||||
}
|
||||
|
||||
const EFFECT_META: Record<EffectType, { label: string; color: string; icon: string }> = {
|
||||
@@ -53,7 +57,10 @@ function PuaPanel({ gameReady, hc, waveStarted }: { gameReady: boolean; hc: numb
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [result, setResult] = useState<PuaResult | null>(null)
|
||||
const [history, setHistory] = useState<PuaResult[]>([])
|
||||
// 历史原文列表(用于发给服务端做相似度检测)
|
||||
const historyTexts = useRef<string[]>([])
|
||||
const [insufficient, setInsufficient] = useState(false)
|
||||
const [dupWarning, setDupWarning] = useState<string | null>(null)
|
||||
const textareaRef = useRef<HTMLTextAreaElement>(null)
|
||||
const placeholder = useRef(PUA_PLACEHOLDERS[Math.floor(Math.random() * PUA_PLACEHOLDERS.length)]).current
|
||||
|
||||
@@ -63,7 +70,6 @@ function PuaPanel({ gameReady, hc, waveStarted }: { gameReady: boolean; hc: numb
|
||||
const handleSubmit = useCallback(async () => {
|
||||
if (!text.trim() || loading || !gameReady || !waveStarted) return
|
||||
|
||||
// 先从游戏扣除 HC(扣不到则拒绝)
|
||||
const spendHC: ((n: number) => boolean) | undefined =
|
||||
typeof window !== 'undefined' ? (window as any).__gameSpendHC : undefined
|
||||
|
||||
@@ -74,6 +80,7 @@ function PuaPanel({ gameReady, hc, waveStarted }: { gameReady: boolean; hc: numb
|
||||
|
||||
const actualCost = calcPuaCost(currentHC)
|
||||
|
||||
// 扣除基础费用
|
||||
if (!spendHC?.(actualCost)) {
|
||||
setInsufficient(true)
|
||||
setTimeout(() => setInsufficient(false), 2000)
|
||||
@@ -82,18 +89,43 @@ function PuaPanel({ gameReady, hc, waveStarted }: { gameReady: boolean; hc: numb
|
||||
|
||||
setLoading(true)
|
||||
setResult(null)
|
||||
setDupWarning(null)
|
||||
setInsufficient(false)
|
||||
|
||||
try {
|
||||
const res = await fetch('/api/pua-score', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ text: text.trim() }),
|
||||
body: JSON.stringify({
|
||||
text: text.trim(),
|
||||
history: historyTexts.current,
|
||||
}),
|
||||
})
|
||||
const data: PuaResult & { cost?: number } = await res.json()
|
||||
const data: PuaResult = await res.json()
|
||||
data.cost = actualCost
|
||||
|
||||
// 处理重复惩罚:额外扣 HC
|
||||
const penalty = data.penaltyMultiplier ?? 0
|
||||
if (penalty > 0 && data.isDuplicate) {
|
||||
const extraCost = Math.round(actualCost * penalty)
|
||||
const penaltyPaid = spendHC?.(extraCost) ?? false
|
||||
if (penaltyPaid) {
|
||||
data.cost = actualCost + extraCost
|
||||
// 显示重复警告
|
||||
const sim = data.similarity ?? 0
|
||||
const level = sim >= 0.8 ? '严重重复' : '内容相似'
|
||||
setDupWarning(
|
||||
`检测到${level}!额外扣除 ${extraCost} HC` +
|
||||
(data.similarTo ? `\n(与"${data.similarTo}"相似)` : '')
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
setResult(data)
|
||||
// 更新历史原文列表(最近8条)
|
||||
historyTexts.current = [text.trim(), ...historyTexts.current].slice(0, 8)
|
||||
setHistory(prev => [data, ...prev].slice(0, 5))
|
||||
// 通知游戏场景应用 buff
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
;(window as any).__gamePuaBuff?.(data.effect, data.score, data.title)
|
||||
}
|
||||
@@ -183,7 +215,7 @@ function PuaPanel({ gameReady, hc, waveStarted }: { gameReady: boolean; hc: numb
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* HC 不足闪烁提示 */}
|
||||
{/* HC 不足提示 */}
|
||||
{insufficient && (
|
||||
<div style={{
|
||||
backgroundColor: '#7F1D1D',
|
||||
@@ -194,12 +226,28 @@ function PuaPanel({ gameReady, hc, waveStarted }: { gameReady: boolean; hc: numb
|
||||
fontSize: '14px',
|
||||
color: '#FCA5A5',
|
||||
textAlign: 'center',
|
||||
animation: 'none',
|
||||
}}>
|
||||
HC 不足!先去打怪赚钱!
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 重复惩罚警告 */}
|
||||
{dupWarning && (
|
||||
<div style={{
|
||||
backgroundColor: '#431407',
|
||||
border: '1px solid #F97316',
|
||||
borderRadius: '6px',
|
||||
padding: '7px 8px',
|
||||
fontFamily: 'VT323, monospace',
|
||||
fontSize: '13px',
|
||||
color: '#FED7AA',
|
||||
lineHeight: 1.4,
|
||||
whiteSpace: 'pre-line',
|
||||
}}>
|
||||
{dupWarning}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 输入框 */}
|
||||
<textarea
|
||||
ref={textareaRef}
|
||||
@@ -295,6 +343,35 @@ function PuaPanel({ gameReady, hc, waveStarted }: { gameReady: boolean; hc: numb
|
||||
}}>
|
||||
{result.desc}
|
||||
</div>
|
||||
{/* 相似度指示(有重复时显示) */}
|
||||
{(result.similarity ?? 0) >= 0.3 && (
|
||||
<div style={{
|
||||
backgroundColor: '#431407',
|
||||
border: '1px solid #F97316',
|
||||
borderRadius: '4px',
|
||||
padding: '4px 6px',
|
||||
fontFamily: 'VT323, monospace',
|
||||
fontSize: '12px',
|
||||
color: '#FED7AA',
|
||||
}}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '3px' }}>
|
||||
<span>重复检测</span>
|
||||
<span style={{ color: (result.similarity ?? 0) >= 0.8 ? '#EF4444' : '#F97316' }}>
|
||||
{Math.round((result.similarity ?? 0) * 100)}%
|
||||
</span>
|
||||
</div>
|
||||
{/* 相似度进度条 */}
|
||||
<div style={{ height: '3px', backgroundColor: '#1c0a04', borderRadius: '2px' }}>
|
||||
<div style={{
|
||||
height: '100%',
|
||||
width: `${(result.similarity ?? 0) * 100}%`,
|
||||
backgroundColor: (result.similarity ?? 0) >= 0.8 ? '#EF4444' : '#F97316',
|
||||
borderRadius: '2px',
|
||||
transition: 'width 0.3s ease',
|
||||
}} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{/* 效果标签 + 花费 */}
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<div style={{
|
||||
@@ -376,13 +453,15 @@ function PuaPanel({ gameReady, hc, waveStarted }: { gameReady: boolean; hc: numb
|
||||
paddingTop: '8px',
|
||||
}}>
|
||||
<span style={{ color: '#475569' }}>费用 = 当前HC × 15%</span><br />
|
||||
<span style={{ color: '#334155' }}>越富越贵,最低20 最高200</span><br />
|
||||
──────────<br />
|
||||
Enter 快速提交<br />
|
||||
1-2分: 废话翻车<br />
|
||||
4-6分: 攻击+HC<br />
|
||||
相似60%: 效果降级<br />
|
||||
相似80%: 强制翻车<br />
|
||||
重复均额外扣HC<br />
|
||||
──────────<br />
|
||||
9-10分: 全场狂暴<br />
|
||||
7-8分: 攻速暴增<br />
|
||||
9-10分: 全场狂暴
|
||||
4-6分: 攻击+HC<br />
|
||||
1-2分: 废话翻车
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user