@@ -166,6 +166,7 @@ class CopilotTokenTracker implements vscode.Disposable {
166166 private maturityPanel : vscode . WebviewPanel | undefined ;
167167 private dashboardPanel : vscode . WebviewPanel | undefined ;
168168 private fluencyLevelViewerPanel : vscode . WebviewPanel | undefined ;
169+ private environmentalPanel : vscode . WebviewPanel | undefined ;
169170 private outputChannel : vscode . OutputChannel ;
170171 private lastDetailedStats : DetailedStats | undefined ;
171172 private lastDailyStats : DailyTokenStats [ ] | undefined ;
@@ -672,6 +673,25 @@ class CopilotTokenTracker implements vscode.Disposable {
672673 this . maturityPanel . webview . html = this . getMaturityHtml ( this . maturityPanel . webview , maturityData ) ;
673674 }
674675
676+ // If the environmental panel is open, update its content
677+ if ( this . environmentalPanel ) {
678+ if ( silent ) {
679+ void this . environmentalPanel . webview . postMessage ( {
680+ command : 'updateStats' ,
681+ data : {
682+ today : detailedStats . today ,
683+ month : detailedStats . month ,
684+ lastMonth : detailedStats . lastMonth ,
685+ last30Days : detailedStats . last30Days ,
686+ lastUpdated : detailedStats . lastUpdated . toISOString ( ) ,
687+ backendConfigured : this . isBackendConfigured ( ) ,
688+ } ,
689+ } ) ;
690+ } else {
691+ this . environmentalPanel . webview . html = this . getEnvironmentalHtml ( this . environmentalPanel . webview , detailedStats ) ;
692+ }
693+ }
694+
675695 this . log ( `Updated stats - Today: ${ detailedStats . today . tokens } , Last 30 Days: ${ detailedStats . last30Days . tokens } ` ) ;
676696 // Store the stats for reuse without recalculation
677697 this . lastDetailedStats = detailedStats ;
@@ -738,7 +758,7 @@ class CopilotTokenTracker implements vscode.Disposable {
738758 const now = new Date ( ) ;
739759 const todayStart = new Date ( now . getFullYear ( ) , now . getMonth ( ) , now . getDate ( ) ) ;
740760 const monthStart = new Date ( now . getFullYear ( ) , now . getMonth ( ) , 1 ) ;
741- // Calculate last month boundaries
761+ // Calculate Previous Month boundaries
742762 const lastMonthEnd = new Date ( now . getFullYear ( ) , now . getMonth ( ) , 0 , 23 , 59 , 59 , 999 ) ; // Last day of previous month
743763 const lastMonthStart = new Date ( lastMonthEnd . getFullYear ( ) , lastMonthEnd . getMonth ( ) , 1 ) ;
744764 // Calculate last 30 days boundary
@@ -930,22 +950,22 @@ class CopilotTokenTracker implements vscode.Disposable {
930950 }
931951 }
932952 else if ( lastActivity >= lastMonthStart && lastActivity <= lastMonthEnd ) {
933- // Session is from last month - only track lastMonth stats
953+ // Session is from Previous Month - only track lastMonth stats
934954 lastMonthStats . tokens += tokens ;
935955 lastMonthStats . estimatedTokens += estimatedTokens ;
936956 lastMonthStats . actualTokens += actualTokens ;
937957 lastMonthStats . thinkingTokens += ( sessionData . thinkingTokens || 0 ) ;
938958 lastMonthStats . sessions += 1 ;
939959 lastMonthStats . interactions += interactions ;
940960
941- // Add editor usage to last month stats
961+ // Add editor usage to Previous Month stats
942962 if ( ! lastMonthStats . editorUsage [ editorType ] ) {
943963 lastMonthStats . editorUsage [ editorType ] = { tokens : 0 , sessions : 0 } ;
944964 }
945965 lastMonthStats . editorUsage [ editorType ] . tokens += tokens ;
946966 lastMonthStats . editorUsage [ editorType ] . sessions += 1 ;
947967
948- // Add model usage to last month stats
968+ // Add model usage to Previous Month stats
949969 for ( const [ model , usage ] of Object . entries ( modelUsage ) ) {
950970 if ( ! lastMonthStats . modelUsage [ model ] ) {
951971 lastMonthStats . modelUsage [ model ] = { inputTokens : 0 , outputTokens : 0 } ;
@@ -963,7 +983,7 @@ class CopilotTokenTracker implements vscode.Disposable {
963983 }
964984 }
965985
966- this . log ( `✅ Analysis complete: Today ${ todayStats . sessions } sessions, Month ${ monthStats . sessions } sessions, Last 30 Days ${ last30DaysStats . sessions } sessions, Last Month ${ lastMonthStats . sessions } sessions` ) ;
986+ this . log ( `✅ Analysis complete: Today ${ todayStats . sessions } sessions, Month ${ monthStats . sessions } sessions, Last 30 Days ${ last30DaysStats . sessions } sessions, Previous Month ${ lastMonthStats . sessions } sessions` ) ;
967987 if ( skippedFiles > 0 ) {
968988 this . log ( `⏭️ Skipped ${ skippedFiles } session file(s) (empty or no activity in recent months)` ) ;
969989 }
@@ -3051,6 +3071,9 @@ class CopilotTokenTracker implements vscode.Disposable {
30513071 case 'showDashboard' :
30523072 await this . showDashboard ( ) ;
30533073 break ;
3074+ case 'showEnvironmental' :
3075+ await this . showEnvironmental ( ) ;
3076+ break ;
30543077 case 'saveSortSettings' :
30553078 await this . context . globalState . update ( 'details.sortSettings' , message . settings ) ;
30563079 break ;
@@ -3064,6 +3087,108 @@ class CopilotTokenTracker implements vscode.Disposable {
30643087 } ) ;
30653088 }
30663089
3090+ public async showEnvironmental ( ) : Promise < void > {
3091+ this . log ( '🌿 Opening Environmental Impact view' ) ;
3092+
3093+ if ( this . environmentalPanel ) {
3094+ this . environmentalPanel . reveal ( ) ;
3095+ this . log ( '🌿 Environmental Impact view revealed (already exists)' ) ;
3096+ return ;
3097+ }
3098+
3099+ let stats = this . lastDetailedStats ;
3100+ if ( ! stats ) {
3101+ stats = await this . updateTokenStats ( ) ;
3102+ if ( ! stats ) {
3103+ return ;
3104+ }
3105+ }
3106+
3107+ this . environmentalPanel = vscode . window . createWebviewPanel (
3108+ 'copilotEnvironmental' ,
3109+ 'Environmental Impact' ,
3110+ { viewColumn : vscode . ViewColumn . One , preserveFocus : true } ,
3111+ {
3112+ enableScripts : true ,
3113+ retainContextWhenHidden : false ,
3114+ localResourceRoots : [ vscode . Uri . joinPath ( this . extensionUri , 'dist' , 'webview' ) ]
3115+ }
3116+ ) ;
3117+
3118+ this . environmentalPanel . webview . html = this . getEnvironmentalHtml ( this . environmentalPanel . webview , stats ) ;
3119+
3120+ this . environmentalPanel . webview . onDidReceiveMessage ( async ( message ) => {
3121+ switch ( message . command ) {
3122+ case 'refresh' : {
3123+ const refreshed = await this . updateTokenStats ( ) ;
3124+ if ( refreshed && this . environmentalPanel ) {
3125+ this . environmentalPanel . webview . html = this . getEnvironmentalHtml ( this . environmentalPanel . webview , refreshed ) ;
3126+ }
3127+ break ;
3128+ }
3129+ case 'showDetails' :
3130+ await this . showDetails ( ) ;
3131+ break ;
3132+ case 'showChart' :
3133+ await this . showChart ( ) ;
3134+ break ;
3135+ case 'showUsageAnalysis' :
3136+ await this . showUsageAnalysis ( ) ;
3137+ break ;
3138+ case 'showDiagnostics' :
3139+ await this . showDiagnosticReport ( ) ;
3140+ break ;
3141+ case 'showMaturity' :
3142+ await this . showMaturity ( ) ;
3143+ break ;
3144+ case 'showDashboard' :
3145+ await this . showDashboard ( ) ;
3146+ break ;
3147+ }
3148+ } ) ;
3149+
3150+ this . environmentalPanel . onDidDispose ( ( ) => {
3151+ this . log ( '🌿 Environmental Impact view closed' ) ;
3152+ this . environmentalPanel = undefined ;
3153+ } ) ;
3154+ }
3155+
3156+ private getEnvironmentalHtml ( webview : vscode . Webview , stats : DetailedStats ) : string {
3157+ const nonce = this . getNonce ( ) ;
3158+ const scriptUri = webview . asWebviewUri (
3159+ vscode . Uri . joinPath ( this . extensionUri , 'dist' , 'webview' , 'environmental.js' )
3160+ ) ;
3161+
3162+ const csp = [
3163+ `default-src 'none'` ,
3164+ `img-src ${ webview . cspSource } https: data:` ,
3165+ `style-src 'unsafe-inline' ${ webview . cspSource } ` ,
3166+ `font-src ${ webview . cspSource } https: data:` ,
3167+ `script-src 'nonce-${ nonce } '` ,
3168+ ] . join ( '; ' ) ;
3169+
3170+ const dataWithBackend = {
3171+ ...stats ,
3172+ backendConfigured : this . isBackendConfigured ( ) ,
3173+ } ;
3174+ const initialData = JSON . stringify ( dataWithBackend ) . replace ( / < / g, '\\u003c' ) ;
3175+
3176+ return `<!DOCTYPE html>
3177+ <html lang="en">
3178+ <head>
3179+ <meta charset="UTF-8" />
3180+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
3181+ <meta http-equiv="Content-Security-Policy" content="${ csp } " />
3182+ <title>Environmental Impact</title>
3183+ </head>
3184+ <body>
3185+ <div id="root"></div>
3186+ <script nonce="${ nonce } ">window.__INITIAL_ENVIRONMENTAL__ = ${ initialData } ;</script>
3187+ <script nonce="${ nonce } " src="${ scriptUri } "></script>
3188+ </body>
3189+ </html>` ;
3190+ }
3191+
30673192 public async showChart ( ) : Promise < void > {
30683193 this . log ( '📈 Opening Chart view' ) ;
30693194
@@ -3118,6 +3243,9 @@ class CopilotTokenTracker implements vscode.Disposable {
31183243 case 'showDashboard' :
31193244 await this . showDashboard ( ) ;
31203245 break ;
3246+ case 'showEnvironmental' :
3247+ await this . showEnvironmental ( ) ;
3248+ break ;
31213249 }
31223250 } ) ;
31233251
@@ -3182,6 +3310,9 @@ class CopilotTokenTracker implements vscode.Disposable {
31823310 case 'showDashboard' :
31833311 await this . showDashboard ( ) ;
31843312 break ;
3313+ case 'showEnvironmental' :
3314+ await this . showEnvironmental ( ) ;
3315+ break ;
31853316 case 'analyseRepository' :
31863317 await this . handleAnalyseRepository ( message . workspacePath ) ;
31873318 break ;
@@ -3891,6 +4022,9 @@ Return ONLY the JSON object, no markdown formatting, no explanations.`;
38914022 case 'showDashboard' :
38924023 await this . showDashboard ( ) ;
38934024 break ;
4025+ case 'showEnvironmental' :
4026+ await this . showEnvironmental ( ) ;
4027+ break ;
38944028 case 'shareToLinkedIn' :
38954029 await this . shareToSocialMedia ( 'linkedin' ) ;
38964030 break ;
@@ -4591,6 +4725,9 @@ ${hashtag}`;
45914725 case "showMaturity" :
45924726 await this . showMaturity ( ) ;
45934727 break ;
4728+ case "showEnvironmental" :
4729+ await this . showEnvironmental ( ) ;
4730+ break ;
45944731 case "deleteUserDataset" :
45954732 await this . handleDeleteUserDataset ( message . userId , message . datasetId ) ;
45964733 break ;
@@ -5681,6 +5818,9 @@ ${hashtag}`;
56815818 case "showDashboard" :
56825819 await this . showDashboard ( ) ;
56835820 break ;
5821+ case "showEnvironmental" :
5822+ await this . showEnvironmental ( ) ;
5823+ break ;
56845824 case "resetDebugCounters" :
56855825 await this . context . globalState . update ( 'extension.openCount' , 0 ) ;
56865826 await this . context . globalState . update ( 'extension.unknownMcpOpenCount' , 0 ) ;
@@ -6529,6 +6669,14 @@ export function activate(context: vscode.ExtensionContext) {
65296669 } ,
65306670 ) ;
65316671
6672+ const showEnvironmentalCommand = vscode . commands . registerCommand (
6673+ "copilot-token-tracker.showEnvironmental" ,
6674+ async ( ) => {
6675+ tokenTracker . log ( "Show environmental impact command called" ) ;
6676+ await tokenTracker . showEnvironmental ( ) ;
6677+ } ,
6678+ ) ;
6679+
65326680 // Register the show fluency level viewer command (debug-only)
65336681 const showFluencyLevelViewerCommand = vscode . commands . registerCommand (
65346682 "copilot-token-tracker.showFluencyLevelViewer" ,
@@ -6565,6 +6713,7 @@ export function activate(context: vscode.ExtensionContext) {
65656713 showMaturityCommand ,
65666714 showFluencyLevelViewerCommand ,
65676715 showDashboardCommand ,
6716+ showEnvironmentalCommand ,
65686717 generateDiagnosticReportCommand ,
65696718 clearCacheCommand ,
65706719 tokenTracker ,
0 commit comments