DeconstructTree.tsx 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. import { Button, Empty, Tag, Typography, Space, Alert, Tooltip } from 'antd'
  2. import { LinkOutlined, ThunderboltOutlined } from '@ant-design/icons'
  3. import type { DeconstructPointsVO, HighValuePoint } from '../api/types'
  4. const { Text, Paragraph } = Typography
  5. interface Props {
  6. data: DeconstructPointsVO | null
  7. loading?: boolean
  8. /** 第二个参数 configCode 优先于 dropdown 的选择 */
  9. onRecallByText: (text: string, configCodeOverride?: string) => void
  10. }
  11. const TYPE_COLOR: Record<string, string> = {
  12. 灵感点: 'blue',
  13. 目的点: 'gold',
  14. 关键点: 'green',
  15. }
  16. /** 当前后端只支持 VIDEO_TOPIC 和 VIDEO_INSPIRATION 两个维度 */
  17. const SUPPORTED_TYPES = new Set(['灵感点'])
  18. const UNSUPPORTED_TIP = '暂仅支持 灵感点 整条召回; 关键点/目的点 暂未配置向量维度'
  19. const UNSUPPORTED_WORD_TIP = '暂不支持词级召回(尚未配置词级向量维度)'
  20. /**
  21. * 按钮纵向对齐: 所有按钮保持同样的右边缘 + 同样的宽度
  22. * 父级(选题/灵感点/关键点/目的点)用 size="middle" 默认高度 → 视觉更突出
  23. * 子级(实质词召回)用 size="small" → 字体更小,视觉退一级
  24. * 两者宽度都=110, 都带闪电图标, 右对齐
  25. */
  26. const BTN_WIDTH = 110
  27. /** 父级生效: 绿色实色 */
  28. const ACTIVE_PARENT_BTN_STYLE: React.CSSProperties = {
  29. width: BTN_WIDTH,
  30. background: '#52c41a',
  31. borderColor: '#52c41a',
  32. color: '#fff',
  33. flexShrink: 0,
  34. }
  35. /** 父级 disabled: 灰色 */
  36. const DISABLED_PARENT_BTN_STYLE: React.CSSProperties = {
  37. width: BTN_WIDTH,
  38. flexShrink: 0,
  39. }
  40. /** 子级 disabled: 字体小,灰色 */
  41. const DISABLED_CHILD_BTN_STYLE: React.CSSProperties = {
  42. width: BTN_WIDTH,
  43. flexShrink: 0,
  44. fontSize: 12,
  45. }
  46. /**
  47. * 展示后端 getDeconstructPoints 返回的"实质≥0.8 高价值点"
  48. *
  49. * 召回策略:
  50. * - 选题"以此选题召回" → VIDEO_TOPIC (生效)
  51. * - 灵感点行"以此召回" → VIDEO_INSPIRATION (生效, 卡片绿色背景)
  52. * - 关键点 / 目的点 整条 → disabled 灰色, 灰色背景
  53. * - 任何点下的实质词"召回" → disabled 灰色 (暂不支持词级)
  54. */
  55. export default function DeconstructTree({ data, loading, onRecallByText }: Props) {
  56. if (loading) return <div>加载中...</div>
  57. if (!data) {
  58. return <Empty description="未找到该视频的解构记录" image={Empty.PRESENTED_IMAGE_SIMPLE} />
  59. }
  60. return (
  61. <Space direction="vertical" size={12} style={{ width: '100%' }}>
  62. {/* 选题 */}
  63. {data.topic && (
  64. <div
  65. style={{
  66. border: '1px solid #d3adf7',
  67. background: '#f9f0ff',
  68. borderRadius: 6,
  69. padding: '10px 12px',
  70. }}
  71. >
  72. <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 6 }}>
  73. <Tag color="purple" style={{ margin: 0 }}>选题</Tag>
  74. <div style={{ flex: 1 }} />
  75. <Button
  76. type="primary"
  77. icon={<ThunderboltOutlined />}
  78. onClick={() => onRecallByText(data.topic!, 'VIDEO_TOPIC')}
  79. style={ACTIVE_PARENT_BTN_STYLE}
  80. >
  81. 以此召回
  82. </Button>
  83. </div>
  84. <Paragraph
  85. style={{ marginBottom: 0, whiteSpace: 'pre-wrap', fontSize: 13, color: '#555' }}
  86. >
  87. {data.topic}
  88. </Paragraph>
  89. </div>
  90. )}
  91. {/* 权重可视化链接 */}
  92. {data.htmlUrl && (
  93. <Alert
  94. type="info"
  95. showIcon
  96. icon={<LinkOutlined />}
  97. message={
  98. <a href={data.htmlUrl} target="_blank" rel="noreferrer">
  99. 查看带权重的可视化页面
  100. </a>
  101. }
  102. style={{ padding: '6px 12px' }}
  103. />
  104. )}
  105. {/* 高价值点 */}
  106. {data.highValuePoints && data.highValuePoints.length > 0 ? (
  107. <Space direction="vertical" size={10} style={{ width: '100%' }}>
  108. {data.highValuePoints.map((p) => (
  109. <PointCard key={`${p.type}-${p.id}`} point={p} onRecallByText={onRecallByText} />
  110. ))}
  111. </Space>
  112. ) : (
  113. <Empty description="该视频无 ≥0.8 的实质点" image={Empty.PRESENTED_IMAGE_SIMPLE} />
  114. )}
  115. </Space>
  116. )
  117. }
  118. function PointCard({
  119. point,
  120. onRecallByText,
  121. }: {
  122. point: HighValuePoint
  123. onRecallByText: (t: string, code?: string) => void
  124. }) {
  125. const color = TYPE_COLOR[point.type] || 'default'
  126. const supported = SUPPORTED_TYPES.has(point.type)
  127. // 整条召回按钮 (生效 vs 灰色) - 父级,默认中号
  128. const headerBtn = supported ? (
  129. <Button
  130. type="primary"
  131. icon={<ThunderboltOutlined />}
  132. onClick={() => onRecallByText(point.name, 'VIDEO_INSPIRATION')}
  133. style={ACTIVE_PARENT_BTN_STYLE}
  134. >
  135. 以此召回
  136. </Button>
  137. ) : (
  138. <Tooltip title={UNSUPPORTED_TIP}>
  139. <Button disabled icon={<ThunderboltOutlined />} style={DISABLED_PARENT_BTN_STYLE}>
  140. 以此召回
  141. </Button>
  142. </Tooltip>
  143. )
  144. return (
  145. <div
  146. style={{
  147. border: supported ? '1px solid #b7eb8f' : '1px solid #e8e8e8',
  148. background: supported ? '#f6ffed' : '#fafafa',
  149. borderRadius: 6,
  150. padding: '10px 12px',
  151. opacity: supported ? 1 : 0.85,
  152. }}
  153. >
  154. <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 4 }}>
  155. <Tag color={color} style={{ margin: 0, flexShrink: 0 }}>{point.type}</Tag>
  156. <Text strong style={{ fontSize: 13, flex: 1 }} ellipsis={{ tooltip: point.name }}>
  157. {point.name}
  158. </Text>
  159. {headerBtn}
  160. </div>
  161. <div style={{ marginBottom: 8 }}>
  162. <Text type="secondary" style={{ fontSize: 11 }}>
  163. {point.id}
  164. </Text>
  165. </div>
  166. {/* 实质词 - 纵向一列, 召回按钮右对齐, 全部 disabled */}
  167. <Space direction="vertical" size={6} style={{ width: '100%' }}>
  168. {point.essences.map((e) => (
  169. <div
  170. key={e.word}
  171. style={{
  172. display: 'flex',
  173. alignItems: 'center',
  174. gap: 8,
  175. padding: '6px 10px',
  176. border: '1px solid #d9d9d9',
  177. borderRadius: 4,
  178. background: '#fff',
  179. fontSize: 12,
  180. }}
  181. >
  182. <span style={{ fontWeight: 500, minWidth: 0, flex: '0 1 auto' }}>{e.word}</span>
  183. <Tag
  184. color={e.score >= 0.9 ? 'red' : e.score >= 0.85 ? 'orange' : 'gold'}
  185. style={{ margin: 0 }}
  186. >
  187. {e.score.toFixed(2)}
  188. </Tag>
  189. {/* 推到右边,所有按钮 width=110 对齐 */}
  190. <div style={{ flex: 1 }} />
  191. <Tooltip title={UNSUPPORTED_WORD_TIP}>
  192. <Button
  193. size="small"
  194. disabled
  195. icon={<ThunderboltOutlined />}
  196. style={DISABLED_CHILD_BTN_STYLE}
  197. >
  198. 以此召回
  199. </Button>
  200. </Tooltip>
  201. </div>
  202. ))}
  203. </Space>
  204. </div>
  205. )
  206. }