@@ -46,26 +46,42 @@ export class AIService {
46
46
*/
47
47
public async generateComment ( prompt : string ) : Promise < string > {
48
48
try {
49
- // Truncate the prompt if it contains code blocks
50
- let finalPrompt = this . truncateCodeBlock ( prompt ) ;
49
+ // First try with generous limit
50
+ let finalPrompt = this . truncateCodeBlock ( prompt , 8000 ) ;
51
51
52
52
// Only append language instruction if not English
53
- if ( this . configuration . language . toLowerCase ( ) !== 'english' ) {
53
+ const normalizedLanguage = this . configuration . language . toLowerCase ( ) . trim ( ) ;
54
+ if ( normalizedLanguage !== 'english' ) {
54
55
finalPrompt += `\n\nEverything except the JSDoc conventions and code should be in ${ this . configuration . language } ` ;
55
56
}
56
57
57
58
console . log ( `Generating comment for prompt of length: ${ finalPrompt . length } ` ) ;
58
59
59
- const response = await this . chatModel . invoke ( finalPrompt ) ;
60
- return response . content as string ;
61
- } catch ( error ) {
62
- if ( error instanceof Error && error . message . includes ( 'maximum context length' ) ) {
63
- console . warn ( 'Token limit exceeded, attempting with further truncation...' ) ;
64
- // Try again with more aggressive truncation
65
- const truncatedPrompt = this . truncateCodeBlock ( prompt , 4000 ) ;
66
- const response = await this . chatModel . invoke ( truncatedPrompt ) ;
60
+ try {
61
+ const response = await this . chatModel . invoke ( finalPrompt ) ;
67
62
return response . content as string ;
63
+ } catch ( error ) {
64
+ if ( error instanceof Error && error . message . includes ( 'maximum context length' ) ) {
65
+ console . warn ( 'Token limit exceeded, attempting with further truncation...' ) ;
66
+ // Try with more aggressive truncation
67
+ finalPrompt = this . truncateCodeBlock ( prompt , 4000 ) ;
68
+ try {
69
+ const response = await this . chatModel . invoke ( finalPrompt ) ;
70
+ return response . content as string ;
71
+ } catch ( retryError ) {
72
+ if ( retryError instanceof Error && retryError . message . includes ( 'maximum context length' ) ) {
73
+ console . warn ( 'Still exceeding token limit, using minimal context...' ) ;
74
+ // Final attempt with minimal context
75
+ finalPrompt = this . truncateCodeBlock ( prompt , 2000 ) ;
76
+ const response = await this . chatModel . invoke ( finalPrompt ) ;
77
+ return response . content as string ;
78
+ }
79
+ throw retryError ;
80
+ }
81
+ }
82
+ throw error ;
68
83
}
84
+ } catch ( error ) {
69
85
this . handleAPIError ( error as Error ) ;
70
86
return '' ;
71
87
}
@@ -250,16 +266,15 @@ export class AIService {
250
266
const fileGroups = this . groupDocsByFile ( docs ) ;
251
267
const sections : string [ ] = [ ] ;
252
268
253
- // Generate documentation for each file without individual intros
254
269
for ( const fileGroup of fileGroups ) {
255
- const fileDoc = await this . generateFileApiDoc ( fileGroup ) ;
256
- if ( fileDoc . trim ( ) ) {
257
- sections . push ( fileDoc ) ;
258
- }
270
+ const fileDoc = await this . generateFileApiDoc ( fileGroup ) ;
271
+ if ( fileDoc . trim ( ) ) {
272
+ sections . push ( fileDoc ) ;
273
+ }
259
274
}
260
275
261
- return sections . join ( '\n\n ' ) ;
262
- }
276
+ return sections . join ( '\n' ) ;
277
+ }
263
278
264
279
/**
265
280
* Generates troubleshooting guide based on documentation and common patterns
@@ -462,68 +477,86 @@ export class AIService {
462
477
private async generateFileApiDoc ( fileGroup : FileDocsGroup ) : Promise < string > {
463
478
const filePath = this . formatFilePath ( fileGroup . filePath ) ;
464
479
const formattedDocs = this . formatApiComponents ( fileGroup ) ;
465
- return formattedDocs ? `### ${ filePath } \n\n${ formattedDocs } ` : '' ;
480
+ // Add TypeScript code block for the file path to indicate it's a TypeScript module
481
+ return formattedDocs ? `### File: \`${ filePath } \`\n${ formattedDocs } ` : '' ;
466
482
}
467
483
468
484
private formatApiComponents ( fileGroup : FileDocsGroup ) : string {
469
485
const sections : string [ ] = [ ] ;
470
486
471
487
// Classes
472
488
if ( fileGroup . classes . length > 0 ) {
473
- sections . push ( '#### Classes\n ' ) ;
489
+ sections . push ( '#### Classes' ) ;
474
490
fileGroup . classes . forEach ( c => {
475
- sections . push ( `##### ${ c . name } \n ` ) ;
476
- if ( c . jsDoc ) sections . push ( `\`\`\`\n ${ c . jsDoc } \n\`\`\`\n` ) ;
491
+ sections . push ( `##### \` ${ c . name } \` ` ) ;
492
+ if ( c . jsDoc ) sections . push ( this . formatJSDoc ( c . jsDoc , c . code ) ) ;
477
493
478
494
// Add any methods belonging to this class
479
495
const classMethods = fileGroup . methods . filter ( m => m . className === c . name ) ;
480
496
if ( classMethods . length > 0 ) {
481
- sections . push ( 'Methods:\n ' ) ;
497
+ sections . push ( '** Methods:** ' ) ;
482
498
classMethods . forEach ( m => {
483
- sections . push ( `* \`${ m . name } \`\n \`\`\`\n ${ m . jsDoc || '' } \n \`\`\`\n ` ) ;
499
+ sections . push ( `###### \`${ m . name } \`${ m . jsDoc ? `\n ${ this . formatJSDoc ( m . jsDoc , m . code ) } ` : '' } ` ) ;
484
500
} ) ;
485
501
}
486
502
} ) ;
487
503
}
488
504
489
505
// Interfaces
490
506
if ( fileGroup . interfaces . length > 0 ) {
491
- sections . push ( '#### Interfaces\n ' ) ;
507
+ sections . push ( '#### Interfaces' ) ;
492
508
fileGroup . interfaces . forEach ( i => {
493
- sections . push ( `##### ${ i . name } \n ` ) ;
494
- if ( i . jsDoc ) sections . push ( `\`\`\`\n ${ i . jsDoc } \n\`\`\`\n` ) ;
509
+ sections . push ( `##### \` ${ i . name } \` ` ) ;
510
+ if ( i . jsDoc ) sections . push ( this . formatJSDoc ( i . jsDoc , i . code ) ) ;
495
511
} ) ;
496
512
}
497
513
498
514
// Types
499
515
if ( fileGroup . types . length > 0 ) {
500
- sections . push ( '#### Types\n ' ) ;
516
+ sections . push ( '#### Types' ) ;
501
517
fileGroup . types . forEach ( t => {
502
- sections . push ( `##### ${ t . name } \n ` ) ;
503
- if ( t . jsDoc ) sections . push ( `\`\`\`\n ${ t . jsDoc } \n\`\`\`\n` ) ;
518
+ sections . push ( `##### \` ${ t . name } \` ` ) ;
519
+ if ( t . jsDoc ) sections . push ( this . formatJSDoc ( t . jsDoc , t . code ) ) ;
504
520
} ) ;
505
521
}
506
522
507
- // Standalone Functions (not class methods)
523
+ // Standalone Functions
508
524
if ( fileGroup . functions . length > 0 ) {
509
- sections . push ( '#### Functions\n ' ) ;
525
+ sections . push ( '#### Functions' ) ;
510
526
fileGroup . functions . forEach ( f => {
511
- sections . push ( `##### ${ f . name } \n ` ) ;
512
- if ( f . jsDoc ) sections . push ( `\`\`\`\n ${ f . jsDoc } \n\`\`\`\n` ) ;
527
+ sections . push ( `##### \` ${ f . name } \` ` ) ;
528
+ if ( f . jsDoc ) sections . push ( this . formatJSDoc ( f . jsDoc , f . code ) ) ;
513
529
} ) ;
514
530
}
515
531
516
- // Standalone Methods (not belonging to any class)
532
+ // Standalone Methods
517
533
const standaloneMethods = fileGroup . methods . filter ( m => ! m . className ) ;
518
534
if ( standaloneMethods . length > 0 ) {
519
- sections . push ( '#### Methods\n ' ) ;
535
+ sections . push ( '#### Methods' ) ;
520
536
standaloneMethods . forEach ( m => {
521
- sections . push ( `##### ${ m . name } \n ` ) ;
522
- if ( m . jsDoc ) sections . push ( `\`\`\`\n ${ m . jsDoc } \n\`\`\`\n` ) ;
537
+ sections . push ( `##### \` ${ m . name } \` ` ) ;
538
+ if ( m . jsDoc ) sections . push ( this . formatJSDoc ( m . jsDoc , m . code ) ) ;
523
539
} ) ;
524
540
}
525
541
526
- return sections . join ( '\n' ) ;
542
+ return sections . join ( '\n\n' ) ;
543
+ }
544
+
545
+ private formatJSDoc ( jsDoc : string , code ?: string ) : string {
546
+ // Clean up the JSDoc
547
+ let cleanDoc = jsDoc . replace ( / ^ ` ` ` \s * \n ? / gm, '' ) . replace ( / \n ? ` ` ` \s * $ / gm, '' ) ;
548
+ cleanDoc = cleanDoc . trim ( ) . replace ( / \n { 3 , } / g, '\n\n' ) ;
549
+
550
+ // Format JSDoc with typescript declaration
551
+ const docSection = '```typescript\n' + cleanDoc + '\n```' ;
552
+
553
+ // If we have the actual code, include it after the JSDoc
554
+ // if (code) {
555
+ // const cleanCode = code.trim().replace(/^```\s*\n?/gm, '').replace(/\n?```\s*$/gm, '');
556
+ // return `${docSection}\n\n**Implementation:**\n\n\`\`\`typescript\n${cleanCode}\n\`\`\``;
557
+ // }
558
+
559
+ return docSection ;
527
560
}
528
561
529
562
private formatComponents ( fileGroup : FileDocsGroup ) : string {
@@ -576,27 +609,57 @@ export class AIService {
576
609
const codeBlockRegex = / ` ` ` [ \s \S ] * ?` ` ` / g;
577
610
const codeBlocks = code . match ( codeBlockRegex ) || [ ] ;
578
611
612
+ // If no code blocks found, truncate the text directly
613
+ if ( codeBlocks . length === 0 ) {
614
+ return code . slice ( 0 , maxLength ) + '... (truncated)' ;
615
+ }
616
+
617
+ // Calculate maximum length per block to stay under total limit
618
+ const nonCodeLength = code . replace ( codeBlockRegex , '' ) . length ;
619
+ const maxLengthPerBlock = Math . floor ( ( maxLength - nonCodeLength ) / codeBlocks . length ) ;
620
+
579
621
for ( let i = 0 ; i < codeBlocks . length ; i ++ ) {
580
622
const block = codeBlocks [ i ] ;
581
- if ( block . length > maxLength ) {
582
- // Keep the opening and closing parts of the code block
623
+ if ( block . length > maxLengthPerBlock ) {
583
624
const lines = block . split ( '\n' ) ;
584
625
const header = lines [ 0 ] ; // Keep the ```typescript or similar
585
626
const footer = lines [ lines . length - 1 ] ; // Keep the closing ```
586
627
587
- // Take the first few lines of actual code
588
- const codeStart = lines . slice ( 1 , Math . floor ( maxLength / 60 ) ) . join ( '\n' ) ;
589
- // Take some lines from the middle
628
+ // Calculate how many lines we can keep
629
+ const maxLinesPerSection = Math . floor ( ( maxLengthPerBlock - header . length - footer . length ) / 3 ) ;
630
+
631
+ // Take fewer lines but ensure we get the most important parts
632
+ const codeStart = lines . slice ( 1 , maxLinesPerSection ) . join ( '\n' ) ;
633
+
634
+ // For the middle section, focus on the important parts
590
635
const middleIndex = Math . floor ( lines . length / 2 ) ;
591
- const codeMiddle = lines . slice ( middleIndex - 2 , middleIndex + 2 ) . join ( '\n' ) ;
592
- // Take the last few lines
593
- const codeEnd = lines . slice ( lines . length - Math . floor ( maxLength / 60 ) , - 1 ) . join ( '\n' ) ;
636
+ const middleStart = Math . max ( maxLinesPerSection , middleIndex - Math . floor ( maxLinesPerSection / 2 ) ) ;
637
+ const middleEnd = Math . min ( lines . length - maxLinesPerSection , middleIndex + Math . floor ( maxLinesPerSection / 2 ) ) ;
638
+ const codeMiddle = lines . slice ( middleStart , middleEnd ) . join ( '\n' ) ;
594
639
595
- const truncatedBlock = `${ header } \n${ codeStart } \n\n// ... truncated ...\n\n${ codeMiddle } \n\n// ... truncated ...\n\n${ codeEnd } \n${ footer } ` ;
640
+ // Take the end section
641
+ const codeEnd = lines . slice ( lines . length - maxLinesPerSection , - 1 ) . join ( '\n' ) ;
642
+
643
+ const truncatedBlock = `${ header } \n${ codeStart } \n// ... truncated [${ lines . length - ( maxLinesPerSection * 2 ) } lines] ...\n${ codeMiddle } \n// ... truncated ...\n${ codeEnd } \n${ footer } ` ;
596
644
code = code . replace ( block , truncatedBlock ) ;
597
645
}
598
646
}
599
647
648
+ // Final safety check - if still too long, do a hard truncate
649
+ if ( code . length > maxLength ) {
650
+ const blocks = code . split ( '```' ) ;
651
+ const truncatedBlocks = blocks . map ( ( block , index ) => {
652
+ // Every odd index is a code block
653
+ if ( index % 2 === 1 ) {
654
+ const lines = block . split ( '\n' ) ;
655
+ const maxLines = 10 ; // Keep only first few lines of each block
656
+ return lines . slice ( 0 , maxLines ) . join ( '\n' ) + '\n// ... remaining code truncated ...\n' ;
657
+ }
658
+ return block . slice ( 0 , 500 ) ; // Limit non-code text
659
+ } ) ;
660
+ code = truncatedBlocks . join ( '```' ) ;
661
+ }
662
+
600
663
return code ;
601
664
}
602
665
}
0 commit comments