DEV Community

Bhdrpkcn
Bhdrpkcn

Posted on

Chrome Built-in AI Challenge: Building an Extension with Gemini Nano AI That Changes Faster Than Me

dinoMind

When I signed up for this project, I thought I’d just be "the UI guy." You know, design some buttons, style some layouts, maybe sprinkle in some functionality. But it turns out that wasn’t the plan at all. But as it turns out, my friends and teammates “a senior front-end wizard doubling as the team lead and my mentor, and a brilliant AI dev with a penchant for solving problems before I even knew they existed” weren’t just in this challenge for the glory. They joined with the goal of helping me grow and evolve as a developer. Their idea of “helping,” though, was to let me dive headfirst into new environments, TypeScript, Chrome extension development, and Gemini Nano AI integration, all while they offered their guidance (and occasional “gentle” nudges). Spoiler alert: The "UI guy" joke quickly became a running gag.

The challenge wasn’t just technical—it was personal, too. Despite getting thumbs-up from dev teams, my non-traditional background and age meant most HR systems seemed to think I wasn’t cut out for the job. But here’s the thing: while HR said no, my teammates said, “Let’s prove them wrong.” And so, I did. Well, at least I tried.

As I learned, my teammates provided invaluable mentorship. The senior front-end dev offered expertise in UI architecture, state management, and avoiding the occasional meltdown. Meanwhile, the AI developer stepped in to help me wrestle with Gemini Nano AI and its constantly shifting capabilities. And me? I was the junior dev, learning TypeScript on the fly, configuring Chrome’s overly strict permissions, and occasionally chasing down bugs that Gemini Nano AI seemed to create as it evolved faster than I could code.

Together, we built something truly collaborative—and chaotic. While they were steady hands, I was the eager learner trying to keep up. In the end, this wasn’t just a submission for the Chrome Built-in AI Challenge. It became a symbol of teamwork, mentorship, and proving that I’m capable of more than just building UIs. And now, I know for sure: with the right team behind you, even the most impossible-seeming projects are just opportunities to grow—while making some hilarious memories along the way.

Setting the Stage

> Hackathon Challenge Analysis

Chrome's Built-in AI Challenge wasn’t just about building a simple extension—it was about leveraging cutting-edge AI capabilities directly integrated into Chrome. The mission? To create something innovative, functional, and user-friendly that showcases the potential of Gemini Nano AI, Google’s built-in AI model. The catch? The AI itself was still evolving, and Chrome’s strict permissions and APIs weren’t exactly forgiving. This wasn’t just a technical challenge—it was a race to learn, adapt, and deliver something impactful in a short timeframe.

> From Idea to Blueprint: What’s the Extension’s Core Logic?

Our initial idea started simple: make YouTube comment sections better. The goal was to use Gemini Nano AI to analyze, summarize, and categorize comments—filtering spam, highlighting insightful discussions, and providing summaries to improve user interactions.
But as the project progressed, the idea evolved into something far more ambitious. The extension shifted focus from merely enhancing YouTube comments to a personalized content generation tool that understands user preferences and tastes.
Here’s the twist: we decided to tie this concept to Chrome’s "Lonely Dino", chrome’s famous offline game mascot. By integrating Gemini Nano AI, the dino could not only entertain users during internet outages but also generate content tailored to their interests using their browsing history (with permissions, of course). Think of it as a futuristic, humor-infused Chrome mascot that bridges AI-driven personalization with offline entertainment.
At its core, the evolved extension aims to:

  • Integrate a sidepanel UI that provides users with real-time insights and interactions, without altering or injecting elements directly into content hubs like YouTube.
  • Leverage Gemini Nano AI to analyze user history and dynamically generate relevant, engaging content.
  • Provide a playful yet functional experience where the Lonely Dino becomes a content creator, offering recommendations and insights—even offline.

Behind the playful facade of a future Chrome’s Lonely Dino lies a sophisticated recommendation engine built on AI. This concept reimagines how AI can interact with users, blending humor, utility, and cutting-edge technology into a single, cohesive experience.

