77import { memo , useState , useRef , useEffect } from 'react'
88import { Handle , Position } from 'reactflow'
99import ColorAccentBar from './ColorAccentBar'
10+ import MetaAnalysisInline from './MetaAnalysisInline'
11+ import useCanvasStore from '../../stores/useCanvasStore'
1012
1113// 安全获取字符串内容
1214const safeString = ( val ) => {
@@ -98,6 +100,14 @@ function ConceptNode({ id, data, selected }) {
98100 const hoverTimeoutRef = useRef ( null )
99101 const collapseTimeoutRef = useRef ( null )
100102
103+ // 元认知按钮状态来自 store data — 跟 yjs 同步
104+ const analyzeMeta = useCanvasStore ( ( s ) => s . analyzeNodeMetaCognitive )
105+ const updateNode = useCanvasStore ( ( s ) => s . updateNode )
106+ const isAnalyzing = data . metaAnalyzing === true
107+ const metaAnalysis = data . metaAnalysis
108+ const metaError = data . metaAnalysisError
109+ const metaExpanded = data . metaExpanded === true
110+
101111 const sizeStyle = SIZE_SCALES [ data . size ] || SIZE_SCALES . medium
102112 const isMarked = data . marked
103113 const markColor = data . markColor || 'var(--accent)'
@@ -108,6 +118,22 @@ function ConceptNode({ id, data, selected }) {
108118 const hasRichContent = data . description && data . description . length > 60
109119 const isExpanded = ( isHovered || isPinned ) && hasRichContent
110120
121+ // 元认知按钮处理
122+ const onAnalyzeMeta = ( e ) => {
123+ e . stopPropagation ( )
124+ if ( isAnalyzing || ! data . title ?. trim ( ) ) return
125+ if ( metaAnalysis ) {
126+ updateNode ( id , { metaExpanded : ! metaExpanded } )
127+ return
128+ }
129+ analyzeMeta ( id ) . catch ( ( err ) => console . error ( '[ConceptNode] analyze failed:' , err ) )
130+ }
131+ const onReanalyze = ( e ) => {
132+ e . stopPropagation ( )
133+ if ( isAnalyzing ) return
134+ analyzeMeta ( id ) . catch ( ( err ) => console . error ( '[ConceptNode] reanalyze failed:' , err ) )
135+ }
136+
111137 // 清理定时器
112138 useEffect ( ( ) => {
113139 return ( ) => {
@@ -324,6 +350,54 @@ function ConceptNode({ id, data, selected }) {
324350 ) ) }
325351 </ div >
326352 ) }
353+
354+ { /* 元认知按钮 (一直可见) */ }
355+ { data . title && (
356+ < div className = "mt-2" >
357+ < button
358+ onClick = { onAnalyzeMeta }
359+ disabled = { isAnalyzing }
360+ className = "w-full text-[10px] py-1 px-2 rounded-sm border transition-all"
361+ style = { {
362+ borderColor : metaAnalysis ? 'var(--accent)' : 'var(--accent-soft, var(--accent))' ,
363+ color : 'var(--accent)' ,
364+ background : metaAnalysis ? 'var(--accent-bg, rgba(245,240,235,0.7))' : 'var(--accent-bg, rgba(245,240,235,0.4))' ,
365+ cursor : isAnalyzing ? 'wait' : 'pointer' ,
366+ opacity : isAnalyzing ? 0.6 : 1 ,
367+ fontWeight : metaAnalysis ? 500 : 400 ,
368+ } }
369+ title = {
370+ isAnalyzing ? '正在分析中…' :
371+ metaAnalysis ? `点击${ metaExpanded ? '收起' : '展开' } 元认知分析` :
372+ '一次 LLM 调用 → 5 维度分析'
373+ }
374+ >
375+ { isAnalyzing ? '分析中…' : metaAnalysis ? `⚡ 元认知 ${ metaExpanded ? '▴' : '▾' } ` : '⚡ 元认知分析' }
376+ </ button >
377+ </ div >
378+ ) }
379+
380+ { /* 元认知错误 */ }
381+ { metaError && ! metaAnalysis && (
382+ < div className = "text-[10px] mt-2 px-2 py-1 rounded-sm" style = { {
383+ color : '#7a3a4a' ,
384+ background : 'rgba(245,235,237,0.6)' ,
385+ border : '1px solid #b27c8b' ,
386+ } } >
387+ 分析失败: { metaError }
388+ < button onClick = { onReanalyze } className = "ml-2 underline" style = { { color : '#7a3a4a' } } > 重试</ button >
389+ </ div >
390+ ) }
391+
392+ { /* 元认知 inline 折叠区 */ }
393+ { metaAnalysis && metaExpanded && (
394+ < MetaAnalysisInline
395+ analysis = { metaAnalysis }
396+ textColor = "var(--text-secondary)"
397+ onReanalyze = { onReanalyze }
398+ isAnalyzing = { isAnalyzing }
399+ />
400+ ) }
327401 </ div >
328402
329403 { /* 底部信息栏 */ }
0 commit comments