From 9cbffd6408461efcd7e0ca50fce27ca3c0d611a3 Mon Sep 17 00:00:00 2001 From: zhaoying Date: Tue, 24 Mar 2026 12:23:23 +0800 Subject: [PATCH] feat(web): add perceptual node --- web/src/assets/images/userMemory/file.svg | 4 +- .../assets/images/userMemory/play_opacity.svg | 15 +++++ web/src/components/D3Graph/CommunityGraph.tsx | 11 +++- web/src/i18n/en.ts | 1 + web/src/i18n/zh.ts | 1 + .../components/PerceptualLastInfo.tsx | 9 ++- .../components/RelationshipNetwork.tsx | 52 +++++++++++++++- .../components/VideoPlayer.tsx | 62 +++++++++++++++++++ 8 files changed, 144 insertions(+), 11 deletions(-) create mode 100644 web/src/assets/images/userMemory/play_opacity.svg create mode 100644 web/src/views/UserMemoryDetail/components/VideoPlayer.tsx diff --git a/web/src/assets/images/userMemory/file.svg b/web/src/assets/images/userMemory/file.svg index 358e7614..6bfd562e 100644 --- a/web/src/assets/images/userMemory/file.svg +++ b/web/src/assets/images/userMemory/file.svg @@ -62,8 +62,8 @@ - - DOC + + TEXT diff --git a/web/src/assets/images/userMemory/play_opacity.svg b/web/src/assets/images/userMemory/play_opacity.svg new file mode 100644 index 00000000..78de47cf --- /dev/null +++ b/web/src/assets/images/userMemory/play_opacity.svg @@ -0,0 +1,15 @@ + + + 播放 + + + + + + + + + + + + \ No newline at end of file diff --git a/web/src/components/D3Graph/CommunityGraph.tsx b/web/src/components/D3Graph/CommunityGraph.tsx index 549d69f3..41ebc83a 100644 --- a/web/src/components/D3Graph/CommunityGraph.tsx +++ b/web/src/components/D3Graph/CommunityGraph.tsx @@ -1,8 +1,15 @@ +/* + * @Author: ZhaoYing + * @Date: 2026-03-13 15:17:06 + * @Last Modified by: ZhaoYing + * @Last Modified time: 2026-03-24 12:19:57 + */ import React, { useState, useRef, useMemo, useEffect, type FC } from 'react' -import Empty from '@/components/Empty' + import { GRAPH_COLORS, initCommunityGraph } from './utils' import { useD3Graph } from './hooks' import type { CommunityD3Node, D3Link, CommunityGraphProps } from './types' +import PageEmpty from '@/components/Empty/PageEmpty' // ─── Component ──────────────────────────────────────────────────────────────── // Renders a D3-powered community graph with optional tooltip and legend. @@ -51,7 +58,7 @@ const CommunityGraph: FC = ({ ? renderTooltipRef.current(tooltip.node) : null - if (isEmpty) return + if (isEmpty) return return (
diff --git a/web/src/i18n/en.ts b/web/src/i18n/en.ts index baa57e4d..abb0be97 100644 --- a/web/src/i18n/en.ts +++ b/web/src/i18n/en.ts @@ -1542,6 +1542,7 @@ export const en = { MemorySummary: 'Long-term Accumulation', Statement: 'Emotional Memory', ExtractedEntity: 'Episodic Memory', + Perceptual: 'Perceptual Memory', positive: 'Positive Emotion', negative: 'Negative Emotion', neutral: 'Neutral Emotion', diff --git a/web/src/i18n/zh.ts b/web/src/i18n/zh.ts index 16cfe4e0..b5bbbfcd 100644 --- a/web/src/i18n/zh.ts +++ b/web/src/i18n/zh.ts @@ -1540,6 +1540,7 @@ export const zh = { MemorySummary: '长期沉淀', Statement: '情绪记忆', ExtractedEntity: '情景记忆', + Perceptual: '感知记忆', positive: '正向情绪', negative: '负向情绪', neutral: '中性情绪', diff --git a/web/src/views/UserMemoryDetail/components/PerceptualLastInfo.tsx b/web/src/views/UserMemoryDetail/components/PerceptualLastInfo.tsx index 6272a432..99609b58 100644 --- a/web/src/views/UserMemoryDetail/components/PerceptualLastInfo.tsx +++ b/web/src/views/UserMemoryDetail/components/PerceptualLastInfo.tsx @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-02-03 18:32:23 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-03-20 11:07:02 + * @Last Modified time: 2026-03-24 11:36:22 */ import { type FC, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' @@ -12,6 +12,7 @@ import clsx from 'clsx' import RbCard from '@/components/RbCard/Card' import AudioPlayer from './AudioPlayer' +import VideoPlayer from './VideoPlayer' import { getPerceptualLastVisual, getPerceptualLastListen, @@ -107,7 +108,7 @@ const PerceptualLastInfo: FC = () => { }) } - const handleDownload = async () => { + const handleDownload = () => { if (!data.file_path) return window.open(data.file_path, '_blank') } @@ -139,9 +140,7 @@ const PerceptualLastInfo: FC = () => { {/\.(jpg|jpeg|png|gif|webp|svg)$/i.test(data.file_name) ? {data.file_name} : /\.(mp4|webm|ogg|mov)$/i.test(data.file_name) - ? - - + ? : /\.(mp3|wav|ogg|m4a|aac)$/i.test(data.file_name) ? : diff --git a/web/src/views/UserMemoryDetail/components/RelationshipNetwork.tsx b/web/src/views/UserMemoryDetail/components/RelationshipNetwork.tsx index df7d639e..a2467ff4 100644 --- a/web/src/views/UserMemoryDetail/components/RelationshipNetwork.tsx +++ b/web/src/views/UserMemoryDetail/components/RelationshipNetwork.tsx @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-02-03 18:32:00 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-03-20 12:14:43 + * @Last Modified time: 2026-03-24 12:19:12 */ /** * Relationship Network Component @@ -13,7 +13,7 @@ import React, { type FC, useEffect, useState, useCallback, useRef } from 'react' import { useTranslation } from 'react-i18next' import { useParams, useNavigate } from 'react-router-dom' -import { Space, Flex, Divider, type SegmentedProps } from 'antd' +import { Space, Flex, Divider, type SegmentedProps, Image } from 'antd' import dayjs from 'dayjs' import clsx from 'clsx' @@ -27,6 +27,8 @@ import Tag from '@/components/Tag' import GraphNetworkChart, { type Node, type Edge } from '@/components/Charts/GraphNetworkChart' import CommunityNetwork from './CommunityNetwork' import PageTabs from '@/components/PageTabs' +import AudioPlayer from './AudioPlayer' +import VideoPlayer from './VideoPlayer' const RelationshipNetwork: FC = () => { const { t } = useTranslation() @@ -149,6 +151,26 @@ const RelationshipNetwork: FC = () => { setSelectedNode(null) } + const [fileSize, setFileSize] = useState('') + useEffect(() => { + setFileSize('') + if (selectedNode && 'file_path' in selectedNode.properties && selectedNode.properties.file_path) { + fetch(selectedNode.properties.file_path, { method: 'HEAD' }) + .then(r => { + const bytes = Number(r.headers.get('content-length')) + if (!bytes) return + setFileSize(bytes < 1024 * 1024 + ? `${(bytes / 1024).toFixed(1)} KB` + : `${(bytes / 1024 / 1024).toFixed(1)} MB`) + }) + .catch(() => {}) + } + }, [selectedNode]) + const handleDownload = () => { + if (!selectedNode?.properties?.file_path) return + window.open(selectedNode?.properties?.file_path, '_blank') + } + return (
@@ -229,6 +251,8 @@ const RelationshipNetwork: FC = () => { ? selectedNode.properties.description : selectedNode.label === 'Statement' && 'statement' in selectedNode.properties ? selectedNode.properties.statement + : selectedNode.label === 'Perceptual' && 'summary' in selectedNode.properties + ? selectedNode.properties.summary : '' }
@@ -285,6 +309,30 @@ const RelationshipNetwork: FC = () => { return null })} } + {selectedNode.label === 'Perceptual' && <> + {selectedNode.properties.file_type.includes('image') + ? {selectedNode.properties.file_name} + : selectedNode.properties.file_type.includes('video') + ? + : selectedNode.properties.file_type.includes('audio') + ? + : + +
+
+
{selectedNode.properties.file_name}
+
+ {fileSize || '-'} +
+
+
+
+
+ } + } }
diff --git a/web/src/views/UserMemoryDetail/components/VideoPlayer.tsx b/web/src/views/UserMemoryDetail/components/VideoPlayer.tsx new file mode 100644 index 00000000..63b369af --- /dev/null +++ b/web/src/views/UserMemoryDetail/components/VideoPlayer.tsx @@ -0,0 +1,62 @@ +/* + * @Author: ZhaoYing + * @Date: 2026-03-24 12:21:56 + * @Last Modified by: ZhaoYing + * @Last Modified time: 2026-03-24 12:21:56 + */ +import { type FC, useRef, useState } from 'react' +import { CloseOutlined } from '@ant-design/icons' +interface VideoPlayerProps { + src: string +} + +const VideoPlayer: FC = ({ src }) => { + const [open, setOpen] = useState(false) + const videoRef = useRef(null) + + const handleOpen = () => setOpen(true) + + const handleClose = () => { + videoRef.current?.pause() + setOpen(false) + } + + return ( + <> + {/* Thumbnail with play overlay */} +
+
+ + {/* Fullscreen modal */} + {open && ( +
+ +
+ )} + + ) +} + +export default VideoPlayer