> Scoping the Challenge: Evaluating Chrome APIs and Gemini Nano AI Capabilities

Before we could dive in, we needed to understand the tools at our disposal:

  • Chrome APIs: These offered the backbone for our extension, enabling us to manipulate the browser environment, inject content scripts, and manage permissions. However, Chrome’s strict manifest v3 rules and sandboxed environments meant we had to navigate complex permission structures and limited capabilities. Chrome API Documentation
  • Gemini Nano AI API: This was our AI powerhouse, but it came with its quirks. The AI’s evolving nature meant that endpoints, response formats, and even capabilities could shift during development. This added an unpredictable layer of complexity, forcing us to build with flexibility in mind.
    Gemini Nano API Documentation
    > What Tech We Had to Use

    To bring our concept to life, we carefully chose a tech stack that balanced modernity and functionality:

  • React: The go-to library for building the extension’s user interface.

  • Vite: A fast, lightweight build tool that streamlined development.

  • CRXJS: A specialized framework for building Chrome extensions, saving us from reinventing the wheel.
    CRXJS

  • Redux: Essential for managing complex state logic across components, particularly for the side panel and injected UI.

  • Observer Logic: This allowed our content script to communicate with Chrome’s APIs and background processes, enabling seamless data flow between the extension and Gemini Nano AI.

This stack wasn’t just a set of tools—it was a learning curve for me, especially as I navigated TypeScript integration, Chrome’s unique development environment, and the complexities of bridging UI with backend processes.

Steps to Enable Gemini Nano AI

  • Download Chrome Dev (Latest Version) The starting point was installing the latest version of Chrome Dev, which supports the experimental features needed for Gemini Nano AI.
  • Enable Flags From chrome://flags, the following features needed to be enabled:
    • Optimization Guide on Device (Enable Bypass PerfR)
    • Prompt API for Gemini Nano
    • WebGPU Developer Features
  • Restart the System After enabling the flags, a good old-fashioned PC restart was necessary to apply the changes.
  • Check for Optimization Guide on Device Model
    Navigate to chrome://components and check if the Optimization Guide on Device Model is listed. If not, move to the next step.

    • Trigger the Model Download via Console
    • Open Chrome Dev Console and run the following commands:
      await ai.assistant.create();
      await window.ai.summarizer.create();
    
    • These commands are supposed to trigger the Optimization Guide setup, even though they return undefined. The goal here was to nudge Chrome into action.
  • Quit and Restart Chrome
    After running the commands, restart Chrome again and revisit chrome://components to check if the Optimization Guide on Device Model appears. If it does, hit Check for Update to download the necessary AI model.

  • Ensure Ample Disk Space
    The download requires at least 22 GB of free disk space, (but mainly takes 1.5 to 2 GB) with the files stored under:
    google/chrome/user data/OptGuideOnDeviceModel

What should have been a straightforward process quickly turned into a series of headaches:

  • Triggering Issues: At some point, Chrome stopped allowing the console commands to trigger the download, leaving us with no way to verify if it was even downloading.
  • No Progress Indicators: There was no feedback on the download's size, progress, or even confirmation that it was happening at all. We were flying blind.
  • Constant Updates: Chrome Dev and Chrome Canary rolled out frequent updates, often breaking our progress and forcing us to reconfigure or troubleshoot features.

It felt like we were part of an unspoken challenge: “How many times can a dev restart Chrome before losing their mind?” The process combined equal parts detective work, guesswork, and a strong internet connection.
But despite the frustrations, these steps were critical to getting Gemini Nano AI up and running in the app. If nothing else, it taught us patience.

Building the Playground

Building the first playground for our extension was a mix of aha moments, trial-and-error, and Dino-related hilarity. Here’s how the journey unfolded:

1. Redux Meets Chrome Extension: Observing the Chaos

Using Redux in a Chrome extension is already challenging, but when you throw observer logic into the mix, it becomes a Rubik’s Cube with no instructions. The goal was to manage the state effectively while ensuring that closing the sidepanel also cleaned up any running processes (like Gemini Nano AI calls).

