Skip to content

Custom Extensions

Learn from example extensions built with Anouk.


Twitter/X Reply Assistant

An extension that helps compose thoughtful replies on Twitter/X.

Features

  • Analyze tweet context and thread
  • Generate reply suggestions
  • Adjust tone (professional, casual, witty)
  • Character count awareness

Implementation

import { AIService } from 'anouk';

class TwitterReplyAssistant {
  constructor() {
    this.aiService = new AIService({
      provider: 'together',
      model: 'meta-llama/Llama-3-70b-chat-hf'
    });

    this.observeTweets();
  }

  observeTweets() {
    // Watch for reply box focus
    document.addEventListener('focusin', (e) => {
      if (this.isReplyBox(e.target)) {
        this.showAssistant(e.target);
      }
    });
  }

  isReplyBox(element) {
    return element.matches('[data-testid="tweetTextarea_0"]');
  }

  async generateReplies(tweetContent) {
    const tones = ['professional', 'casual', 'witty'];
    const replies = {};

    for (const tone of tones) {
      replies[tone] = await this.aiService.call(
        `Generate a ${tone} reply to this tweet.
         Keep it under 280 characters.
         Be authentic and engaging.`,
        tweetContent,
        this.getTweetId(),
        `reply_${tone}`
      );
    }

    return replies;
  }

  showAssistant(replyBox) {
    // Get parent tweet content
    const tweet = this.getParentTweet(replyBox);
    const tweetContent = tweet?.textContent;

    if (!tweetContent) return;

    // Create floating assistant
    const assistant = document.createElement('div');
    assistant.className = 'twitter-reply-assistant';
    assistant.innerHTML = `
      <div class="assistant-header">
        <span>Reply Assistant</span>
        <button class="generate-btn">Generate Replies</button>
      </div>
      <div class="suggestions"></div>
    `;

    // Position near reply box
    replyBox.parentElement.appendChild(assistant);

    // Generate on click
    assistant.querySelector('.generate-btn').onclick = async () => {
      const suggestions = assistant.querySelector('.suggestions');
      suggestions.innerHTML = '<p>Generating...</p>';

      const replies = await this.generateReplies(tweetContent);

      suggestions.innerHTML = Object.entries(replies)
        .map(([tone, text]) => `
          <div class="suggestion" data-tone="${tone}">
            <span class="tone-label">${tone}</span>
            <p>${text}</p>
            <button class="use-btn">Use</button>
          </div>
        `).join('');

      // Insert on click
      suggestions.querySelectorAll('.use-btn').forEach(btn => {
        btn.onclick = () => {
          const text = btn.parentElement.querySelector('p').textContent;
          this.insertReply(replyBox, text);
        };
      });
    };
  }

  insertReply(replyBox, text) {
    replyBox.focus();
    document.execCommand('insertText', false, text);
  }
}

new TwitterReplyAssistant();

Code Review Assistant

Browser extension for GitHub that provides AI-powered code review suggestions.

Features

  • Analyze pull request diffs
  • Suggest improvements
  • Identify potential bugs
  • Check for security issues

Implementation

import { AIService } from 'anouk';

class CodeReviewAssistant {
  constructor() {
    this.aiService = new AIService({
      provider: 'openai',
      model: 'gpt-4',
      systemPrompt: `You are an expert code reviewer.
        Focus on: bugs, security issues, performance,
        code quality, and best practices.
        Be constructive and specific.`
    });

    this.init();
  }

  init() {
    // Check if on GitHub PR page
    if (this.isGitHubPR()) {
      this.addReviewButton();
    }

    // Watch for navigation
    this.observeNavigation();
  }

  isGitHubPR() {
    return window.location.pathname.includes('/pull/');
  }

  addReviewButton() {
    const actionsBar = document.querySelector('.pr-review-tools');
    if (!actionsBar) return;

    const button = document.createElement('button');
    button.className = 'btn btn-primary ml-2';
    button.textContent = 'AI Review';
    button.onclick = () => this.performReview();

    actionsBar.appendChild(button);
  }

  async performReview() {
    const diffs = this.extractDiffs();

    for (const diff of diffs) {
      const review = await this.aiService.call(
        `Review this code change. Identify:
         1. Potential bugs or errors
         2. Security vulnerabilities
         3. Performance issues
         4. Code quality improvements

         Format as actionable comments.`,
        diff.content,
        diff.file,
        'review'
      );

      this.displayReview(diff.file, review);
    }
  }

  extractDiffs() {
    const diffContainers = document.querySelectorAll('.file');
    return Array.from(diffContainers).map(container => ({
      file: container.querySelector('.file-header').textContent,
      content: container.querySelector('.blob-code-content').textContent
    }));
  }

