| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222 |
- import { Button, Empty, Tag, Typography, Space, Alert, Tooltip } from 'antd'
- import { LinkOutlined, ThunderboltOutlined } from '@ant-design/icons'
- import type { DeconstructPointsVO, HighValuePoint } from '../api/types'
- const { Text, Paragraph } = Typography
- interface Props {
- data: DeconstructPointsVO | null
- loading?: boolean
- /** 第二个参数 configCode 优先于 dropdown 的选择 */
- onRecallByText: (text: string, configCodeOverride?: string) => void
- }
- const TYPE_COLOR: Record<string, string> = {
- 灵感点: 'blue',
- 目的点: 'gold',
- 关键点: 'green',
- }
- /** 当前后端只支持 VIDEO_TOPIC 和 VIDEO_INSPIRATION 两个维度 */
- const SUPPORTED_TYPES = new Set(['灵感点'])
- const UNSUPPORTED_TIP = '暂仅支持 灵感点 整条召回; 关键点/目的点 暂未配置向量维度'
- const UNSUPPORTED_WORD_TIP = '暂不支持词级召回(尚未配置词级向量维度)'
- /**
- * 按钮纵向对齐: 所有按钮保持同样的右边缘 + 同样的宽度
- * 父级(选题/灵感点/关键点/目的点)用 size="middle" 默认高度 → 视觉更突出
- * 子级(实质词召回)用 size="small" → 字体更小,视觉退一级
- * 两者宽度都=110, 都带闪电图标, 右对齐
- */
- const BTN_WIDTH = 110
- /** 父级生效: 绿色实色 */
- const ACTIVE_PARENT_BTN_STYLE: React.CSSProperties = {
- width: BTN_WIDTH,
- background: '#52c41a',
- borderColor: '#52c41a',
- color: '#fff',
- flexShrink: 0,
- }
- /** 父级 disabled: 灰色 */
- const DISABLED_PARENT_BTN_STYLE: React.CSSProperties = {
- width: BTN_WIDTH,
- flexShrink: 0,
- }
- /** 子级 disabled: 字体小,灰色 */
- const DISABLED_CHILD_BTN_STYLE: React.CSSProperties = {
- width: BTN_WIDTH,
- flexShrink: 0,
- fontSize: 12,
- }
- /**
- * 展示后端 getDeconstructPoints 返回的"实质≥0.8 高价值点"
- *
- * 召回策略:
- * - 选题"以此选题召回" → VIDEO_TOPIC (生效)
- * - 灵感点行"以此召回" → VIDEO_INSPIRATION (生效, 卡片绿色背景)
- * - 关键点 / 目的点 整条 → disabled 灰色, 灰色背景
- * - 任何点下的实质词"召回" → disabled 灰色 (暂不支持词级)
- */
- export default function DeconstructTree({ data, loading, onRecallByText }: Props) {
- if (loading) return <div>加载中...</div>
- if (!data) {
- return <Empty description="未找到该视频的解构记录" image={Empty.PRESENTED_IMAGE_SIMPLE} />
- }
- return (
- <Space direction="vertical" size={12} style={{ width: '100%' }}>
- {/* 选题 */}
- {data.topic && (
- <div
- style={{
- border: '1px solid #d3adf7',
- background: '#f9f0ff',
- borderRadius: 6,
- padding: '10px 12px',
- }}
- >
- <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 6 }}>
- <Tag color="purple" style={{ margin: 0 }}>选题</Tag>
- <div style={{ flex: 1 }} />
- <Button
- type="primary"
- icon={<ThunderboltOutlined />}
- onClick={() => onRecallByText(data.topic!, 'VIDEO_TOPIC')}
- style={ACTIVE_PARENT_BTN_STYLE}
- >
- 以此召回
- </Button>
- </div>
- <Paragraph
- style={{ marginBottom: 0, whiteSpace: 'pre-wrap', fontSize: 13, color: '#555' }}
- >
- {data.topic}
- </Paragraph>
- </div>
- )}
- {/* 权重可视化链接 */}
- {data.htmlUrl && (
- <Alert
- type="info"
- showIcon
- icon={<LinkOutlined />}
- message={
- <a href={data.htmlUrl} target="_blank" rel="noreferrer">
- 查看带权重的可视化页面
- </a>
- }
- style={{ padding: '6px 12px' }}
- />
- )}
- {/* 高价值点 */}
- {data.highValuePoints && data.highValuePoints.length > 0 ? (
- <Space direction="vertical" size={10} style={{ width: '100%' }}>
- {data.highValuePoints.map((p) => (
- <PointCard key={`${p.type}-${p.id}`} point={p} onRecallByText={onRecallByText} />
- ))}
- </Space>
- ) : (
- <Empty description="该视频无 ≥0.8 的实质点" image={Empty.PRESENTED_IMAGE_SIMPLE} />
- )}
- </Space>
- )
- }
- function PointCard({
- point,
- onRecallByText,
- }: {
- point: HighValuePoint
- onRecallByText: (t: string, code?: string) => void
- }) {
- const color = TYPE_COLOR[point.type] || 'default'
- const supported = SUPPORTED_TYPES.has(point.type)
- // 整条召回按钮 (生效 vs 灰色) - 父级,默认中号
- const headerBtn = supported ? (
- <Button
- type="primary"
- icon={<ThunderboltOutlined />}
- onClick={() => onRecallByText(point.name, 'VIDEO_INSPIRATION')}
- style={ACTIVE_PARENT_BTN_STYLE}
- >
- 以此召回
- </Button>
- ) : (
- <Tooltip title={UNSUPPORTED_TIP}>
- <Button disabled icon={<ThunderboltOutlined />} style={DISABLED_PARENT_BTN_STYLE}>
- 以此召回
- </Button>
- </Tooltip>
- )
- return (
- <div
- style={{
- border: supported ? '1px solid #b7eb8f' : '1px solid #e8e8e8',
- background: supported ? '#f6ffed' : '#fafafa',
- borderRadius: 6,
- padding: '10px 12px',
- opacity: supported ? 1 : 0.85,
- }}
- >
- <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 4 }}>
- <Tag color={color} style={{ margin: 0, flexShrink: 0 }}>{point.type}</Tag>
- <Text strong style={{ fontSize: 13, flex: 1 }} ellipsis={{ tooltip: point.name }}>
- {point.name}
- </Text>
- {headerBtn}
- </div>
- <div style={{ marginBottom: 8 }}>
- <Text type="secondary" style={{ fontSize: 11 }}>
- {point.id}
- </Text>
- </div>
- {/* 实质词 - 纵向一列, 召回按钮右对齐, 全部 disabled */}
- <Space direction="vertical" size={6} style={{ width: '100%' }}>
- {point.essences.map((e) => (
- <div
- key={e.word}
- style={{
- display: 'flex',
- alignItems: 'center',
- gap: 8,
- padding: '6px 10px',
- border: '1px solid #d9d9d9',
- borderRadius: 4,
- background: '#fff',
- fontSize: 12,
- }}
- >
- <span style={{ fontWeight: 500, minWidth: 0, flex: '0 1 auto' }}>{e.word}</span>
- <Tag
- color={e.score >= 0.9 ? 'red' : e.score >= 0.85 ? 'orange' : 'gold'}
- style={{ margin: 0 }}
- >
- {e.score.toFixed(2)}
- </Tag>
- {/* 推到右边,所有按钮 width=110 对齐 */}
- <div style={{ flex: 1 }} />
- <Tooltip title={UNSUPPORTED_WORD_TIP}>
- <Button
- size="small"
- disabled
- icon={<ThunderboltOutlined />}
- style={DISABLED_CHILD_BTN_STYLE}
- >
- 以此召回
- </Button>
- </Tooltip>
- </div>
- ))}
- </Space>
- </div>
- )
- }
|