feat(web): add graph detail page
This commit is contained in:
@@ -3,8 +3,7 @@ import { useTranslation } from 'react-i18next'
|
|||||||
import ReactEcharts from 'echarts-for-react';
|
import ReactEcharts from 'echarts-for-react';
|
||||||
import Empty from '@/components/Empty'
|
import Empty from '@/components/Empty'
|
||||||
import Loading from '@/components/Empty/Loading'
|
import Loading from '@/components/Empty/Loading'
|
||||||
import type { Emotion } from './GraphDetail'
|
import type { Emotion } from '../pages/GraphDetail'
|
||||||
import { format } from 'echarts';
|
|
||||||
|
|
||||||
interface EmotionLineProps {
|
interface EmotionLineProps {
|
||||||
chartData: Emotion[];
|
chartData: Emotion[];
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next'
|
|||||||
import ReactEcharts from 'echarts-for-react'
|
import ReactEcharts from 'echarts-for-react'
|
||||||
import Empty from '@/components/Empty'
|
import Empty from '@/components/Empty'
|
||||||
import Loading from '@/components/Empty/Loading'
|
import Loading from '@/components/Empty/Loading'
|
||||||
import type { Interaction } from './GraphDetail'
|
import type { Interaction } from '../pages/GraphDetail'
|
||||||
|
|
||||||
interface InteractionBarProps {
|
interface InteractionBarProps {
|
||||||
chartData: Interaction[];
|
chartData: Interaction[];
|
||||||
|
|||||||
@@ -1,19 +1,18 @@
|
|||||||
import React, { type FC, useEffect, useState, useRef, useCallback } from 'react'
|
import React, { type FC, useEffect, useState, useRef, useCallback } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useParams } from 'react-router-dom'
|
import { useParams, useNavigate } from 'react-router-dom'
|
||||||
import { Col, Row, Space, Button } from 'antd'
|
import { Col, Row, Space, Button } from 'antd'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
|
|
||||||
import RbCard from '@/components/RbCard/Card'
|
import RbCard from '@/components/RbCard/Card'
|
||||||
import ReactEcharts from 'echarts-for-react'
|
import ReactEcharts from 'echarts-for-react'
|
||||||
import detailEmpty from '@/assets/images/userMemory/detail_empty.png'
|
import detailEmpty from '@/assets/images/userMemory/detail_empty.png'
|
||||||
import type { Node, Edge, GraphData, StatementNodeProperties, ExtractedEntityNodeProperties, GraphDetailRef } from '../types'
|
import type { Node, Edge, GraphData, StatementNodeProperties, ExtractedEntityNodeProperties } from '../types'
|
||||||
import {
|
import {
|
||||||
getMemorySearchEdges,
|
getMemorySearchEdges,
|
||||||
} from '@/api/memory'
|
} from '@/api/memory'
|
||||||
import Empty from '@/components/Empty'
|
import Empty from '@/components/Empty'
|
||||||
import Tag from '@/components/Tag'
|
import Tag from '@/components/Tag'
|
||||||
import GraphDetail from '../components/GraphDetail'
|
|
||||||
|
|
||||||
const colors = ['#155EEF', '#369F21', '#4DA8FF', '#FF5D34', '#9C6FFF', '#FF8A4C', '#8BAEF7', '#FFB048']
|
const colors = ['#155EEF', '#369F21', '#4DA8FF', '#FF5D34', '#9C6FFF', '#FF8A4C', '#8BAEF7', '#FFB048']
|
||||||
const RelationshipNetwork:FC = () => {
|
const RelationshipNetwork:FC = () => {
|
||||||
@@ -26,7 +25,7 @@ const RelationshipNetwork:FC = () => {
|
|||||||
const [categories, setCategories] = useState<{ name: string }[]>([])
|
const [categories, setCategories] = useState<{ name: string }[]>([])
|
||||||
const [selectedNode, setSelectedNode] = useState<Node | null>(null)
|
const [selectedNode, setSelectedNode] = useState<Node | null>(null)
|
||||||
// const [fullScreen, setFullScreen] = useState<boolean>(false)
|
// const [fullScreen, setFullScreen] = useState<boolean>(false)
|
||||||
const graphDetailRef = useRef<GraphDetailRef>(null)
|
const navigate = useNavigate()
|
||||||
|
|
||||||
console.log('categories', categories)
|
console.log('categories', categories)
|
||||||
// 关系网络
|
// 关系网络
|
||||||
@@ -133,15 +132,14 @@ const RelationshipNetwork:FC = () => {
|
|||||||
}
|
}
|
||||||
}, [nodes])
|
}, [nodes])
|
||||||
|
|
||||||
// const handleFullScreen = () => {
|
|
||||||
// setFullScreen(prev => !prev)
|
|
||||||
// }
|
|
||||||
|
|
||||||
console.log('selectedNode', selectedNode)
|
|
||||||
|
|
||||||
const handleViewAll = () => {
|
const handleViewAll = () => {
|
||||||
if (!selectedNode) return
|
if (!selectedNode) return
|
||||||
graphDetailRef.current?.handleOpen(selectedNode)
|
const params = new URLSearchParams({
|
||||||
|
nodeId: selectedNode.id,
|
||||||
|
nodeLabel: selectedNode.label,
|
||||||
|
nodeName: selectedNode.name || ''
|
||||||
|
})
|
||||||
|
navigate(`/user-memory/detail/${id}/GRAPH?${params.toString()}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -336,8 +334,6 @@ const RelationshipNetwork:FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</RbCard>
|
</RbCard>
|
||||||
</Col>
|
</Col>
|
||||||
|
|
||||||
<GraphDetail ref={graphDetailRef} />
|
|
||||||
</Row>
|
</Row>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
import { useState, forwardRef, useImperativeHandle, useMemo } from 'react'
|
import { useState, forwardRef, useImperativeHandle, useMemo, useEffect } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { useSearchParams } from 'react-router-dom'
|
||||||
import { Row, Col, Tabs, Space, Skeleton } from 'antd'
|
import { Row, Col, Tabs, Space, Skeleton } from 'antd'
|
||||||
|
|
||||||
import { getRelationshipEvolution, getTimelineMemories } from '@/api/memory'
|
import { getRelationshipEvolution, getTimelineMemories } from '@/api/memory'
|
||||||
import type { Node, GraphDetailRef } from '../types'
|
import type { Node, GraphDetailRef } from '../types'
|
||||||
import RbDrawer from '@/components/RbDrawer'
|
|
||||||
import RbCard from '@/components/RbCard/Card'
|
import RbCard from '@/components/RbCard/Card'
|
||||||
import EmotionLine from './EmotionLine'
|
import EmotionLine from '../components/EmotionLine'
|
||||||
import { formatDateTime } from '@/utils/format'
|
import { formatDateTime } from '@/utils/format'
|
||||||
import Tag from '@/components/Tag'
|
import Tag from '@/components/Tag'
|
||||||
import InteractionBar from './InteractionBar'
|
import InteractionBar from '../components/InteractionBar'
|
||||||
import Empty from '@/components/Empty'
|
import Empty from '@/components/Empty'
|
||||||
|
import PageHeader from '../components/PageHeader'
|
||||||
|
|
||||||
export interface Emotion {
|
export interface Emotion {
|
||||||
emotion_intensity: number;
|
emotion_intensity: number;
|
||||||
@@ -35,7 +36,7 @@ interface Timeline {
|
|||||||
|
|
||||||
const GraphDetail = forwardRef<GraphDetailRef>((_props, ref) => {
|
const GraphDetail = forwardRef<GraphDetailRef>((_props, ref) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const [open, setOpen] = useState(false);
|
const [searchParams] = useSearchParams()
|
||||||
const [vo, setVo] = useState<Node | null>(null)
|
const [vo, setVo] = useState<Node | null>(null)
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
const [emotionData, setEmotionData] = useState<Emotion[]>([])
|
const [emotionData, setEmotionData] = useState<Emotion[]>([])
|
||||||
@@ -43,14 +44,23 @@ const GraphDetail = forwardRef<GraphDetailRef>((_props, ref) => {
|
|||||||
const [activeTab, setActiveTab] = useState('timelines_memory')
|
const [activeTab, setActiveTab] = useState('timelines_memory')
|
||||||
const [timelineLoading, setTimelineLoading] = useState(false)
|
const [timelineLoading, setTimelineLoading] = useState(false)
|
||||||
const [timelineMemories, setTimelineMemories] = useState<Timeline>({ timelines_memory: [], MemorySummary: [], Statement: [], ExtractedEntity: []})
|
const [timelineMemories, setTimelineMemories] = useState<Timeline>({ timelines_memory: [], MemorySummary: [], Statement: [], ExtractedEntity: []})
|
||||||
|
useEffect(() => {
|
||||||
|
const nodeId = searchParams.get('nodeId')
|
||||||
|
const nodeLabel = searchParams.get('nodeLabel')
|
||||||
|
const nodeName = searchParams.get('nodeName')
|
||||||
|
|
||||||
|
if (nodeId && nodeLabel) {
|
||||||
|
const nodeFromUrl = {
|
||||||
|
id: nodeId,
|
||||||
|
label: nodeLabel,
|
||||||
|
name: nodeName || nodeLabel
|
||||||
|
}
|
||||||
|
handleOpen(nodeFromUrl as Node)
|
||||||
|
}
|
||||||
|
}, [searchParams])
|
||||||
|
|
||||||
const handleCancel = () => {
|
|
||||||
setVo(null)
|
|
||||||
setOpen(false)
|
|
||||||
}
|
|
||||||
const handleOpen = (vo: Node) => {
|
const handleOpen = (vo: Node) => {
|
||||||
setActiveTab('timelines_memory')
|
setActiveTab('timelines_memory')
|
||||||
setOpen(true)
|
|
||||||
setVo(vo)
|
setVo(vo)
|
||||||
getRelationshipEvolutionData(vo)
|
getRelationshipEvolutionData(vo)
|
||||||
getTimelineMemoriesData(vo)
|
getTimelineMemoriesData(vo)
|
||||||
@@ -85,56 +95,57 @@ const GraphDetail = forwardRef<GraphDetailRef>((_props, ref) => {
|
|||||||
}, [activeTab, timelineMemories])
|
}, [activeTab, timelineMemories])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RbDrawer
|
<>
|
||||||
title={vo?.name}
|
<PageHeader
|
||||||
open={open}
|
name={vo?.name}
|
||||||
onClose={handleCancel}
|
source="node"
|
||||||
width={1000}
|
/>
|
||||||
>
|
<div className="rb:h-full rb:max-w-266 rb:mx-auto">
|
||||||
<div className="rb:text-[16px] rb:font-medium rb:leading-5.5 rb:mb-3">{t('userMemory.relationshipEvolution')}</div>
|
<div className="rb:text-[16px] rb:font-medium rb:leading-5.5 rb:mb-3">{t('userMemory.relationshipEvolution')}</div>
|
||||||
<RbCard>
|
<RbCard>
|
||||||
<Row gutter={16}>
|
<Row gutter={16}>
|
||||||
<Col span={12}>
|
<Col span={12}>
|
||||||
<EmotionLine chartData={emotionData} loading={loading} />
|
<EmotionLine chartData={emotionData} loading={loading} />
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={12}>
|
<Col span={12}>
|
||||||
<InteractionBar chartData={interactionData} loading={loading} />
|
<InteractionBar chartData={interactionData} loading={loading} />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
</RbCard>
|
</RbCard>
|
||||||
|
|
||||||
<div className="rb:text-[16px] rb:font-medium rb:leading-5.5 rb:mb-3 rb:mt-6">{t('userMemory.timelineMemories')}</div>
|
<div className="rb:text-[16px] rb:font-medium rb:leading-5.5 rb:mb-3 rb:mt-6">{t('userMemory.timelineMemories')}</div>
|
||||||
<RbCard>
|
<RbCard>
|
||||||
<Tabs
|
<Tabs
|
||||||
activeKey={activeTab}
|
activeKey={activeTab}
|
||||||
items={['timelines_memory', 'ExtractedEntity', 'Statement', 'MemorySummary'].map(key => ({
|
items={['timelines_memory', 'ExtractedEntity', 'Statement', 'MemorySummary'].map(key => ({
|
||||||
label: t(`userMemory.${key}`),
|
label: t(`userMemory.${key}`),
|
||||||
key
|
key
|
||||||
}))}
|
}))}
|
||||||
onChange={(key: string) => setActiveTab(key)}
|
onChange={(key: string) => setActiveTab(key)}
|
||||||
/>
|
/>
|
||||||
{timelineLoading
|
{timelineLoading
|
||||||
? <Skeleton active />
|
? <Skeleton active />
|
||||||
: !activeContent || activeContent.length === 0
|
: !activeContent || activeContent.length === 0
|
||||||
? <Empty size={120} className="rb:mt-12 rb:mb-20.25" />
|
? <Empty size={120} className="rb:mt-12 rb:mb-20.25" />
|
||||||
: <Space size={16} direction="vertical" className="rb:w-full">
|
: <Space size={16} direction="vertical" className="rb:w-full">
|
||||||
{activeContent.map((vo, index) => (
|
{activeContent.map((vo, index) => (
|
||||||
<RbCard
|
<RbCard
|
||||||
key={index}
|
key={index}
|
||||||
headerType="borderL"
|
headerType="borderL"
|
||||||
headerClassName="rb:before:bg-[#155EEF]!"
|
headerClassName="rb:before:bg-[#155EEF]!"
|
||||||
title={vo.text}
|
title={vo.text}
|
||||||
>
|
>
|
||||||
<div className="rb:text-[#A8A9AA] rb:text-[12px] rb:leading-4">{formatDateTime(vo.created_at)}</div>
|
<div className="rb:text-[#A8A9AA] rb:text-[12px] rb:leading-4">{formatDateTime(vo.created_at)}</div>
|
||||||
<Tag className="rb:mt-2">{vo.type}</Tag>
|
<Tag className="rb:mt-2">{vo.type}</Tag>
|
||||||
</RbCard>
|
</RbCard>
|
||||||
))}
|
))}
|
||||||
</Space>
|
</Space>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
</RbCard>
|
</RbCard>
|
||||||
</RbDrawer>
|
</div>
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
export default GraphDetail
|
export default GraphDetail
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { type FC, useEffect, useState, useMemo, useRef } from 'react'
|
import { type FC, useEffect, useState, useMemo, useRef } from 'react'
|
||||||
import { useParams, useNavigate } from 'react-router-dom'
|
import { useParams, useNavigate } from 'react-router-dom'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Dropdown, Space, Button } from 'antd'
|
import { Dropdown, Button } from 'antd'
|
||||||
|
|
||||||
import PageHeader from '../components/PageHeader'
|
import PageHeader from '../components/PageHeader'
|
||||||
import StatementDetail from './StatementDetail'
|
import StatementDetail from './StatementDetail'
|
||||||
@@ -16,6 +16,7 @@ import {
|
|||||||
getEndUserProfile,
|
getEndUserProfile,
|
||||||
} from '@/api/memory'
|
} from '@/api/memory'
|
||||||
import refreshIcon from '@/assets/images/refresh_hover.svg'
|
import refreshIcon from '@/assets/images/refresh_hover.svg'
|
||||||
|
import GraphDetail from './GraphDetail'
|
||||||
|
|
||||||
const Detail: FC = () => {
|
const Detail: FC = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
@@ -47,6 +48,10 @@ const Detail: FC = () => {
|
|||||||
forgetDetailRef.current?.handleRefresh()
|
forgetDetailRef.current?.handleRefresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (type === 'GRAPH') {
|
||||||
|
return <GraphDetail />
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="rb:h-full rb:w-full">
|
<div className="rb:h-full rb:w-full">
|
||||||
<PageHeader
|
<PageHeader
|
||||||
|
|||||||
Reference in New Issue
Block a user