Here’s the catch:
> Redux in the background.js: The Redux store had to be built and maintained in background.js instead of a typical frontend environment. Why? Because the background.js file acts as a persistent environment for handling state across different parts of the extension.

import { configureStore } from "@reduxjs/toolkit";
import rootReducer from "./redux/rootReducer";
import { createWrapStore } from "webext-redux";
// Redux setup
const store = configureStore({
 reducer: rootReducer,
});
const wrapStore = createWrapStore();
Enter fullscreen mode Exit fullscreen mode

> Closing the Sidepanel: When the user closed the sidepanel, it wasn’t just about hiding the UI—we also had to ensure saving chat sessions and contents to local storage and any running functions were gracefully terminated. No zombies allowed!

// Handle messages for opening/closing the side panel
chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
 if (message.action === "openSidePanel") {
   chrome.sidePanel.setOptions({ path: "sidepanel.html", enabled: true });


   chrome.sidePanel
     .setPanelBehavior({ openPanelOnActionClick: true })
     .catch((error) => console.error(error));


   // chrome.sidePanel.open();
   store.dispatch(openSidePanel());
   sendResponse({ status: "success", isOpen: true });
 } else if (message.action === "closeSidePanel") {
   chrome.sidePanel.setOptions({ enabled: false });
   store.dispatch(saveChatHistory());
   store.dispatch(closeSidePanel());
   sendResponse({ status: "success", isOpen: false });
 }
});
Enter fullscreen mode Exit fullscreen mode

Let’s just say Redux became the traffic controller of our playground, ensuring the AI and sidepanel worked harmoniously.

2. Content Script + Sidepanel: The Pros and Cons

The sidepanel was our central hub for interactions, and using it alongside content_scripts had clear advantages:
Direct Interaction: It allowed the extension to communicate with the browser environment without altering web pages directly.
Custom Permissions: This approach gave us the flexibility to request specific permissions as needed (though Chrome’s permissions system made this anything but smooth).
However, the cons were just as prominent:
Trial Tokens: Accessing Gemini Nano AI required trial tokens, and managing these within the constraints of a Chrome extension was a constant juggling act.

<!DOCTYPE html>
<html lang="en">
 <head>
   <meta charset="UTF-8" />
   <meta
     http-equiv="origin-trial"
     content="TRIAL_TOKEN_KEY_GOES_HERE"
   />
   <title>Side Panel</title>
 </head>
 <body>
   <div id="sidepanel-root"></div>
   <script type="module" src="/src/layout/sidepanel.tsx"></script>
 </body>
</html>
Enter fullscreen mode Exit fullscreen mode

Junior Perspective Frustrations: The documentation on Chrome permissions and content_scripts wasn’t exactly written with beginners in mind. It often felt like solving a riddle, where the answer was “read it five more times, and maybe you’ll get it.”

3. A Prompt for a Laugh: Tiny Wins with AI

Getting Gemini Nano AI to generate single-line jokes felt like a huge milestone. Sure, it wasn’t groundbreaking, but at this point, we’d take every win we could get. Watching the AI crack a joke—even a bad one—felt like progress. Plus, it set the stage for bigger things to come.

4. Dino’s New Role: Running for the User’s Content
Through all of this, we never lost sight of the Lonely Dino. Originally running for its life from meteors, the Dino’s mission evolved:

  • Now, it runs to fetch and generate content for the beloved user.
  • It’s more than just a mascot—it’s a quirky embodiment of the extension’s purpose: entertaining, engaging, and helping users even when offline. We drew inspiration from this excellent CodePen Animation by Nick Spiel, which provided a great foundation for pixel animations. From there, we adapted the Dino to not just survive, but thrive, with a little help from Gemini Nano AI.

