@@ -10,6 +10,8 @@ import { handleQuestion } from '../chat/messageHandler';
1010import { clearChatHistory } from '../chat/chatHistory' ;
1111import { modelManager } from '../../utils/modelManager' ;
1212import { initializeLanguage , t } from '../../utils/i18n' ;
13+ import { estimateSimpleTokens , getTokenCountWithStatus , type TokenEstimate } from '../../utils/tokenCounter' ;
14+ import { simpleTokenEstimate , getSimpleTokenStatus } from '../../utils/simpleTokenCounter' ;
1315
1416interface Position {
1517 x : number ;
@@ -320,6 +322,10 @@ export async function createSidebar(): Promise<void> {
320322 <div id="ai-answer"></div>
321323 <div class="ai-input-section">
322324 <textarea id="ai-question" placeholder="${ t ( 'sidebar.askPlaceholder' ) } " rows="4"></textarea>
325+ <div class="ai-token-indicator" id="ai-token-indicator">
326+ <span class="ai-token-count" id="ai-token-count">0 tokens</span>
327+ <span class="ai-token-status" id="ai-token-status"></span>
328+ </div>
323329 <div class="ai-bottom-controls">
324330 <button id="ai-ask-button">${ t ( 'sidebar.askButton' ) } </button>
325331 <select id="ai-model-selector" class="ai-model-selector"></select>
@@ -380,6 +386,58 @@ function setupEventListeners(): void {
380386 initializeModelSelector ( modelSelector ) ;
381387 }
382388
389+ // Token counting function
390+ async function updateTokenIndicator ( ) : Promise < void > {
391+ const question = questionInput ?. value . trim ( ) || '' ;
392+ const selectedModel = modelSelector ?. value as ModelType || 'gpt-4o-mini' ;
393+ const tokenCount = document . getElementById ( 'ai-token-count' ) ;
394+ const tokenStatus = document . getElementById ( 'ai-token-status' ) ;
395+ const tokenIndicator = document . getElementById ( 'ai-token-indicator' ) ;
396+
397+ if ( ! tokenCount || ! tokenStatus || ! tokenIndicator ) return ;
398+
399+ // if (!question) {
400+ // tokenCount.textContent = '0 tokens';
401+ // tokenStatus.textContent = '';
402+ // tokenIndicator.className = 'ai-token-indicator';
403+ // return;
404+ // }
405+
406+ // Show loading state
407+ tokenCount . textContent = 'Counting...' ;
408+ tokenStatus . textContent = '' ;
409+ tokenIndicator . className = 'ai-token-indicator' ;
410+
411+ try {
412+ const content = getPageContent ( ) ;
413+
414+ // Try tiktoken first, fallback to simple estimation
415+ try {
416+ const estimate = await estimateSimpleTokens ( question , content , selectedModel ) ;
417+ const { text, status } = getTokenCountWithStatus ( estimate ) ;
418+
419+ tokenCount . textContent = text ;
420+ tokenStatus . textContent = estimate . warning || '' ;
421+ tokenIndicator . className = `ai-token-indicator ai-token-${ status } ` ;
422+ } catch ( tiktokenError ) {
423+ console . warn ( 'Tiktoken failed, using simple estimation:' , tiktokenError ) ;
424+
425+ // Fallback to simple estimation
426+ const estimate = simpleTokenEstimate ( question , content , selectedModel ) ;
427+ const { text, status } = getSimpleTokenStatus ( estimate ) ;
428+
429+ tokenCount . textContent = text ;
430+ tokenStatus . textContent = estimate . warning || 'Using estimated count' ;
431+ tokenIndicator . className = `ai-token-indicator ai-token-${ status } ` ;
432+ }
433+ } catch ( error ) {
434+ console . warn ( 'All token counting methods failed:' , error ) ;
435+ tokenCount . textContent = 'Token count unavailable' ;
436+ tokenStatus . textContent = 'Error loading token counter' ;
437+ tokenIndicator . className = 'ai-token-indicator' ;
438+ }
439+ }
440+
383441 // Set initial theme
384442 const prefersDark = window . matchMedia ( '(prefers-color-scheme: dark)' ) . matches ;
385443 let currentTheme : Theme = ( localStorage . getItem ( 'theme' ) as Theme ) || ( prefersDark ? 'dark' : 'light' ) ;
@@ -439,6 +497,31 @@ function setupEventListeners(): void {
439497 }
440498 } ) ;
441499
500+ // Update token count when question changes
501+ questionInput ?. addEventListener ( 'input' , debouncedTokenUpdate ) ;
502+
503+ // Update token count when model changes
504+ modelSelector ?. addEventListener ( 'change' , debouncedTokenUpdate ) ;
505+
506+ // Debounce function to prevent too many rapid token updates
507+ let tokenUpdateTimeout : number | undefined ;
508+ function debouncedTokenUpdate ( ) {
509+ if ( tokenUpdateTimeout ) {
510+ clearTimeout ( tokenUpdateTimeout ) ;
511+ }
512+ tokenUpdateTimeout = window . setTimeout ( ( ) => {
513+ updateTokenIndicator ( ) ;
514+ } , 100 ) ; // 100ms debounce
515+ }
516+
517+ // Update token count when context changes
518+ document . addEventListener ( 'contextUpdate' , debouncedTokenUpdate ) ;
519+
520+ // Initial token count update
521+ setTimeout ( ( ) => {
522+ debouncedTokenUpdate ( ) ;
523+ } , 200 ) ;
524+
442525 // Clear chat confirmation modal
443526 const confirmButton = modal ?. querySelector ( '.ai-confirm-button' ) ;
444527 const cancelButton = modal ?. querySelector ( '.ai-cancel-button' ) ;
0 commit comments