@@ -44,10 +44,10 @@ import {
4444 FiShare2 ,
4545} from 'react-icons/fi'
4646import { Link , useNavigate } from 'react-router-dom'
47- import MessageText from '../../components/slack/MessageText'
4847import ErrorBoundary from '../../components/common/ErrorBoundary'
4948import { useAnalysisData } from '../../hooks'
5049import { ServiceResource } from '../../lib/integrationService'
50+ import { renderPlainText , extractSectionContent , isObviouslyNotJson } from '../../utils/textRenderer'
5151
5252interface ChannelAnalysisListProps {
5353 title : string
@@ -73,148 +73,6 @@ const ChannelAnalysisList: FC<ChannelAnalysisListProps> = ({
7373 emptyMessage = 'No information available.' ,
7474} ) => {
7575 const navigate = useNavigate ( )
76- const renderPlainText = (
77- text : string | unknown ,
78- workspaceUuid : string | undefined
79- ) => {
80- const textStr = typeof text === 'string' ? text : String ( text || '' )
81- if ( ! textStr || textStr . trim ( ) . length === 0 ) {
82- return < Text color = "gray.500" > No content available</ Text >
83- }
84-
85- let cleanedText = textStr
86-
87- if ( / ^ \s * \{ \s * \} \s * $ / . test ( cleanedText ) ) {
88- return < Text color = "gray.500" > No content available</ Text >
89- }
90-
91- cleanedText = cleanedText . replace ( / \\ n / g, '\n' )
92-
93- const isLikelyPlainText =
94- / ^ [ A - Z a - z ] / . test ( cleanedText . trim ( ) ) &&
95- ! cleanedText . includes ( '```json' ) &&
96- ! ( cleanedText . trim ( ) . startsWith ( '{' ) && cleanedText . trim ( ) . endsWith ( '}' ) )
97-
98- if ( isLikelyPlainText ) {
99- return (
100- < Box className = "formatted-text" >
101- { cleanedText . split ( '\n' ) . map ( ( paragraph , index ) => (
102- < Box key = { index } mb = { 2 } >
103- { paragraph . trim ( ) ? (
104- < MessageText
105- text = { paragraph }
106- workspaceUuid = { workspaceUuid ?? '' }
107- resolveMentions = { true }
108- fallbackToSimpleFormat = { true }
109- />
110- ) : (
111- < Box height = "0.7em" />
112- ) }
113- </ Box >
114- ) ) }
115- </ Box >
116- )
117- }
118-
119- if (
120- cleanedText . includes ( '{' ) &&
121- cleanedText . includes ( '}' ) &&
122- cleanedText . includes ( '"' )
123- ) {
124- try {
125- const contentMatch = cleanedText . match ( / " [ ^ " ] + " \s * : \s * " ( [ ^ " ] * ) " / )
126- if ( contentMatch && contentMatch [ 1 ] ) {
127- cleanedText = contentMatch [ 1 ] . replace ( / \\ n / g, '\n' )
128- } else {
129- cleanedText = cleanedText
130- . replace ( / [ { } " ] / g, '' ) // Remove braces and quotes
131- . replace ( / [ \w _ ] + \s * : / g, '' ) // Remove field names
132- . replace ( / , \s * / g, '\n' ) // Replace commas with newlines
133- . trim ( )
134- }
135- } catch ( e ) {
136- console . warn ( 'Error cleaning text content:' , e )
137- }
138- }
139-
140- const hasMarkdownHeaders = / ^ # + \s + .+ $ / m. test ( cleanedText )
141-
142- return (
143- < Box className = "formatted-text" >
144- { cleanedText . split ( '\n' ) . map ( ( paragraph , index ) => {
145- if ( ! paragraph . trim ( ) ) {
146- return < Box key = { index } height = "0.7em" />
147- }
148-
149- if ( hasMarkdownHeaders && / ^ ( # + ) \s + ( .+ ) $ / . test ( paragraph ) ) {
150- const match = paragraph . match ( / ^ ( # + ) \s + ( .+ ) $ / )
151- if ( match ) {
152- const level = match [ 1 ] . length
153- const headerText = match [ 2 ]
154-
155- const isTabHeader = [
156- 'Summary' ,
157- 'Topics' ,
158- 'Contributors' ,
159- 'Highlights' ,
160- ] . some ( ( tab ) =>
161- headerText . toLowerCase ( ) . includes ( tab . toLowerCase ( ) )
162- )
163-
164- if ( isTabHeader ) {
165- return < Box key = { index } height = "0.5em" /> // Skip this header
166- }
167-
168- const size = level === 1 ? 'lg' : level === 2 ? 'md' : 'sm'
169- return (
170- < Heading
171- as = { `h${ Math . min ( level , 6 ) } ` as React . ElementType }
172- size = { size }
173- mt = { 4 }
174- mb = { 2 }
175- key = { index }
176- >
177- { headerText }
178- </ Heading >
179- )
180- }
181- }
182-
183- if (
184- paragraph . trim ( ) . startsWith ( '- ' ) ||
185- paragraph . trim ( ) . startsWith ( '* ' )
186- ) {
187- return (
188- < Box key = { index } mb = { 2 } pl = { 4 } display = "flex" >
189- < Box as = "span" mr = { 2 } >
190- •
191- </ Box >
192- < Box flex = "1" >
193- < MessageText
194- text = { paragraph . trim ( ) . substring ( 2 ) }
195- workspaceUuid = { workspaceUuid ?? '' }
196- resolveMentions = { true }
197- fallbackToSimpleFormat = { true }
198- />
199- </ Box >
200- </ Box >
201- )
202- }
203-
204- return (
205- < Box key = { index } mb = { 2 } >
206- < MessageText
207- text = { paragraph }
208- workspaceUuid = { workspaceUuid ?? '' }
209- resolveMentions = { true }
210- fallbackToSimpleFormat = { true }
211- />
212- </ Box >
213- )
214- } ) }
215- </ Box >
216- )
217- }
21876
21977 const filteredAnalyses =
22078 reportResult ?. resource_analyses &&
@@ -490,153 +348,7 @@ Generated using Toban Contribution Viewer with ${modelUsed}
490348 } )
491349 }
492350
493- /**
494- * Render plain text with proper formatting and support for markdown-like syntax
495- */
496- const renderPlainText = ( text : string | unknown , workspace_uuid : string ) => {
497- const textStr = typeof text === 'string' ? text : String ( text || '' )
498- if ( ! textStr || textStr . trim ( ) . length === 0 ) {
499- return < Text color = "gray.500" > No content available</ Text >
500- }
501351
502- let cleanedText = textStr
503-
504- if ( / ^ \s * \{ \s * \} \s * $ / . test ( cleanedText ) ) {
505- return < Text color = "gray.500" > No content available</ Text >
506- }
507-
508- cleanedText = cleanedText . replace ( / \\ n / g, '\n' )
509-
510- const isLikelyPlainText =
511- / ^ [ A - Z a - z ] / . test ( cleanedText . trim ( ) ) &&
512- ! cleanedText . includes ( '```json' ) &&
513- ! ( cleanedText . trim ( ) . startsWith ( '{' ) && cleanedText . trim ( ) . endsWith ( '}' ) )
514-
515- if ( isLikelyPlainText ) {
516- console . log ( 'Content appears to be plain text, rendering directly' )
517- return (
518- < Box className = "formatted-text" >
519- { cleanedText . split ( '\n' ) . map ( ( paragraph , index ) => (
520- < Box key = { index } mb = { 2 } >
521- { paragraph . trim ( ) ? (
522- < MessageText
523- text = { paragraph }
524- resolveMentions = { true }
525- fallbackToSimpleFormat = { true }
526- workspaceUuid = { workspace_uuid ?? '' }
527- />
528- ) : (
529- < Box height = "0.7em" />
530- ) }
531- </ Box >
532- ) ) }
533- </ Box >
534- )
535- }
536-
537- if (
538- cleanedText . includes ( '{' ) &&
539- cleanedText . includes ( '}' ) &&
540- cleanedText . includes ( '"' )
541- ) {
542- try {
543- const contentMatch = cleanedText . match ( / " [ ^ " ] + " \s * : \s * " ( [ ^ " ] * ) " / )
544- if ( contentMatch && contentMatch [ 1 ] ) {
545- cleanedText = contentMatch [ 1 ] . replace ( / \\ n / g, '\n' )
546- } else {
547- cleanedText = cleanedText
548- . replace ( / [ { } " ] / g, '' ) // Remove braces and quotes
549- . replace ( / [ \w _ ] + \s * : / g, '' ) // Remove field names
550- . replace ( / , \s * / g, '\n' ) // Replace commas with newlines
551- . trim ( )
552- }
553- } catch ( e ) {
554- console . warn ( 'Error cleaning text content:' , e )
555- }
556- }
557-
558- const hasMarkdownHeaders = / ^ # + \s + .+ $ / m. test ( cleanedText )
559-
560- return (
561- < Box className = "formatted-text" >
562- { cleanedText . split ( '\n' ) . map ( ( paragraph , index ) => {
563- if ( ! paragraph . trim ( ) ) {
564- return < Box key = { index } height = "0.7em" />
565- }
566-
567- if ( hasMarkdownHeaders && / ^ ( # + ) \s + ( .+ ) $ / . test ( paragraph ) ) {
568- const match = paragraph . match ( / ^ ( # + ) \s + ( .+ ) $ / )
569- if ( match ) {
570- const level = match [ 1 ] . length
571- const headerText = match [ 2 ]
572-
573- const isTabHeader = [
574- 'Summary' ,
575- 'Topics' ,
576- 'Contributors' ,
577- 'Highlights' ,
578- ] . some ( ( tab ) =>
579- headerText . toLowerCase ( ) . includes ( tab . toLowerCase ( ) )
580- )
581-
582- if ( isTabHeader ) {
583- return < Box key = { index } height = "0.5em" /> // Skip this header
584- }
585-
586- const size = level === 1 ? 'lg' : level === 2 ? 'md' : 'sm'
587- return (
588- < Heading
589- as = { `h${ Math . min ( level , 6 ) } ` as React . ElementType }
590- size = { size }
591- mt = { 4 }
592- mb = { 2 }
593- key = { index }
594- >
595- { headerText }
596- </ Heading >
597- )
598- }
599- }
600-
601- if (
602- paragraph . trim ( ) . startsWith ( '- ' ) ||
603- paragraph . trim ( ) . startsWith ( '* ' )
604- ) {
605- return (
606- < Box key = { index } mb = { 2 } pl = { 4 } display = "flex" >
607- < Box as = "span" mr = { 2 } >
608- •
609- </ Box >
610- < Box flex = "1" >
611- < MessageText
612- text = { paragraph . trim ( ) . substring ( 2 ) }
613- resolveMentions = { true }
614- fallbackToSimpleFormat = { true }
615- workspaceUuid = { workspace_uuid ?? '' }
616- />
617- </ Box >
618- </ Box >
619- )
620- }
621-
622- return (
623- < Box key = { index } mb = { 2 } >
624- < MessageText
625- text = { paragraph }
626- resolveMentions = { true }
627- fallbackToSimpleFormat = { true }
628- workspaceUuid = { workspace_uuid ?? '' }
629- />
630- </ Box >
631- )
632- } ) }
633- </ Box >
634- )
635- }
636-
637- const isObviouslyNotJson = ( str : string ) => {
638- return ! str . includes ( '{' ) && ! str . includes ( '"' ) && ! str . includes ( ':' )
639- }
640352
641353 const fixedAnalysis = analysis
642354 ? {
@@ -667,14 +379,6 @@ Generated using Toban Contribution Viewer with ${modelUsed}
667379 fixedAnalysis . contributor_insights
668380 }
669381
670- const extractSectionContent = ( text : string , sectionName : string ) => {
671- const regex = new RegExp (
672- `#+\\s*${ sectionName } \\s*\\n([\\s\\S]*?)(?=#+\\s*|$)` ,
673- 'i'
674- )
675- const match = text . match ( regex )
676- return match ? match [ 1 ] . trim ( ) : ''
677- }
678382
679383 if ( analysis && fixedAnalysis ) {
680384 if (
0 commit comments