This playground wasn’t just about testing ideas—it was about learning to manage chaos. Redux wasn’t just Redux anymore—it was a background state wizard. The sidepanel wasn’t just a UI—it was a gateway to permissions hell. And the Dino? Well, it wasn’t just running from meteors—it was running for its life (and ours).
But through the struggles, we built something special: a foundation for a smarter, more engaging Chrome extension that’s both functional and, dare we say, fun.

"From Chaos to Structure: Lessons from Google’s Chromium Group, TypeScript’s Double-Edged Sword, and Building a Smarter Dino"

> Big Help from Google’s Chrome AI Dev Preview Group

At this stage, I owe a huge shoutout to the Google Chrome Built-in AI Early Preview Program discussion group. This community was invaluable. Turns out, I wasn’t the only one wrestling with AI integration quirks and Chrome’s strict requirements.
From basic setup queries to complex troubleshooting, the group tackled every question—no matter how simple, repetitive, or head-scratchingly difficult—with patience and professionalism. A big thanks to the Chrome dev team for their fast, detailed responses and for creating a space where developers like me could find clarity in the chaos. Cudos to them for helping us all feel less alone in the trenches!

Refactoring with a Mentor’s Vision

By now, we had a good structure of working functions and components, but “good” wasn’t enough. My senior front-end mentor stepped in with a critical eye, showing me how to refactor and elevate the code.
This wasn’t just about cleaning up—it was about optimizing for scalability, readability, and efficiency. With their guidance, we:
**Refactored setter-fetcher hooks **to handle AI data more effectively.

Fetcher function example :

import { ComponentType } from "../types/componentType";
import { promptConfig } from "./config/promptConfig";


import { handleError } from "./error/errorHandler";


let session: any | null = null;
let clonedSession: any | null = null;


const controller = new AbortController();


export const fetchGeminiResponse = async (
 userMessage: string,
 component: ComponentType,
 summary?: string
): Promise<string> => {
 if (!userMessage.trim()) {
   return handleError("No data available to generate content data.", {
     fallbackValue: "Error: No input provided.",
   });
 }


 const { promptTemplate } = promptConfig[component];
 let prompt: string;
 const isChatbox = component === "chatbox";
 const isContentChat = component === "contentChat";


 try {
   if (isChatbox) {
     prompt = promptTemplate.replace("{userMessage}", userMessage);
   } else if (isContentChat) {
     prompt =
       promptTemplate
         ?.replace("{summary}", summary || "No summary provided")
         .replace("{userMessage}", userMessage) || "";
   } else {
     prompt = promptTemplate?.replace("{userMessage}", userMessage) || "";
   }


   if (!window.ai || !window.ai.languageModel) {
     return handleError("Gemini Nano is not available in this browser.", {
       fallbackValue: "Error: AI service unavailable.",
     });
   }


   if (!session) {
     session = await window.ai.languageModel.create({
       temperature: 0.7,
       topK: 3,
     });
   }


   if (!clonedSession) {
     clonedSession = await session.clone({ signal: controller.signal });
   }


   const stream = await clonedSession.promptStreaming(prompt, {
     signal: controller.signal,
   });


   let responseText = "";
   let previousChunk = "";


   for await (const chunk of stream) {
     const newChunk = chunk.startsWith(previousChunk)
       ? chunk.slice(previousChunk.length)
       : chunk;
     responseText += newChunk;
     previousChunk = chunk;
   }


   return responseText;
 } catch (error) {
   return handleError(error, {
     logToConsole: true,
     fallbackValue: "Error: Could not reach the AI service.",
   });
 }
};
Enter fullscreen mode Exit fullscreen mode

Setter function example :

import { useState } from "react";
import { fetchGeminiResponse } from "../utils/fetchGeminiResponse";
import { promptConfig } from "../utils/config/promptConfig";
import { Message, Sender } from "../types/messageType";
import { ComponentType, ComponentTypeEnum } from "../types/componentType";
import { saveContentChatData } from "@/utils/dataUtils";


