import { useMemo } from 'react' import { Button, Popover, Slider, InputNumber, Space, Typography, Divider, Collapse, Tooltip, } from 'antd' import { SettingOutlined, QuestionCircleOutlined, ReloadOutlined } from '@ant-design/icons' import { DEFAULT_RANKING_PARAMS, type RankingParams, } from '../utils/scoring' import { getConfigDisplayLabel } from '../api/configCodes' const { Text } = Typography interface Props { params: RankingParams onChange: (next: RankingParams) => void /** 当前结果集中出现过的 configCode 列表, 用于 "按维度阈值覆盖" 折叠面板 */ configCodesInResult: string[] /** 后端字典 (configCode → 中文标签) */ configCodes: Record } const FORMULA_TEXT = `综合得分 = c × (α × sim_norm + (1-α) × rov_norm) sim_norm = clip((sim - simThreshold) / (1 - simThreshold), 0, 1) rov_norm = clip((rov - rovP5) / (rovP95 - rovP5), 0, 1) c = deconstructBoost (选题/灵感点/关键点/目的点) / 1.0 (其他维度) 先按 simThreshold 硬筛 (sim < 阈值剔除), 再按综合分排序.` export default function RankingSettingsButton({ params, onChange, configCodesInResult, configCodes, }: Props) { const update = (patch: Partial) => onChange({ ...params, ...patch }) const updateCodeThreshold = (code: string, value: number | null) => { const next = { ...params.simThresholdsByCode } if (value == null) delete next[code] else next[code] = value update({ simThresholdsByCode: next }) } const codeRows = useMemo( () => configCodesInResult.map((c) => ({ code: c, label: getConfigDisplayLabel(c, configCodes), override: params.simThresholdsByCode[c], })), [configCodesInResult, configCodes, params.simThresholdsByCode], ) const content = (
update({ simThreshold: typeof v === 'number' ? v : 0 })} style={{ width: 120 }} />
update({ alpha: v })} style={{ flex: 1 }} /> update({ alpha: typeof v === 'number' ? v : 0.6 })} style={{ width: 80 }} />
update({ rovP5: typeof v === 'number' ? v : 0 })} style={{ width: 120 }} /> update({ rovP95: typeof v === 'number' ? v : 0.07 })} style={{ width: 120 }} /> {/* 素材质量维度权重 */} 素材质量维度权重 update({ wSim: typeof v === 'number' ? v : 0.4 })} /> {params.wSim.toFixed(2)} update({ wCtr: typeof v === 'number' ? v : 0.3 })} /> {params.wCtr.toFixed(2)} update({ wViral: typeof v === 'number' ? v : 0.2 })} /> {params.wViral.toFixed(2)} update({ wRoi: typeof v === 'number' ? v : 0.1 })} /> {params.wRoi.toFixed(2)} 视频维度 update({ deconstructBoost: typeof v === 'number' ? v : 1.2 })} style={{ width: 120 }} /> {codeRows.length > 0 && ( 按维度覆盖阈值 ( {Object.keys(params.simThresholdsByCode).length} / {codeRows.length} 已设) ), children: ( {codeRows.map((r) => (
{r.label} updateCodeThreshold(r.code, typeof v === 'number' ? v : null) } style={{ width: 110 }} />
))}
), }, ]} /> )}
) const title = (
排序参数 {FORMULA_TEXT}} >
) return ( ) } function Row({ label, tip, children }: { label: string; tip?: string; children: React.ReactNode }) { return (
{label} {tip && ( )}
{children}
) }