  displayReview(file, review) {
    const fileHeader = Array.from(document.querySelectorAll('.file-header'))
      .find(h => h.textContent.includes(file));

    if (!fileHeader) return;

    const reviewPanel = document.createElement('div');
    reviewPanel.className = 'ai-review-panel';
    reviewPanel.innerHTML = `
      <div class="review-header">
        <strong>AI Review</strong>
      </div>
      <div class="review-content">
        ${this.formatReview(review)}
      </div>
    `;

    fileHeader.parentElement.insertBefore(
      reviewPanel,
      fileHeader.nextSibling
    );
  }

  formatReview(review) {
    return review
      .split('\n')
      .map(line => `<p>${line}</p>`)
      .join('');
  }
}

new CodeReviewAssistant();

Meeting Notes Summarizer

Extension for Google Meet that generates meeting summaries.

Features

  • Capture meeting transcript
  • Generate summary and action items
  • Create follow-up email draft
  • Export to various formats

Implementation

import { AIService } from 'anouk';

class MeetingNotesSummarizer {
  constructor() {
    this.aiService = new AIService({
      provider: 'openai',
      model: 'gpt-4-turbo',
      maxTokens: 4000
    });

    this.transcript = [];
    this.init();
  }

  init() {
    if (this.isGoogleMeet()) {
      this.addUI();
      this.observeTranscript();
    }
  }

  isGoogleMeet() {
    return window.location.hostname === 'meet.google.com';
  }

  addUI() {
    const button = document.createElement('button');
    button.id = 'summarize-meeting';
    button.textContent = 'Summarize';
    button.onclick = () => this.generateSummary();

    // Add to Meet toolbar
    const toolbar = document.querySelector('[data-meeting-toolbar]');
    if (toolbar) {
      toolbar.appendChild(button);
    }
  }

  observeTranscript() {
    // Watch for caption updates
    const observer = new MutationObserver((mutations) => {
      for (const mutation of mutations) {
        if (mutation.addedNodes.length) {
          this.captureCaption(mutation.addedNodes);
        }
      }
    });

    const captionsContainer = document.querySelector('[data-captions]');
    if (captionsContainer) {
      observer.observe(captionsContainer, { childList: true });
    }
  }

  captureCaption(nodes) {
    nodes.forEach(node => {
      if (node.textContent) {
        this.transcript.push({
          time: new Date().toISOString(),
          text: node.textContent
        });
      }
    });
  }

  async generateSummary() {
    const transcriptText = this.transcript
      .map(t => t.text)
      .join('\n');

    const summary = await this.aiService.call(
      `Summarize this meeting transcript:

       1. Key Discussion Points (bullet points)
       2. Decisions Made
       3. Action Items (with assignees if mentioned)
       4. Follow-up Required

       Be concise but comprehensive.`,
      transcriptText,
      `meeting_${Date.now()}`,
      'summary'
    );

    this.showSummary(summary);
  }

  showSummary(summary) {
    const modal = document.createElement('div');
    modal.className = 'summary-modal';
    modal.innerHTML = `
      <div class="modal-content">
        <h2>Meeting Summary</h2>
        <div class="summary-text">${summary}</div>
        <div class="modal-actions">
          <button id="copy-summary">Copy</button>
          <button id="email-summary">Draft Email</button>
          <button id="close-modal">Close</button>
        </div>
      </div>
    `;

    document.body.appendChild(modal);

    modal.querySelector('#copy-summary').onclick = () => {
      navigator.clipboard.writeText(summary);
    };

    modal.querySelector('#email-summary').onclick = () => {
      this.draftFollowUpEmail(summary);
    };

    modal.querySelector('#close-modal').onclick = () => {
      modal.remove();
    };
  }

  async draftFollowUpEmail(summary) {
    const email = await this.aiService.call(
      `Draft a professional follow-up email based on this meeting summary.
       Include action items and next steps.`,
      summary,
      `email_${Date.now()}`,
      'email'
    );

    // Open in Gmail compose
    const gmailUrl = `https://mail.google.com/mail/?view=cm&body=${encodeURIComponent(email)}`;
    window.open(gmailUrl, '_blank');
  }
}

new MeetingNotesSummarizer();

Extension Ideas

Here are more ideas for Anouk-powered extensions:

Extension Use Case
LinkedIn Optimizer Improve profile and posts
Slack Summarizer Summarize channel activity
YouTube Notes Generate video summaries
News Analyzer Fact-check and summarize articles
Form Filler Auto-fill applications with AI
Language Tutor Real-time translation and learning
Shopping Assistant Product comparisons and reviews
Recipe Parser Extract and format recipes

Best Practices

Performance

  • Use caching aggressively
  • Debounce user interactions
  • Lazy load AI features

UX

  • Show loading states
  • Provide clear error messages
  • Allow easy dismissal of UI

Privacy

  • Don't log sensitive data
  • Let users control what's analyzed
  • Use local models when possible