export const useGeminiResponse = () => {
 const [loading, setLoading] = useState(false);
 const [messages, setMessages] = useState<Message[]>([]);


 const fetchResponse = async (
   userMessage: string,
   component: ComponentType,
   id?: string,
   summary?: string
 ) => {
   setLoading(true);


   try {
     const responseText = await fetchGeminiResponse(
       userMessage,
       component,
       summary
     );


     const { saveData } = promptConfig[component];


     if (component === ComponentTypeEnum.Chatbox) {
       const aiMessage: Message = { sender: Sender.AI, text: responseText };
       setMessages((prevMessages) => [
         ...prevMessages.filter((msg) => msg.sender !== Sender.AI),
         aiMessage,
       ]);
       (saveData as (data: Message) => void)(aiMessage);
     } else if (component === ComponentTypeEnum.ContentChat) {
       const aiMessage: Message = { sender: Sender.AI, text: responseText };
       setMessages((prevMessages) => [
         ...prevMessages.filter((msg) => msg.sender !== Sender.AI),
         aiMessage,
       ]);
       saveContentChatData(id, aiMessage);
     } else {
       (saveData as (data: string[]) => void)([responseText]);
     }


     return responseText;
   } catch (error) {
     console.error("Error in fetchResponse:", error);
     return "An error occurred while fetching the response.";
   } finally {
     setLoading(false);
   }
 };


 return { loading, messages, fetchResponse };
};
Enter fullscreen mode Exit fullscreen mode
  • Developed multi-layered functions to interact with Gemini Nano AI APIs, ensuring they worked seamlessly together.
  • Streamlined workflows for tasks like summarizing history data, generating personalized content, and creating interest tags—all while maintaining a coherent user experience.

TypeScript: A Double-Edged Sword

Ah, TypeScript, the two-sided knife that kept me on my toes. On one hand, its strict type system was a lifesaver, catching errors and improving predictability as I worked. On the other hand, it felt like it was cutting me just as often—especially as I tackled our data-util logic for saving and loading user data.

here is the part of data type logic:


export type HistoryItem = {
  simpleUrl: string;
  title?: string;
  visitCount?: number;
};

export type TagStat = {
  tag: string;
  like: number;
  dislike: number;
  ctr: number;
  totalCount: number;
};

type StorageKey =
  | "historyData"
  //...
  | "interestData"
  | "contentData"
  | "tagData"

type StorageMap = {
  chatHistory: Message[];
  contentChatHistory: Message[];
  historyData: HistoryItem[];
  //...
  tagData: string;
  contentData: Content[];
  tagStat: TagStat[];
};

Enter fullscreen mode Exit fullscreen mode

here is the part of data util logic:


export const removeLocalStorageData = (
  name: StorageKey,
  callback: () => void
) => {
  chrome.storage.local.remove(name, callback);
};

// History Data Management
export const saveHistoryData = (historyItems: HistoryItem[]): void => {
  chrome.storage.local.set({ historyData: historyItems });
};

export const loadHistoryData = (
  callback: (historyItems: HistoryItem[]) => void
): void => {
  chrome.storage.local.get("historyData", (result) => {
    const historyItems: HistoryItem[] = result.historyData || [];
    callback(historyItems);
  });
};

// Content Data Management
export const saveContentData = (content: Content[]) => {
  if (Array.isArray(content)) {
    chrome.storage.local.set<StorageMap>({ contentData: content });
  } else {
    console.error("Invalid content data provided. Expected an array:", content);
  }
};

export const saveTagStats = (data: TagStat[]) => {
  if (Array.isArray(data)) {
    chrome.storage.local.set<StorageMap>({ tagStat: data });
  } else {
    console.error("Invalid content data provided. Expected an array:", data);
  }
};

export const loadContentData = (callback: (content: Content[]) => void) => {
  chrome.storage.local.get("contentData", (result) => {
    const content = result.contentData;

    if (Array.isArray(content)) {
      callback(content);
    } else {
      console.warn(
        "Invalid content data found in storage. Defaulting to empty array."
      );
      callback([]);
    }
  });
};

Enter fullscreen mode Exit fullscreen mode

