diff --git a/app/api/pua-score/route.ts b/app/api/pua-score/route.ts index 957e066..ac21ef4 100644 --- a/app/api/pua-score/route.ts +++ b/app/api/pua-score/route.ts @@ -2,70 +2,118 @@ import { NextResponse } from 'next/server' import { llmClient } from '@/app/api/llm-client' const SYSTEM_PROMPT = `你是一个专门分析互联网大厂老板PUA话术的AI裁判。 -用户输入一段老板/领导对员工说的"打鸡血"或"PUA"的话,你需要分析这段话并给出评分和游戏效果。 -评分标准(1-10分): -1-3分:废话/没营养("大家加油"之类)→ 轻微效果 -4-6分:标准大厂黑话("对齐""闭环""狼性")→ 中等效果 -7-8分:强力PUA("996是福报""不拼搏对不起父母")→ 强力效果 -9-10分:终极毒鸡汤(极限施压/情感绑架)→ 全场爆发 +你的任务分两步: +1. 判断当前输入是否与历史记录重复或高度相似(意思相近算相似,不只是字面相同) +2. 对当前输入进行鸡血值评分 + +相似度判断标准(similarity字段,0.0-1.0): +- 0.0-0.3:全新内容,没有相似之处 +- 0.3-0.6:有一定相关,但表达不同 +- 0.6-0.8:明显相似,换汤不换药 +- 0.8-1.0:基本重复,几乎一样的意思 + +评分标准(score字段,1-10分): +- 1-3分:废话/没营养 → backfire效果 +- 4-6分:标准大厂黑话 → 中等效果 +- 7-8分:强力PUA → 攻速暴增 +- 9-10分:终极毒鸡汤 → 全场狂暴 只返回如下JSON,不要其他内容: { + "similarity": 数字(0.0-1.0), + "similarTo": "最相似的历史内容摘要,没有则为空字符串", "score": 数字(1-10), "title": "效果名称(2-6个汉字,有创意)", "desc": "对话语的一句话点评(15字以内,带点讽刺)", "effect": "attack_boost" | "speed_boost" | "money_rain" | "rage_mode" | "backfire" -} +}` -effect说明: -- attack_boost: 攻击力提升(score 4-6) -- speed_boost: 攻击速度提升(score 7-8) -- money_rain: HC暴增(score 5-7,话语中强调利益) -- rage_mode: 全场狂暴(score 9-10) -- backfire: 话太废了反而debuff(score 1-2)` +// 重复惩罚倍率:根据相似度递增 +function getDuplicatePenaltyMultiplier(similarity: number): number { + if (similarity >= 0.8) return 2.0 // 严重重复:再扣双倍 + if (similarity >= 0.6) return 1.0 // 明显相似:再扣一倍 + return 0 // 不额外惩罚 +} export async function POST(req: Request) { try { - const { text } = await req.json() + const { text, history } = await req.json() as { + text: string + history: string[] // 最近N条历史原文 + } + if (!text || typeof text !== 'string' || text.trim().length < 2) { return NextResponse.json({ error: '输入内容太短了' }, { status: 400 }) } + // 构建带历史的 prompt + const historyBlock = history && history.length > 0 + ? `\n\n历史记录(已经说过的话,按时间倒序):\n${history.slice(0, 5).map((h, i) => `${i + 1}. "${h}"`).join('\n')}` + : '\n\n历史记录:(暂无)' + + const userMsg = `当前输入:"${text.slice(0, 200)}"${historyBlock}` + const resp = await llmClient.chat({ model: 'gpt-4o-mini', messages: [ { role: 'system', content: SYSTEM_PROMPT }, - { role: 'user', content: text.slice(0, 200) }, + { role: 'user', content: userMsg }, ], - maxTokens: 150, - temperature: 0.7, + maxTokens: 200, + temperature: 0.6, }) const raw = resp.choices?.[0]?.message?.content ?? '' - - // 提取 JSON(防止 LLM 多输出文字) const match = raw.match(/\{[\s\S]*\}/) if (!match) throw new Error('LLM 返回格式异常') const result = JSON.parse(match[0]) const score = Math.max(1, Math.min(10, Number(result.score) || 5)) + const similarity = Math.max(0, Math.min(1, Number(result.similarity) || 0)) + const penaltyMultiplier = getDuplicatePenaltyMultiplier(similarity) + const isDuplicate = similarity >= 0.6 + + // 如果重复,效果强制降级或 backfire + let effect = result.effect || 'attack_boost' + let adjustedScore = score + if (isDuplicate && similarity >= 0.8) { + effect = 'backfire' + adjustedScore = Math.max(1, score - 3) + } else if (isDuplicate && similarity >= 0.6) { + // 效果打半折:rage_mode→speed_boost,speed_boost→attack_boost,etc. + const downgrade: Record = { + rage_mode: 'speed_boost', + speed_boost: 'attack_boost', + money_rain: 'attack_boost', + attack_boost: 'backfire', + backfire: 'backfire', + } + effect = downgrade[effect] ?? 'backfire' + adjustedScore = Math.max(1, score - 2) + } return NextResponse.json({ - score, + score: adjustedScore, title: result.title || '打鸡血', desc: result.desc || '还行吧', - effect: result.effect || 'attack_boost', + effect, + similarity, + similarTo: result.similarTo || '', + isDuplicate, + penaltyMultiplier, // 前端据此额外扣 HC }) } catch (e) { console.error('[pua-score]', e) - // 降级:随机给个分数 - const score = Math.floor(Math.random() * 7) + 3 return NextResponse.json({ - score, + score: 3, title: '随机鸡血', desc: 'AI开小差了,随机发力', effect: 'attack_boost', + similarity: 0, + similarTo: '', + isDuplicate: false, + penaltyMultiplier: 0, }) } } diff --git a/app/game/page.tsx b/app/game/page.tsx index f5f45f9..a74865c 100644 --- a/app/game/page.tsx +++ b/app/game/page.tsx @@ -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 = { @@ -53,7 +57,10 @@ function PuaPanel({ gameReady, hc, waveStarted }: { gameReady: boolean; hc: numb const [loading, setLoading] = useState(false) const [result, setResult] = useState(null) const [history, setHistory] = useState([]) + // 历史原文列表(用于发给服务端做相似度检测) + const historyTexts = useRef([]) const [insufficient, setInsufficient] = useState(false) + const [dupWarning, setDupWarning] = useState(null) const textareaRef = useRef(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 - {/* HC 不足闪烁提示 */} + {/* HC 不足提示 */} {insufficient && (
HC 不足!先去打怪赚钱!
)} + {/* 重复惩罚警告 */} + {dupWarning && ( +
+ {dupWarning} +
+ )} + {/* 输入框 */}