@@ -52,6 +52,9 @@ export default function NoteGroupPanel({ groupFilter, onGroupFilterChange, selec
5252 const [ groups , setGroups ] = useState < NoteGroup [ ] > ( [ ] ) ;
5353 const [ expandedId , setExpandedId ] = useState < number | null > ( null ) ;
5454 const [ status , setStatus ] = useState ( "" ) ;
55+ // v0.11.1:feed 捕获开关(默认关——功能预览纪律)与捕获输入态
56+ const [ feedCaptureOn , setFeedCaptureOn ] = useState ( false ) ;
57+ const [ fragText , setFragText ] = useState ( "" ) ;
5558 // 改判表单态(展开区消费)
5659 const [ overrideKind , setOverrideKind ] = useState ( "standalone" ) ;
5760 const [ overrideDomain , setOverrideDomain ] = useState ( "" ) ;
@@ -65,7 +68,46 @@ export default function NoteGroupPanel({ groupFilter, onGroupFilterChange, selec
6568 }
6669 } , [ ] ) ;
6770
68- useEffect ( ( ) => { void load ( ) ; } , [ load ] ) ;
71+ useEffect ( ( ) => {
72+ void load ( ) ;
73+ // 开关状态只在挂载时读一次(设置页改动后重新进笔记页即刷新)
74+ invoke < { feedCapture : boolean } > ( "get_feature_flags" )
75+ . then ( ( f ) => setFeedCaptureOn ( f . feedCapture ) )
76+ . catch ( ( ) => setFeedCaptureOn ( false ) ) ;
77+ } , [ load ] ) ;
78+
79+ // ── 碎片捕获(feed 进料口;后端二次校验开关)──
80+ const runCapture = async ( source : "manual" | "clipboard" ) => {
81+ try {
82+ let text = fragText . trim ( ) ;
83+ let imageBytes : number [ ] | null = null ;
84+ if ( source === "clipboard" ) {
85+ // 剪贴板优先:文本+图片一次捕获(REQ-132 预留兑现)
86+ const clipText = await navigator . clipboard . readText ( ) . catch ( ( ) => "" ) ;
87+ if ( clipText . trim ( ) ) text = clipText . trim ( ) ;
88+ const items = await navigator . clipboard . read ( ) . catch ( ( ) => [ ] ) ;
89+ for ( const item of items ) {
90+ const imgType = item . types . find ( ( t ) => t . startsWith ( "image/" ) ) ;
91+ if ( imgType ) {
92+ const blob = await item . getType ( imgType ) ;
93+ imageBytes = Array . from ( new Uint8Array ( await blob . arrayBuffer ( ) ) ) ;
94+ break ;
95+ }
96+ }
97+ }
98+ if ( ! text && ! imageBytes ) {
99+ setStatus ( "请输入碎片内容或复制剪贴板后再捕获" ) ;
100+ return ;
101+ }
102+ await invoke ( "capture_fragment" , { text, imageBytes, source } ) ;
103+ setFragText ( "" ) ;
104+ setStatus ( "" ) ;
105+ await load ( ) ; // 可能新建 feed 主题组——刷新组列表
106+ onChanged ( ) ;
107+ } catch ( e ) {
108+ setStatus ( `碎片捕获失败: ${ e } ` ) ;
109+ }
110+ } ;
69111
70112 // ── 改判(REQ-198 修改即记忆)──
71113 const runOverride = async ( g : NoteGroup ) => {
@@ -103,6 +145,30 @@ export default function NoteGroupPanel({ groupFilter, onGroupFilterChange, selec
103145 < button onClick = { ( ) => void load ( ) } style = { { marginLeft : "auto" , fontSize : 13 , cursor : "pointer" } } title = "刷新组列表" > ⟳</ button >
104146 </ div >
105147 < div style = { { flex : 1 , overflowY : "auto" , padding : 6 } } >
148+ { /* v0.11.1:碎片快速捕获区(开关默认关;开启后置顶——feed 进料口) */ }
149+ { feedCaptureOn && (
150+ < div style = { { margin : "2px 0 8px" , padding : 8 , background : "#faf5ff" , borderRadius : 6 , border : "1px solid #e9d5ff" } } >
151+ < div style = { { fontSize : 11 , fontWeight : 600 , color : "#7c3aed" , marginBottom : 4 } } > ⚡ 碎片捕获</ div >
152+ < textarea
153+ value = { fragText }
154+ onChange = { ( e ) => setFragText ( e . target . value ) }
155+ placeholder = "几句话记下刚学到的…"
156+ rows = { 2 }
157+ style = { { width : "100%" , fontSize : 12 , padding : 4 , border : "1px solid #e5e7eb" , borderRadius : 4 , resize : "vertical" , boxSizing : "border-box" } }
158+ />
159+ < div style = { { display : "flex" , gap : 4 , marginTop : 4 } } >
160+ < button onClick = { ( ) => void runCapture ( "manual" ) }
161+ style = { { fontSize : 11 , cursor : "pointer" , padding : "2px 8px" , borderRadius : 4 , border : "1px solid #d1d5db" , background : "#fff" } } >
162+ 捕获
163+ </ button >
164+ < button onClick = { ( ) => void runCapture ( "clipboard" ) }
165+ style = { { fontSize : 11 , cursor : "pointer" , padding : "2px 8px" , borderRadius : 4 , border : "1px solid #d1d5db" , background : "#fff" } }
166+ title = "文本+图片从剪贴板一次捕获" >
167+ 📋 剪贴板捕获
168+ </ button >
169+ </ div >
170+ </ div >
171+ ) }
106172 { /* 全部(取消组过滤) */ }
107173 < div
108174 onClick = { ( ) => onGroupFilterChange ( null ) }
@@ -149,6 +215,9 @@ export default function NoteGroupPanel({ groupFilter, onGroupFilterChange, selec
149215 </ div >
150216 < div style = { { display : "flex" , alignItems : "center" , gap : 4 , marginTop : 3 } } >
151217 < span style = { { fontSize : 10 , color : badge . color , background : "#f9fafb" , borderRadius : 8 , padding : "0 5px" } } > { badge . label } </ span >
218+ { g . terrain === "feed" && (
219+ < span style = { { fontSize : 10 , color : "#7c3aed" , background : "#faf5ff" , borderRadius : 8 , padding : "0 5px" } } > feed</ span >
220+ ) }
152221 { reason . needsConfirm && ! g . routeOverridden && (
153222 < span style = { { fontSize : 10 , color : "#b45309" , background : "#fffbeb" , borderRadius : 8 , padding : "0 5px" } } > ⚠ 待确认</ span >
154223 ) }
0 commit comments