But, with time, I started to understand its quirks. Predicting type errors became second nature, and I found myself appreciating the structure it brought to our increasingly complex codebase. If nothing else, TypeScript made sure I stayed humble—and sharp!

The Big Wins: What We Built

dinomind flowchart

After countless iterations and a lot of help from both my mentor and our AI expert, here’s what we achieved:

  • Refactored Hooks: Modular and reusable setter-fetcher functions that handle data interactions cleanly.
  • Multi-API Integration: Seamless communication between Gemini Nano AI’s APIs, allowing us to:

-Summarize history data.

  • Generate personalized content based on user interests.
  • Continuously summarize conversations to maintain chat context.
  • Chat Continuity: Previously, closing the sidepanel meant losing progress. Now, thanks to our refactored Redux logic and fine-tuned AI prompts, we could save sessions and pick up right where we left off with fine tuned summarization chat session logic.

  • User-Centric Features:

  • Interest Tagging: AI dynamically generates tags based on user history for future content generation.
  • Streamlined Prompts: Custom prompts for AI interactions, ensuring smarter, more relevant responses.
  • Recommendation system & Using Ai prompts: This phase was pivotal, and not just because we were racing against the clock to meet our deadline. It marked the first concrete step toward realizing our big initial idea: a recommendation system based on user preferences, seamlessly integrated with Gemini Nano AI. However, this vision wasn’t easy to achieve, as we constantly battled errors with AI, Chrome versions, and function logic.

Thanks to my AI Senior, who was incredibly kind and patient, I learned the art of “talking” to AI. Understanding what to ask, how to structure requests, and how to optimize responses became a game-changer for our project. This wasn’t just about functionality anymore; it was about **transforming the extension into an intuitive, dynamic tool **that could truly elevate the user experience.

Example our prompt configuration :

import {
 saveChatData,
 saveContentChatData,
 saveContentData,
 saveInterestData,
 saveTagData,
} from "../dataUtils";
import { ComponentType } from "../../types/componentType";
import { Message } from "../../types/messageType";
import { Content } from "@/hooks/useContentResponse";


type SaveDataFunction = {
 chatbox: (data: Message) => void;
 contentChat: (payload: { id: string; data: Message }) => void;
 interest: (data: string[]) => void;
 tag: (data: string[]) => void;
 content: (data: Content[]) => void;
 summarizeChat: (data: string) => void;
};


export const promptConfig: Record<
 ComponentType,
 {
   promptTemplate: string;
   contentPromptTemplate?: string;
   saveData: SaveDataFunction[ComponentType];
 }
> = {
 chatbox: {
   promptTemplate: `Analyze the user's input to respond correctly:


   If the user is asking a question, respond with a concise and clear answer without additional explanations.
   If the user intends to create content, generate  detailed and coherent response directly addressing their input.
   Ensure the response is formatted clearly and is directly usable without further adjustments.
   user's input: "{userMessage}"
   `,


   contentPromptTemplate: `Generate relevant and specific tags for the following content summary to enhance its discoverability and provide actionable insights for the user's query:


   Content Summary: "{summary}"


   User Question: "{userMessage}"


   Ensure the tags are precise and relevant to the summary and question. Avoid generic or overly broad terms that might dilute the specificity of the results. If appropriate tags do not exist, leave them uncreated.
   `,
   saveData: saveChatData,
 },
 interest: {
   promptTemplate: `
   Here is the URL I visited: "{userMessage}".
   The purpose of this analysis is to create relevant content tags for this URL.


   The URL's title and metadata provide hints about its focus. Generate a summary that captures:
   1. The essence of the URL's content and purpose.
   2. A list of relevant tags that describe its core aspects.
`,
   saveData: saveInterestData,
 },


 tag: {
   promptTemplate: `
   You are content tag generator. You generate tags from given input with few words.
   Analyze the given text and generate one specific and concise category or tag that is highly relevant to the content, consisting of one to three words.
   Focus on accurately capturing the essence of the text without being overly generic or broad.
   Avoid platform names unless integral to the content, and ensure the tags are engaging, actionable, and relevant.


   - Examples:
     - If the link is about video contents output: "trending video ideas," "educational video ideas," or "popular tutorials about video making."
     - If the link is about social networking (like Facebook, instagram): "trending community discussions," "trending memes," or "trending social debates."
     - If the link is about instant messaging - chatting (like Whatsapp, Discord): "chat hacks," "trending discussions," or "daily buzz."
     - If the link is about a blog post : "interesting decorations," "parenting ideas," "photography tricks," or "food trends."
     - If the link is about a news website: "trending news," "interesting news," or "trending updates in sport."


   Input:
   "{userMessage}"
`,
   saveData: saveTagData,
 },

//...

};
Enter fullscreen mode Exit fullscreen mode

