@@ -47,90 +47,169 @@ impl Default for SearchAggregator {
4747 }
4848}
4949
50+ fn ensure_terminal_punct ( text : & str ) -> String {
51+ let t = text. trim ( ) ;
52+ if t. ends_with ( '.' ) || t. ends_with ( '!' ) || t. ends_with ( '?' ) {
53+ t. to_string ( )
54+ } else {
55+ format ! ( "{}." , t)
56+ }
57+ }
58+
59+ fn sanitize ( text : & str ) -> String {
60+ text. replace ( "__###newline###__" , " " )
61+ . split_whitespace ( )
62+ . collect :: < Vec < _ > > ( )
63+ . join ( " " )
64+ }
65+
66+ fn is_category_label ( text : & str ) -> bool {
67+ let lower = text. to_lowercase ( ) ;
68+ let word_count = text. split_whitespace ( ) . count ( ) ;
69+ if word_count < 5 {
70+ return true ;
71+ }
72+ let verb_indicators = [
73+ " is " ,
74+ " are " ,
75+ " was " ,
76+ " were " ,
77+ " has " ,
78+ " have " ,
79+ " can " ,
80+ " will " ,
81+ " does " ,
82+ " do " ,
83+ " provides " ,
84+ " supports " ,
85+ " describes " ,
86+ " represents " ,
87+ " enables " ,
88+ " includes " ,
89+ " spans " ,
90+ " emphasizing " ,
91+ " provided " ,
92+ ] ;
93+ let has_verb = verb_indicators. iter ( ) . any ( |& v| lower. contains ( v) ) ;
94+ if !has_verb {
95+ return true ;
96+ }
97+ let category_patterns = [
98+ "programming languages" ,
99+ "software using" ,
100+ "free software" ,
101+ "license" ,
102+ "category" ,
103+ ] ;
104+ category_patterns. iter ( ) . any ( |& p| lower. contains ( p) )
105+ }
106+
107+ fn strip_topic_prefix ( text : & str ) -> String {
108+ if let Some ( dash_pos) = text. find ( " - " ) {
109+ let after = text[ dash_pos + 3 ..] . trim ( ) ;
110+ if after. split_whitespace ( ) . count ( ) >= 5 {
111+ return after. to_string ( ) ;
112+ }
113+ }
114+ text. to_string ( )
115+ }
116+
50117pub fn corpus_from_results ( results : & [ LiteSearchResult ] ) -> String {
51118 results
52119 . iter ( )
53- . map ( |r| {
120+ . filter_map ( |r| {
54121 let mut parts: Vec < String > = Vec :: new ( ) ;
55- if !r. title . is_empty ( ) {
56- let mut title = r. title . trim ( ) . to_string ( ) ;
57- if !title. ends_with ( '.' ) && !title. ends_with ( '!' ) && !title. ends_with ( '?' ) {
58- title. push ( '.' ) ;
59- }
60- parts. push ( title) ;
122+ let title = r. title . trim ( ) ;
123+ if !title. is_empty ( ) && !title. contains ( '|' ) && title. split_whitespace ( ) . count ( ) >= 3 {
124+ parts. push ( ensure_terminal_punct ( title) ) ;
125+ }
126+ let snippet = r. snippet . trim ( ) ;
127+ if !snippet. is_empty ( )
128+ && !snippet. contains ( '|' )
129+ && snippet. split_whitespace ( ) . count ( ) >= 7
130+ {
131+ parts. push ( ensure_terminal_punct ( snippet) ) ;
61132 }
62- if !r. snippet . is_empty ( ) {
63- let mut snippet = r. snippet . trim ( ) . to_string ( ) ;
64- if !snippet. ends_with ( '.' ) && !snippet. ends_with ( '!' ) && !snippet. ends_with ( '?' ) {
65- snippet. push ( '.' ) ;
66- }
67- parts. push ( snippet) ;
133+ if parts. is_empty ( ) {
134+ None
135+ } else {
136+ Some ( parts. join ( " " ) )
68137 }
69- parts. join ( " " )
70138 } )
71- . filter ( |s| !s. is_empty ( ) )
72139 . collect :: < Vec < _ > > ( )
73140 . join ( " " )
74141}
75142
76- pub fn corpus_from_response ( resp : & Response ) -> String {
77- let mut parts = Vec :: new ( ) ;
78-
79- let mut add_part = |text : & str | {
80- let trimmed = text. trim ( ) ;
81- if !trimmed. is_empty ( ) {
82- let mut content = trimmed. to_string ( ) ;
83- if !content. ends_with ( '.' ) && !content. ends_with ( '!' ) && !content. ends_with ( '?' ) {
84- content. push ( '.' ) ;
143+ pub fn corpus_from_results_raw ( results : & [ LiteSearchResult ] ) -> String {
144+ results
145+ . iter ( )
146+ . filter_map ( |r| {
147+ let mut parts: Vec < String > = Vec :: new ( ) ;
148+ let snippet = r. snippet . trim ( ) ;
149+ if !snippet. is_empty ( ) && !snippet. contains ( '|' ) {
150+ parts. push ( ensure_terminal_punct ( snippet) ) ;
85151 }
86- parts. push ( content) ;
87- }
88- } ;
152+ let title = r. title . trim ( ) ;
153+ if !title. is_empty ( ) && !title. contains ( '|' ) && parts. is_empty ( ) {
154+ parts. push ( ensure_terminal_punct ( title) ) ;
155+ }
156+ if parts. is_empty ( ) {
157+ None
158+ } else {
159+ Some ( parts. join ( " " ) )
160+ }
161+ } )
162+ . collect :: < Vec < _ > > ( )
163+ . join ( " " )
164+ }
165+
166+ pub fn corpus_from_response ( resp : & Response ) -> String {
167+ let mut parts: Vec < String > = Vec :: new ( ) ;
89168
90169 if let Some ( abstract_text) = & resp. abstract_text {
91- add_part ( abstract_text) ;
170+ let t = sanitize ( abstract_text) ;
171+ if !t. is_empty ( ) {
172+ parts. push ( ensure_terminal_punct ( & t) ) ;
173+ }
92174 }
175+
93176 if let Some ( answer) = & resp. answer {
94- add_part ( answer) ;
177+ let t = sanitize ( answer) ;
178+ if !t. is_empty ( ) {
179+ parts. push ( ensure_terminal_punct ( & t) ) ;
180+ }
95181 }
182+
96183 if let Some ( definition) = & resp. definition {
97- add_part ( definition) ;
184+ let t = sanitize ( definition) ;
185+ if !t. is_empty ( ) {
186+ parts. push ( ensure_terminal_punct ( & t) ) ;
187+ }
98188 }
99189
100- for topic in resp. related_topics . iter ( ) . take ( 10 ) {
101- if let Some ( text) = & topic. text
102- && text. split_whitespace ( ) . count ( ) >= 5
103- {
104- add_part ( text) ;
190+ for topic in resp. related_topics . iter ( ) . take ( 15 ) {
191+ if let Some ( raw_text) = & topic. text {
192+ let cleaned = strip_topic_prefix ( & sanitize ( raw_text) ) ;
193+ if !is_category_label ( & cleaned) {
194+ parts. push ( ensure_terminal_punct ( & cleaned) ) ;
195+ }
105196 }
106197 }
107198
108199 parts. join ( " " )
109200}
110201
111202pub fn seed_from_results ( query : & str , results : & [ LiteSearchResult ] ) -> String {
203+ let stopwords = [
204+ "the" , "and" , "for" , "with" , "that" , "this" , "from" , "what" , "how" , "are" , "was" , "were" ,
205+ "will" , "have" , "been" , "they" ,
206+ ] ;
112207 let topic_words: Vec < String > = results
113208 . iter ( )
114209 . flat_map ( |r| r. title . split_whitespace ( ) . map ( str:: to_string) )
115210 . filter ( |w| {
116211 let low = w. to_lowercase ( ) ;
117- w. len ( ) > 3
118- && !matches ! (
119- low. as_str( ) ,
120- "the"
121- | "and"
122- | "for"
123- | "with"
124- | "that"
125- | "this"
126- | "from"
127- | "what"
128- | "how"
129- | "are"
130- | "was"
131- | "were"
132- | "will"
133- )
212+ w. len ( ) > 3 && !stopwords. contains ( & low. as_str ( ) )
134213 } )
135214 . take ( 6 )
136215 . collect ( ) ;
0 commit comments