Our most unique capability—a f*ine-tuned Gemini Nano AI integrated with a recommendation system* was the crown jewel, but getting it to work smoothly involved endless troubleshooting. Still, overcoming these challenges laid the foundation for a system that understood user preferences, generated relevant content, and offered personalized recommendations, even as the pressure mounted.

The Dino Evolves

And, of course, the Lonely Dino wasn’t left out of the fun. Now, it didn’t just run from meteors; it was busy fetching and generating content for its beloved user, all while maintaining a seamless chat experience. Behind its playful facade was a sophisticated system of AI integrations, all working together to make the Dino the perfect Chrome mascot for the future.

Last 10 Hours to CODE FREEZE!

The final hours before code freeze were a chaotic blend of bug hunting, UI polishing, and last-minute implementations. With the deadline looming, all commits went straight to the main branch—no time for fancy PR reviews. It was an endless cycle of fixing bugs, merging conflicts, and uncovering new issues that needed to be squashed. It felt like a real-life game of Whac-A-Mole, except the moles were bugs, and the hammer was our collective exhaustion.
Picture three people working simultaneously on the same motor in a garage—that was us in the final stretch: coding, testing, debugging, and hoping that nothing major would fall apart. Stress levels were through the roof, and I’ll admit, I felt the pressure. But this is where the experience of my teammates truly shone.
Their calm demeanor and steady guidance helped me stay grounded, even when the clock was ticking louder than ever. Even if my previous experience as a senior architect gave me tools to handle high-pressure situations, i still feel the pressure and can't handle it very well, but having them by my side reminded me I wasn’t alone in this. With their support, I was able to channel the stress into focus, keep pushing forward, and work as part of a team that truly had each other's backs. Sleep wasn’t an option, but their reassurance (and a lot of caffeine) made sure I kept going strong.

UI Guy’s Back !

As the self-proclaimed “the UI guy”, this was my moment. With the core logic and backend in place, I finally got to finalize the UI/UX. Using Radix UI and bold styles that complemented our ridiculous-yet-awesome Dino icon, I put the finishing touches on the design. From React Markdown for content rendering to AI response animations, everything started to come together. My work wasn’t just about aesthetics—it was about making the user experience intuitive, functional, and fun.

<div>
        {messages.map((message, index) => (
          <div key={index} className={`message ${message.sender}`}>
            {message.sender === Sender.AI && (
              <>
                {/* Render DinoResponse for every AI message */}
                <DinoResponse isLoading={false} />

                {index === activeAiMessageIndex && loading ? (
                  <TextGenerateEffectFx
                    words={message.text || ""}
                    duration={0.3}
                    filter={false}
                  />
                ) : (
                  <ReactMarkdown className="prose prose-invert">
                    {message.text}
                  </ReactMarkdown>
                )}
              </>
            )}
            {message.sender !== Sender.AI && (
              <>
                <ReactMarkdown className="prose prose-invert border border-gray-500 rounded-xl text-right px-2 max-w-[70vw] w-fit ml-auto">
                  {message.text}
                </ReactMarkdown>
                {loading && (
                  <DinoResponse
                    isLoading={index + 1 === messages.length && loading}
                  />
                )}
              </>
            )}
          </div>
        ))}
        <div ref={messagesEndRef} />
      </div>
Enter fullscreen mode Exit fullscreen mode

Last-Minute Implementations

Even as the recommendation system continued to evolve, we managed to squeeze in some big features before the codefreeze:

  • Content Injection: Added the ability to inject content into web pages with a “Explain This Logic” button powered by the summarize AI.

to inject content you need permission first :

"permissions": [
    //...
    "contextMenus"
  ],
Enter fullscreen mode Exit fullscreen mode

and you also need the maintain communication between background.js and related components:

// Context Menu for Summarize It
chrome.runtime.onInstalled.addListener(() => {
  chrome.contextMenus.create({
    id: "summarizeIt",
    title: "Explain",
    contexts: ["selection"],
  });
});

// Handle context menu clicks
chrome.contextMenus.onClicked.addListener(async (info, tab) => {
  if (info.menuItemId === "summarizeIt" && info.selectionText && tab?.id) {
    chrome.tabs.sendMessage(tab.id, {
      action: "openSummarizeModal",
      text: info.selectionText,
    });
  }
});

chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (message.action === "closeSummarizeModal" && sender.tab?.id) {
    chrome.tabs.sendMessage(sender.tab.id, {
      action: "closeSummarizeModal",
    });
    sendResponse({ status: "success" });
  }
});
Enter fullscreen mode Exit fullscreen mode
  • Translation Capabilities: Integrated a multilingual translation feature to expand the app’s functionality.
  • Prompt Reshaping: Tweaked and fine-tuned prompt configurations to improve AI responses and ensure the app felt polished.

The Final Hour: Code Freeze

Miraculously, we managed to call code freeze with one hour left on the clock. My tutor immediately started working on the explainer video, while the AI Senior tackled the documentation with precision and clarity. Meanwhile, I—utterly spent—finally surrendered to sleep, satisfied that we had done everything we could to deliver a functional and innovative app.
The final hours were a testament to teamwork, perseverance, and just a touch of madness. From chaotic merges to polished animations, we pushed ourselves to the limit to create something we were truly proud of. And as I drifted off to sleep, all I could think was: “We did it, hopefully..”

please visit our hackaton page :

DinoMind | Devpost

Enhancing Chrome's offline experience with an AI chatbot and recommender system—personalized content and intelligent interactions.

devpost.com

our github repo:

GitHub logo Dino-Mind / dinomind

Offline content generator chrome extension (gemini nano)

Dinomind

Dinomind is a smart Chrome extension designed to enhance your browsing experience by leveraging user habits and interests. Whether you’re online or offline, Gemini Nano Assistant provides personalized insights, tips, and resources tailored to your preferences, helping you get the most out of Chrome.

Inspiration

Styled as a homage to Chrome's classic offline "Lonely T-Rex" game, this extension reimagines the concept with a futuristic twist. While the T-Rex now races against meteors to protect its kind, the design symbolizes a forward-thinking vision for Chrome's capabilities. With sleek, meteor-inspired animations, the extension blends nostalgia with innovation, offering a visually captivating experience that aligns with the evolving role of Chrome in the modern browsing landscape.

Video Demonstration

Watch the video

Features

  • Personalized Side Panel: Access an intuitive side panel with content based on your browsing habits and interests. It includes relevant resources, shortcuts, and suggestions that complement your Chrome browsing experience.
  • Recommendation System

demo video:

To wrap up this post, I want to extend a heartfelt thanks to my closest friends and incredible teammates who made this journey not just possible but genuinely transformative. A special shoutout to Orhun Özer, my mentor and a senior full-stack wizard LinkedIn, whose guidance and patience helped me navigate the wild world of web development, and Eralp Turgay, a senior AI genius LinkedIn, who not only demystified AI for me but also showed me how to make it work seamlessly in real-world projects. Both of them have been instrumental in my career shift from architecture to web development, providing their expertise, encouragement, and plenty of laughs along the way. This project wasn’t just a hackathon entry; it was a testament to what great friends and mentors can help you achieve. Thank you both for believing in me! 🚀🚀🚀

Top comments (0)