<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Bunhere]]></title><description><![CDATA[Bunhere]]></description><link>https://blog.bunhere.com</link><generator>RSS for Node</generator><lastBuildDate>Tue, 14 Apr 2026 02:13:07 GMT</lastBuildDate><atom:link href="https://blog.bunhere.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Create a Tarot View App with the Gemini API in just 15 minutes.]]></title><description><![CDATA[It's a little old, but I still believe I'm GenZ, and when it comes to trouble, GenZ always finds answers from the universe 🤓.
I was introduced to a friend to watch the Tarot, but because he was waiting too long to watch, I happened to see a prompt t...]]></description><link>https://blog.bunhere.com/create-a-tarot-view-app-with-the-gemini-api-in-just-15-minutes</link><guid isPermaLink="true">https://blog.bunhere.com/create-a-tarot-view-app-with-the-gemini-api-in-just-15-minutes</guid><category><![CDATA[geminiAPI]]></category><category><![CDATA[tarot]]></category><dc:creator><![CDATA[Emma Ngo]]></dc:creator><pubDate>Mon, 03 Jun 2024 03:43:07 GMT</pubDate><content:encoded><![CDATA[<p><img src="https://bunhere.s3.amazonaws.com/bunhere-blog-public/images/blogs/tarot-with-gemini.png" alt="Tarot with Gemini API" /></p>
<p>It's a little old, but I still believe I'm GenZ, and when it comes to trouble, GenZ always finds answers from the universe 🤓.</p>
<p>I was introduced to a friend to watch the Tarot, but because he was waiting too long to watch, I happened to see a prompt to watch Tartot on Tiktok. I tried through both ChatGPT and Gemini and found output on both sides attributed to a problem that we're experiencing 😝.</p>
<blockquote>
<p>Anyone who is too serious can drop this content. All three of you, cosmic energy!</p>
</blockquote>
<h2 id="heading-ideas">Ideas</h2>
<p>The contents of the prompt are like this:</p>
<pre><code>Generate <span class="hljs-number">3</span> random numbers between <span class="hljs-number">1</span> and <span class="hljs-number">78.</span> Next, look up the corresponding tarot cards, and their meanings. Finally, put together an overall reading <span class="hljs-keyword">for</span> me, based on the <span class="hljs-number">3</span> cards. Answer <span class="hljs-keyword">in</span> Vietnamese
</code></pre><p>And here's what we got (it's a little bit different because I added a note to show the results in Vietnamese):</p>
<p><img src="https://bunhere.s3.amazonaws.com/bunhere-blog-public/images/blogs/result-from-gemini.png" alt="Tarot result from GeminiAPI" /></p>
<blockquote>
<p>In conclusion, both results suggest that we need to "see and listen to the inner voice".</p>
</blockquote>
<p>From the idea that I decided to write an app instead of randomly taking three of the 78 Tarot cards, I showed 78 cards out (of course a random show), and then everyone picked out three cards with three numbers. Then re-adjust the <code>prompt</code> call API to get the results from Gemini (because the installation from the GeminiAPI is quite simple) and then return them to the user. </p>
<p>That way, people can be more proactive in choosing cards.</p>
<h2 id="heading-deploy">Deploy</h2>
<h2 id="heading-interface">Interface</h2>
<p>Techstack: I've been using NextJS a lot lately, because it's easy to deploy as well. (Vercel).</p>
<p>First, we need to render the layout of the 78 cards for the user to play.</p>
<ol>
<li><strong>Resources</strong></li>
</ol>
<p>To get the UI of your Tarot cards up <a target="_blank" href="https://dribbble.com/search/78-tarot-cards">Dribbble</a> search, <em>DriBBble is the website that the designer visits for ideas.</em></p>
<p><img src="https://bunhere.s3.amazonaws.com/bunhere-blog-public/images/blogs/Dribbble-78-tarot-cards.png" alt="Dribbble" /></p>
<p>After we download the Tarot set, we choose to upload it and access it via S3 (you can store it right in src).</p>
<p>In the image section, we go through the data section, create a data set of tarot cards in the form of JSON queries.</p>
<p><em>Note: You can skip this section and initialise an array from 1-78 for processing, because what we need is three numbers "picked" but we want to show more information about the cards so we create this json data set.</em></p>
<p>And of course, we generate data by using Gemini 🤪. I'm using the prompt:</p>
<pre><code>Create a data set <span class="hljs-keyword">of</span> objects containing the contents <span class="hljs-keyword">of</span> all tarot cards. The main contents <span class="hljs-keyword">of</span> the objects are the id, the name <span class="hljs-keyword">of</span> the card, the main and reverse meaning and further description <span class="hljs-keyword">of</span> the cards. To help us create additional contents <span class="hljs-keyword">for</span> the card, the src is <span class="hljs-string">'/[alias-name-of-the-card].png'</span>.
</code></pre><p>Result:</p>
<pre><code class="lang-json">[
    {
        <span class="hljs-attr">"id"</span>: <span class="hljs-number">0</span>,
        <span class="hljs-attr">"name"</span>: <span class="hljs-string">"The Fool"</span>,
        <span class="hljs-attr">"mainMeaning"</span>: <span class="hljs-string">"Beginnings, naivety, spontaneity, freedom."</span>,
        <span class="hljs-attr">"reverseMeaning"</span>: <span class="hljs-string">"Restraint, recklessness, risk."</span>,
        <span class="hljs-attr">"description"</span>: <span class="hljs-string">"The Fool represents a new beginning, believing in the future, inexperienced, not knowing what's going to happen, lucky beginners, transformers, and believers in the universe."</span>,
        <span class="hljs-attr">"src"</span>: <span class="hljs-string">"/the-fool.png"</span>,
        <span class="hljs-attr">"type"</span>: <span class="hljs-string">"major-arcana"</span>
    },
    {
        ...
    },
    ...
]
</code></pre>
<blockquote>
<p>Full source: <a target="_blank" href="https://github.com/bunheree/tarot/blob/main/src/data/en-cards.json">cards.json</a></p>
</blockquote>
<ol start="2">
<li><strong>Random Tarot Cards</strong></li>
</ol>
<p>Write the function to randomly display the order of the tarot cards.</p>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> { Card } <span class="hljs-keyword">from</span> <span class="hljs-string">"@/types/card"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">shuffleArray</span>&lt;<span class="hljs-title">Card</span>&gt;(<span class="hljs-params">array: Card[]</span>): <span class="hljs-title">Card</span>[] </span>{
  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = array.length - <span class="hljs-number">1</span>; i &gt; <span class="hljs-number">0</span>; i--) {
    <span class="hljs-keyword">const</span> j = <span class="hljs-built_in">Math</span>.floor(<span class="hljs-built_in">Math</span>.random() * (i + <span class="hljs-number">1</span>));
    [array[i], array[j]] = [array[j], array[i]];
  }
  <span class="hljs-keyword">return</span> array;
}
</code></pre>
<blockquote>
<p>Suhffle source: <a target="_blank" href="https://github.com/bunheree/tarot/blob/main/src/common/shuffle.ts">src/common/shuffle.ts</a></p>
</blockquote>
<p>Random display of tarot cards</p>
<pre><code class="lang-tsx">import { useEffect, useState } from "react";
import tarotCards from "@/data/cards.json";
import TarotCard from "@/components/TarotCard";
import { shuffleArray } from "@/common/shuffle";
import { Card } from "@/types/card";

...
const rootCards: Card[] = tarotCards
const [cards, setCards] = useState&lt;Card[]&gt;(rootCards)

useEffect(() =&gt; {
    setCards(shuffleArray(rootCards)) // Call shuffleArray when loading
}, [rootCards])

const handleRefresh = () =&gt; {
    setCards(shuffleArray(rootCards)) // Call shuffleArray when refreshing pick
}

return (
    &lt;&gt;
        {cards &amp;&amp; cards.map((card) =&gt; (
            &lt;TarotCard key={card.id} card={card} /&gt;
        ))}
        &lt;button onClick={handleRefresh}&gt;Refresh&lt;/button&gt;
    &lt;/&gt;
);
</code></pre>
<blockquote>
<p>Page source: <a target="_blank" href="https://github.com/bunheree/tarot/blob/main/src/app/page.tsx">src/app/page.tsx</a></p>
</blockquote>
<h3 id="heading-logic-processing">Logic Processing</h3>
<ol>
<li>Connect to Gemini API</li>
</ol>
<p><strong>Step 1:</strong> Go to <a target="_blank" href="https://aistadio.google.com/">aistudio.google</a> and create the <code>API Key</code>. Add to file <code>.env</code></p>
<p>Details: </p>
<pre><code class="lang-bash">NEXT_PUBLIC_S3_ENDPOINT=sAIza...ICYp
</code></pre>
<p>Settings:</p>
<pre><code class="lang-bash">yarn add @google/generative-ai
</code></pre>
<p><strong>Step 2:</strong> Edit Prompt</p>
<pre><code>With <span class="hljs-number">3</span> cards: [card], [card <span class="hljs-number">2</span>], and [card <span class="hljs-number">3</span>]. Next, look up the corresponding tarot cards and their meanings. Finally, put together an overall reading <span class="hljs-keyword">for</span> me based on the three cards.
</code></pre><p><strong>Step 3:</strong> Process <code>code</code> to connect to GeminiAPI</p>
<p>You can learn more about using GeminiAPI with <a target="_blank" href="https://ai.google.dev/gemini-api/docs/get-started/tutorial?lang=web#set-up-project">document</a>.</p>
<p><img src="https://bunhere.s3.amazonaws.com/bunhere-blog-public/images/blogs/gemni-api-billing.png" alt="billing information" /></p>
<p><strong>Note:</strong> You need to note the billing information in order to process it accordingly. Because I'm using account FREE, there's a 15 request/minute limit, so I created a request limit API <code>MAX_CALLS_PER_MINUTE</code>.</p>
<pre><code class="lang-tsx">'use client'
import { useEffect, useState } from "react"

import tarotCards from "@/data/cards.json"
import { shuffleArray } from "@/common/shuffle"
import { Card } from "@/types/card"

import TarotCard from "@/components/TarotCard"
import DefaultCard from "@/components/DefaultCard"

import { GoogleGenerativeAI } from "@google/generative-ai"

const API_KEY = process.env.NEXT_PUBLIC_GEMINI_API_KEY || ''
const MAX_CALLS_PER_MINUTE = 10

export default function Home() {
  const rootCards: Card[] = tarotCards
  const [cards, setCards] = useState&lt;Card[]&gt;(rootCards)
  const [pickCards, setPickCards] = useState&lt;number[]&gt;([])
  const [prompt, setPrompt] = useState&lt;string&gt;("")
  const [result, setResult] = useState&lt;string&gt;("")
  const [isLoading, setIsLoading] = useState&lt;boolean&gt;(false)
  const [callCount, setCallCount] = useState(0)
  const [timer, setTimer] = useState&lt;NodeJS.Timeout | null&gt;(null)

  useEffect(() =&gt; {
    setCards(shuffleArray(rootCards))
  }, [rootCards])

  useEffect(() =&gt; {
    if (pickCards.length &gt; 2) { // Pick enough 3 cards
      setPrompt(`With 3 cards: ${pickCards[0]}, ${pickCards[1]}, and ${pickCards[2]}. Next, look up the corresponding tarot cards and their meanings. Finally, put together an overall reading for me based on the three cards.`)
    }
  }, [pickCards, cards])

  const handlePickCard = (cardId: number) =&gt; {
    if (pickCards.length &gt; 2) {
      console.log('You only can choose 3 cards')
      return
    }
    setPickCards([...pickCards, cardId])
  }

  const handleResetPick = () =&gt; {
    setCards(shuffleArray(rootCards))
    setPickCards([])
    setPrompt("")
    setResult("")
  }

  useEffect(() =&gt; {
    if (timer === null) {
      const newTimer = setInterval(() =&gt; {
        setCallCount(0)
      }, 60000) // Reset count every 60 seconds
      setTimer(newTimer)

      // Clean up timer on component unmount
      return () =&gt; clearInterval(newTimer)
    }
  }, [timer])

  const handleReadCards = async () =&gt; {
    if (prompt === "") {
      alert('Please choose all three cards!')
      return
    }

    if (callCount &gt;= MAX_CALLS_PER_MINUTE) {
      alert('System overload! Please wait a minute and then try again.')
      return
    }

    setCallCount(callCount + 1)
    await handleSendPromptToGemini(prompt)
  }

  const handleSendPromptToGemini = async (prompt: string) =&gt; {
    setIsLoading(true);
    try {
      const genAI = new GoogleGenerativeAI(API_KEY)
      const model = genAI.getGenerativeModel({ model: "gemini-pro" })

      const result = await model.generateContent(prompt)
      const response = result.response
      const text = response.text()

      setResult(text)
    } catch (error) {
      setResult('Failed to fetch response.')
    }
    setIsLoading(false)
  }

  return (
    &lt;main className="flex min-h-screen flex-col items-center justify-between p-24"&gt;
      {isLoading &amp;&amp;
        &lt;p className="pb-8"&gt;loading...&lt;/p&gt;
      }
      &lt;button className={`bg-black text-white p-4 text-center border hover:bg-white hover:text-black ${isLoading ? 'opacity-50 cursor-not-allowed' : ''}`} disabled={isLoading} onClick={handleResetPick}&gt;Reset&lt;/button&gt;
      {result &amp;&amp;
        &lt;p className="pb-8"&gt;{result}&lt;/p&gt;
      }
      &lt;div className="relative flex flex-wrap"&gt;
        {cards &amp;&amp; cards.filter((card) =&gt; !pickCards.includes(card.id)).map((card: Card) =&gt; (
          &lt;div key={card.id} className="relative -ml-20 hover:-mt-4"&gt;
            &lt;TarotCard card={card} handlePickCard={handlePickCard} /&gt;
          &lt;/div&gt;
        ))}
      &lt;/div&gt;
      &lt;button className={`bg-black text-white p-4 text-center border hover:bg-white hover:text-black ${isLoading ? 'opacity-50 cursor-not-allowed' : ''}`} disabled={isLoading} onClick={() =&gt; handleReadCards()}&gt;Read Cards&lt;/button&gt;

      &lt;DefaultCard cards={cards} pickCard={pickCards} /&gt;
    &lt;/main&gt;
  )
}
</code></pre>
<blockquote>
<p>Page source: <a target="_blank" href="https://github.com/bunheree/tarot/blob/main/src/app/page.tsx">src/app/page.tsx</a></p>
</blockquote>
<h3 id="heading-deloy">Deloy</h3>
<ul>
<li>Create <code>repo</code> and push your project to Github</li>
<li>Log in to <code>Vercel</code> if you already have an account; connect to your Github account.<ul>
<li>Select <code>New project</code></li>
<li>Select <code>repo</code> from your Github account</li>
<li>Deloy project (if failed in Settings &gt; Environment &gt; Add variable <code>NEXT_PUBLIC_GEMINI_API_KEY</code> and corresponding value)</li>
</ul>
</li>
</ul>
<p><strong>Details</strong>: <a target="_blank" href="https://vercel.com/docs/deployments/git/vercel-for-github">Deloy Vercel For Github</a></p>
<h2 id="heading-demo">Demo</h2>
<p>Access <a target="_blank" href="https://tarot1.bunhere.com">tarot1.bunhere.com</a> for the demo. Official edition at <a target="_blank" href="https://tarot.bunhere.com">tarot.bunhere.com</a></p>
<p>View full source code <a target="_blank" href="https://github.com/bunheree/tarot/tree/version-1.0">here</a>.</p>
<p><img src="https://bunhere.s3.amazonaws.com/bunhere-blog-public/images/blogs/tarot-bun.png" alt="Tarot | Bunhere" /></p>
<h2 id="heading-end">End.</h2>
<p>Happy coding!!! 👩🏼‍💻</p>
<p>References: </p>
<ul>
<li><a target="_blank" href="https://ai.google.dev/gemini-api/docs/get-started/tutorial?lang=web#set-up-project">GeminiAPI document</a></li>
<li><a target="_blank" href="https://vercel.com/docs/deployments/overview">Deploying to Vercel</a></li>
</ul>
<blockquote>
<p>Author: <a target="_blank" href="https://bunhere.com">bunhere.com</a> <br />
<em>I am always looking for feedback on my writing, so please let me know what you think.</em> ❤️</p>
</blockquote>
]]></content:encoded></item><item><title><![CDATA[Firebase Genkit — AI Application by Javascript/Typescript]]></title><description><![CDATA[Google recently launched the Firebase Genkit as an open source framework to build, deploy, and monitor AI applications.
Overall, you guys are constantly launching AI-related technology, and you see it's just like an arms race, everyone wants their pr...]]></description><link>https://blog.bunhere.com/firebase-genkit-ai-application</link><guid isPermaLink="true">https://blog.bunhere.com/firebase-genkit-ai-application</guid><category><![CDATA[firebase genkit]]></category><category><![CDATA[GoogleIO2024]]></category><category><![CDATA[Firebase]]></category><dc:creator><![CDATA[Emma Ngo]]></dc:creator><pubDate>Tue, 28 May 2024 15:54:42 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1716911494751/f7145142-b074-48a5-943e-b5a54a7d3252.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Google recently launched the <strong>Firebase Genkit</strong> as an open source framework to build, deploy, and monitor AI applications.</p>
<p>Overall, you guys are constantly launching AI-related technology, and you see it's just like an arms race, everyone wants their products to be better than others from OpenAI with the GTP-4o that was just launched, and Google with the Gemini that's parallel to the Build with AI series of events to learn technology with Vertex AI...</p>
<p>Back to the main content of this article, Genkit is used to create custom content creation applications, semantic searches, input processing, and answering questions based on the data you set up, automated decision-making, command coordination, and more. You can find out more on Firebase's home page about Genkit attached <a target="_blank" href="https://firebase.google.com/products/genkit">here</a>.</p>
<p>Genkit currently supports JavaScript/TypeScript development (Node.js).</p>
<h2 id="heading-key-features">Key Features</h2>
<p>From the information page of <em>Firebase Genkit</em> there are highlights 10 features that are considered as <em>key features</em> so in this article let's find out what those 10 features are!</p>
<h3 id="heading-1-many-models-one-interface">1. Many models, one interface</h3>
<p>That means you can use this feature to integrate image recognition models, text translation models, and text creation models into the same application.</p>
<h3 id="heading-2-structured-output">2. Structured output</h3>
<p>This means that the data returned from Genkit will be a structured output, so returning it will make it easier for you to handle the data. For example, when using an image recognition model, the results you can get are information about the objects detected in the image, including location, size, etc.</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> { generate } <span class="hljs-keyword">from</span> <span class="hljs-string">"@genkit-ai/ai"</span>;
<span class="hljs-keyword">import</span> { geminiPro } <span class="hljs-keyword">from</span> <span class="hljs-string">"@genkit-ai/vertexai"</span>;
<span class="hljs-keyword">import</span> { z } <span class="hljs-keyword">from</span> <span class="hljs-string">"zod"</span>;

<span class="hljs-keyword">const</span> LocationSchema = z.object({
  <span class="hljs-attr">name</span>: z.string().describe(<span class="hljs-string">'Location name'</span>),
  <span class="hljs-attr">address</span>: z.string().describe(<span class="hljs-string">'Address'</span>),
  <span class="hljs-attr">reviews</span>: z.array(z.object({
    <span class="hljs-attr">name</span>: z.string(),
    <span class="hljs-attr">goodReview</span>: z.number().describe(<span class="hljs-string">'Number of good review'</span>),
    <span class="hljs-attr">badReview</span>: z.number().describe(<span class="hljs-string">'Number of bad review'</span>),
  })).describe(<span class="hljs-string">'3 properties that model can be response!'</span>)
});

<span class="hljs-keyword">const</span> createLocation = defineFlow({
    <span class="hljs-attr">name</span>: <span class="hljs-string">"createLocation"</span>,
    <span class="hljs-attr">inputSchema</span>: z.string(),
    <span class="hljs-attr">outputSchema</span>: LocationSchema,
  },
  <span class="hljs-function">(<span class="hljs-params">habitat</span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> generate({
      <span class="hljs-attr">model</span>: geminiPro,
      <span class="hljs-attr">prompt</span>: <span class="hljs-string">`You were created successfully for the location: <span class="hljs-subst">${habitat}</span>.`</span>,
      <span class="hljs-attr">output</span>: {<span class="hljs-attr">schema</span>: LocationSchema}
    });
    <span class="hljs-comment">// strongly typed and ready to go</span>
    <span class="hljs-keyword">return</span> result.output();
  }
)

<span class="hljs-built_in">console</span>.log(<span class="hljs-keyword">await</span> createCreature(<span class="hljs-string">"a developer conference"</span>));
</code></pre>
<h3 id="heading-3-multimodal-multimedia">3. Multimodal, multimedia</h3>
<p>Genkit supports working with different types of input and output data, such as text, images, audio and video. Simply understand, this feature can be applied to create an image object recognition app, translate text into speech, and create music based on text.</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> { imagen2, geminiProVision } <span class="hljs-keyword">from</span> <span class="hljs-string">'@genkit-ai/vertexai'</span>;
<span class="hljs-keyword">import</span> { generate } <span class="hljs-keyword">from</span> <span class="hljs-string">'@genkit-ai/ai'</span>;

<span class="hljs-keyword">const</span> imageResult = <span class="hljs-keyword">await</span> generate({
  <span class="hljs-attr">model</span>: imagen2,
  <span class="hljs-attr">prompt</span>: <span class="hljs-string">'Create image include time, location and history information of that location'</span>,
});
<span class="hljs-keyword">const</span> generatedImage = imageResult.media();

<span class="hljs-keyword">const</span> descriptionResult = <span class="hljs-keyword">await</span> generate({
  <span class="hljs-attr">model</span>: geminiProVision,
  <span class="hljs-attr">prompt</span>: [
    {
      <span class="hljs-attr">text</span>: <span class="hljs-string">'What is the time, location and history information the image express?'</span>,
    },
    { <span class="hljs-attr">media</span>: generatedImage },
  ],
});
<span class="hljs-built_in">console</span>.log(descriptionResult.text());
</code></pre>
<h3 id="heading-4-llm">4. LLM</h3>
<p>Genkit provides special tools for the LLM, such as the ability to access memory and context to generate better results. For example, when you build a chatbot, you can apply this feature to improve <em>accuracy</em> and <em>fluidity</em> for outputs based on the LLM you provide in the application.</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> { generate, defineTool } <span class="hljs-keyword">from</span> <span class="hljs-string">"@genkit-ai/ai"</span>;
<span class="hljs-keyword">import</span> { geminiPro } <span class="hljs-keyword">from</span> <span class="hljs-string">"@genkit-ai/vertexai"</span>;
<span class="hljs-keyword">import</span> { z } <span class="hljs-keyword">from</span> <span class="hljs-string">"zod"</span>;

<span class="hljs-keyword">const</span> createReminder = defineTool({
  <span class="hljs-attr">name</span>: <span class="hljs-string">"createReminder"</span>,
  <span class="hljs-attr">description</span>: <span class="hljs-string">"Create Reminder"</span>,
  <span class="hljs-attr">inputSchema</span>: z.object({
    <span class="hljs-attr">time</span>: z.string().describe(<span class="hljs-string">'ISO timestamp string, e.g. 2024-04-03T12:23:00Z'</span>),
    <span class="hljs-attr">reminder</span>: z.string().describe(<span class="hljs-string">'The content of reminder'</span>),
  }),
  <span class="hljs-attr">outputSchema</span>: z.number().describe(<span class="hljs-string">'ID of reminder'</span>),
  <span class="hljs-function">(<span class="hljs-params">reminder</span>) =&gt;</span> db.reminders.create(reminder)
});

<span class="hljs-keyword">const</span> searchNotes = defineTool({
  <span class="hljs-attr">name</span>: <span class="hljs-string">"searchNotes"</span>,
  <span class="hljs-attr">description</span>: <span class="hljs-string">"Use this to find the person or the note content"</span>,
  <span class="hljs-attr">inputSchema</span>: z.string().describe(<span class="hljs-string">'the search query'</span>),
  <span class="hljs-attr">outputSchema</span>: z.object({<span class="hljs-attr">notes</span>: z.array(NoteSchema)}),
  <span class="hljs-function">(<span class="hljs-params">query</span>) =&gt;</span> db.notes.search(query)
});

<span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> generate({
  <span class="hljs-attr">model</span>: geminiPro,
  <span class="hljs-attr">tools</span>: [createReminder, searchNotes],
  <span class="hljs-attr">prompt</span>: <span class="hljs-string">`
  You're a note assistant. Use the tools available, try to answer the question.
  If you create a reminder, describe your reminder in writing as a feedback.

  Question: I've recorded my appointment with my friend - can you put a time reminder?
  `</span>
});
<span class="hljs-built_in">console</span>.log(result.text());
</code></pre>
<h3 id="heading-5-manage-prompt-with-dotprompt">5. Manage Prompt with Dotprompt</h3>
<p>Dotprompt is an integrated tool in Genkit that can easily create, manage and test prompt for machine learning models. In the document on the Firebase page, it explains that Dotprompt is a reminder file format that allows you to put it all into a single file for easier organization and checking.</p>
<blockquote>
<p><em>Note: Prompt is a text that is used to guide the model on the task you want it to perform.</em></p>
</blockquote>
<p>The key is to use Dotprompt to create different prompt for text-generating patterns such as "Write a status sentence when traveling at sea to post a photo on Facebook" or "Writ an article to promote the Google IO 2024 event in Ho Chi Minh City on June 16, 2024".</p>
<pre><code>---
model: vertexai/gemini<span class="hljs-number">-1.0</span>-pro
<span class="hljs-attr">config</span>:
  temperature: <span class="hljs-number">0.9</span>
<span class="hljs-attr">input</span>:
  schema:
    properties:
      location: {<span class="hljs-attr">type</span>: string}
      <span class="hljs-attr">time</span>: {<span class="hljs-attr">type</span>: string}
      <span class="hljs-attr">name</span>: {<span class="hljs-attr">type</span>: string}
    <span class="hljs-attr">required</span>: [location]
  <span class="hljs-attr">default</span>:
    location: Ho Chi Minh city
---

Google IO <span class="hljs-number">2024</span> is Google<span class="hljs-string">'s upcoming highlight event will come to {{location}}.  

For special guests {{name}} {{/if}}{{#if time}} occurred at the time {{time}}{ {/ if}}.</span>
</code></pre><h3 id="heading-6-run-flows-locally">6. Run flows locally</h3>
<p>Genkit allows you to run local application flows on your computer without deploying applications to the Cloud, making it easier for you to <em>check</em> and <em>fix bugs</em> during development.</p>
<h3 id="heading-7-inspect-traces">7. Inspect traces</h3>
<p>Debugging complex, multi-step workflows with AI can be difficult due to randomness and hidden processes. Genkit provides tools to track your application. Tracks are detailed records of how your application interacts with Firebase services and machine learning models. This helps you debug your application and improve performance.</p>
<p>If you're building a chatbot, you can see your chatbot processing user requests and identifying any accuracy or performance issues.</p>
<blockquote>
<p>Note: It's hard to imagine the interpretation in words, so for a better understanding, you can see how it works based on the descriptive image <a target="_blank" href="https://firebase.google.com/docs/genkit#7_inspect_traces">here</a></p>
</blockquote>
<h3 id="heading-8-open-amp-extensible">8. Open &amp; extensible</h3>
<p>Simply put, Genkit is designed to enable scaling and customization so it can add new functions and integrate third-party services. For example, it's possible to use third-party voice recognition services to develop applications.</p>
<p>Or you can use Genkit and then develop it into a separate plugin and then upload it to npm.</p>
<h3 id="heading-9-built-for-production">9. Built for production</h3>
<p>Easily expand your streams to any platform, Genkit is fully equipped with OpenTelemetry and customizes metadata to monitor production.</p>
<p>Additionally, there are official Google Cloud and Firebase plugins that allow you to export Google Cloud activity data and integrate firebase services such as Cloud for FireBase, Firebaze Authentication, Application Testing, and Firestore.</p>
<p><em>To illustrate this feature, imagine you're developing an app like Google Trans. This application will have to handle a huge volume of translation requests and in the long run this application will emerge and there will be a huge number of users you need to respond to how to keep your application running stable with such a large volume of users instead of thinking and solving such problems Genkit will take care of this.</em></p>
<h3 id="heading-10-authorization-amp-security-handling">10. Authorization &amp; security handling</h3>
<p>When building any public application, it is important to protect the data stored in your system. When it comes to LLM, there is a need for further in-depth to ensure that the model only accesses the necessary data, the tool call commands are defined to the appropriate range for the user calling LLM and the streams are only called by the validated client applications.</p>
<p>Genkit supports a variety of authentication methods, such as Firebase and OAuth, combined with authorization policies and contexts management mechanisms.</p>
<p>The Genkit app creates an application that users need to log in to use and decentralize content that can be used by that user.</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> { defineFlow, runFlow } <span class="hljs-keyword">from</span> <span class="hljs-string">'@genkit-ai/flow'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> selfSummaryFlow = defineFlow(
  {
    <span class="hljs-attr">name</span>: <span class="hljs-string">'selfSummaryFlow'</span>,
    <span class="hljs-attr">inputSchema</span>: z.object({<span class="hljs-attr">uid</span>: z.string()}),
    <span class="hljs-attr">outputSchema</span>: z.string(),
    <span class="hljs-attr">authPolicy</span>: <span class="hljs-function">(<span class="hljs-params">auth, input</span>) =&gt;</span> {
      <span class="hljs-keyword">if</span> (!auth) {
        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'Authorization required.'</span>);
      }
      <span class="hljs-keyword">if</span> (input.uid !== auth.uid) {
        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'You may only summarize your own profile data.'</span>);
      }
    }
  },
  <span class="hljs-keyword">async</span> (input) =&gt; { ... });
</code></pre>
<h2 id="heading-review">Review</h2>
<p>Genkit is a new technology introduced recently, so the above article is based on the main information on the page <a target="_blank" href="https://firebase.google.com/docs/genkit">Firebase documents</a> and edited in my own way 😎, so if there are any errors, please contribute to the comments below.</p>
<p>I don't have time to study Genkit in depth either, and if you have any articles or information about the <strong>Firebase Genkit</strong>, please do not hesitate to share it below!</p>
<p>References: <a target="_blank" href="https://firebase.google.com/docs/genkit">firebase.google.com/docs/genkit</a></p>
<blockquote>
<p>Author: <a target="_blank" href="https://bunhere.com">bunhere.com</a> <br /><br />
<em>I am always looking for feedback on my writing, so please let me know what you think.</em> ❤️</p>
</blockquote>
]]></content:encoded></item><item><title><![CDATA[License Your Code Right: A GitHub Must-Do]]></title><description><![CDATA[Introduction
When you create a repo on GitHub, you will see the license option for your project. Although license selection is not mandatory, it can be important to determine how others can use your source code.
Has anyone ever wondered what this lic...]]></description><link>https://blog.bunhere.com/license-github-must-do</link><guid isPermaLink="true">https://blog.bunhere.com/license-github-must-do</guid><category><![CDATA[GitHub]]></category><category><![CDATA[GitHub Licenses]]></category><dc:creator><![CDATA[Emma Ngo]]></dc:creator><pubDate>Wed, 31 Jan 2024 07:35:40 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1706686484428/1be7b63f-e0ca-4d6d-9df9-ff020e152c96.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-introduction"><strong>Introduction</strong></h2>
<p>When you create a repo on GitHub, you will see the <code>license</code> option for your project. Although <em>license selection is not mandatory</em>, it can be important to determine how others can use your source code.</p>
<p>Has anyone ever wondered what this license means?</p>
<p>A license is a legal document that defines the rights of users to the source code of a project. There are many different types of licenses, each with different rights and restrictions.</p>
<h2 id="heading-github-licenses"><strong>GitHub Licenses</strong></h2>
<p>Each type of license has its own terms and conditions, but they are generally divided into two main categories.</p>
<ul>
<li><p><strong>Copyleft licenses</strong> require that all copies of the source code be distributed under the same license. This means that software licensed under GPL is always open source and free.</p>
</li>
<li><p><strong>Permissive licenses</strong> allow users to use and distribute the source code in any way they want, as long as they give credit to the author.</p>
</li>
</ul>
<p>When choosing a license, you need to consider the following factors:</p>
<ul>
<li><p><strong>How do you want others to be able to use your source code?</strong> Can they use it in commercial projects? Can they modify and redistribute your source code?</p>
</li>
<li><p><strong>How do you want to protect your copyright (intellectual property)?</strong> Some licenses can help you protect your copyright better than others.</p>
</li>
</ul>
<h2 id="heading-popular-github-licenses"><strong>Popular GitHub Licenses</strong></h2>
<h3 id="heading-apache-license-20"><strong>Apache License 2.0</strong></h3>
<p>Type: <em>Permissive</em></p>
<p>Allows: Others to use, copy, modify, distribute, and sell your source code without your permission. However, this license requires you to give credit to the author of the source code.</p>
<p>Commonly used by: Apache software projects, Android, OpenSSL, etc.</p>
<h3 id="heading-gnu-general-public-license-v30"><strong>GNU General Public License v3.0</strong></h3>
<p>Type: <em>Copyleft</em></p>
<p>Allows: Others to use, copy, modify, distribute, and sell your source code. However, this license requires that any software built on top of your source code also be licensed under GPLv3.</p>
<p>Commonly used by: Linux, GNU utilities, LibreOffice, etc.</p>
<h3 id="heading-mit-license"><strong>MIT License</strong></h3>
<p>Type: <em>Permissive</em></p>
<p>Allows: Others to use, copy, modify, distribute, and sell your source code without your permission. The main condition is to keep the copyright notice and not make any warranty.</p>
<p>Commonly used by: jQuery, Ruby on Rails, Node.js, etc.</p>
<h3 id="heading-bsd-2-clause-simplified-license"><strong>BSD 2-Clause "Simplified" License</strong></h3>
<p>Type: <em>Permissive</em> Allows: Others to use, copy, modify, distribute, and sell your source code without your permission. This license requires you to give credit to the author of the source code, but does not require you to distribute your source code under the same license. Commonly used by: BSD operating system, OpenBSD, SQLite, etc.</p>
<h3 id="heading-bsd-3-clause-new-or-revised-license"><strong>BSD 3-Clause "New" or "Revised" License</strong></h3>
<p>Type: <em>Permissive</em></p>
<p>Allows: Others to use, copy, modify, distribute, and sell your source code without your permission. Expanding from BSD 2-Clause, this license requires you to give credit to the author of the source code and distribute your source code under the same license, as well as requiring you not to use the names of the authors or organizations to promote the product.</p>
<p>Commonly used by: BSD operating system, Lua, Nginx, etc.</p>
<h3 id="heading-boost-software-license-10"><strong>Boost Software License 1.0</strong></h3>
<p>Type: <em>Permissive</em></p>
<p>Allows: Others to use, copy, modify, distribute, and sell your source code without your permission. This license requires you to give credit to the author of the source code, but does not require you to distribute your source code under the same license. Similar to MIT, but has an additional condition to avoid using the name, logo, or trademark of the authors or development organizations to promote the product.</p>
<p>Commonly used by: Boost libraries, many C++ projects</p>
<h3 id="heading-creative-commons-zero-v10-universal"><strong>Creative Commons Zero v1.0 Universal</strong></h3>
<p>Type: <em>Public</em></p>
<p>Allows: Others to use, copy, modify, distribute, and sell your source code without your permission. Why is the type of this license not under the two definitions above? It belongs to the public domain type, which means that all copyright and related rights are waived, maximizing the ability to reuse without restrictions.</p>
<p>Commonly used by: Creative works, open data, scientific publications</p>
<h3 id="heading-eclipse-public-license-20"><strong>Eclipse Public License 2.0</strong></h3>
<p>Type: <em>Copyleft</em></p>
<p>Allows: Others to use, copy, modify, distribute, and sell your source code without your permission. This license requires you to give credit to the author of the source code, but does not require you to distribute your source code under the same license. Commonly used by: Eclipse IDE, projects in the Eclipse Foundation.</p>
<h3 id="heading-gnu-affero-general-public-license-v30"><strong>GNU Affero General Public License v3.0</strong></h3>
<p>Type: <em>Copyleft</em></p>
<p>Allows: Others to use, copy, modify, distribute, and sell your source code without your permission, but they must also distribute their source code under the same license.</p>
<p>Meaning: Software licensed under AGPL is always free and open source software, even if it is distributed as a SaaS application.</p>
<h3 id="heading-gnu-general-public-license-v20"><strong>GNU General Public License v2.0</strong></h3>
<p>Type: <em>Copyleft</em></p>
<p>Allows: Others to use, copy, modify, distribute, and sell your source code without your permission, but they must also distribute their source code under the same license.</p>
<p>Meaning: Software licensed under GPL is always free and open source software.</p>
<h3 id="heading-gnu-lesser-general-public-license-v21"><strong>GNU Lesser General Public License v2.1</strong></h3>
<p>Type: <em>Copyleft</em></p>
<p>Allows: Others to use, copy, modify, distribute, and sell your source code without your permission, but only in free software applications. Meaning: The modified code can be modified and distributed, even in proprietary software, as long as the modified code is clearly separated from the proprietary components.</p>
<h3 id="heading-mozilla-public-license-20"><strong>Mozilla Public License 2.0</strong></h3>
<p>Type: <em>Permissive</em></p>
<p>Allows: Users to use, modify, and distribute the code, for both commercial and non-commercial purposes. Meaning: This license is compatible with other open source licenses, including GPL and Apache.</p>
<p>Commonly used by: This license is commonly used for web-based and browser-related software.</p>
<h3 id="heading-the-unlicense"><strong>The Unlicense</strong></h3>
<p>Allows: Code licensed under The Unlicense can be used in any software project, for any purpose. The code can also be modified and distributed without the author's permission.</p>
<p>Meaning: This license allows for unlimited use, modification, and distribution of the code, even for commercial purposes.</p>
<h2 id="heading-add-license-to-your-project"><strong>Add license to your project</strong></h2>
<p>After you have selected license, you can add license to your repo using the <code>LICENSE.md</code> file. This file should contain the name of the license, the version of a license and a link to the full text of the licence.</p>
<p><strong>For example:</strong> If you choose to use <code>MIT License</code>, you can add the following <code>LICENSE.md</code> file to your repo:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># MIT License</span>
Copyright (c) bunhere.com

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the <span class="hljs-string">"Software"</span>), to deal
<span class="hljs-keyword">in</span> the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to <span class="hljs-keyword">do</span> so, subject to the following conditions:

The above copyright notice and this permission notice shall be included <span class="hljs-keyword">in</span> all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED <span class="hljs-string">"AS IS"</span>, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
</code></pre>
<h2 id="heading-end"><strong>End</strong></h2>
<p>By choosing the right license for your repo, you can help ensure that your source code is used the way you want.</p>
<p>Research more here: <a target="_blank" href="https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/licensing-a-repository">licensing-a-repository</a></p>
<p>Find me: <a target="_blank" href="https://bunhere.com/">bunhere.com</a></p>
<p><em>I am always looking for feedback on my writing, so please let me know what you think.</em> ❤️</p>
]]></content:encoded></item><item><title><![CDATA[Make your website like an application with PWA]]></title><description><![CDATA[When I used Lighthouse to analyze my website, I found out that there was one criterion that my website could not meet, and that was PWA.
I'm starting to wonder what PWA is.
PWA (Progressive Web Apps) is a web app that uses progressive enhancement to ...]]></description><link>https://blog.bunhere.com/make-your-website-like-an-application-with-pwa</link><guid isPermaLink="true">https://blog.bunhere.com/make-your-website-like-an-application-with-pwa</guid><category><![CDATA[PWA]]></category><category><![CDATA[pwa web app development,]]></category><dc:creator><![CDATA[Emma Ngo]]></dc:creator><pubDate>Sun, 29 Oct 2023 17:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1715068978420/10fcf441-fa4b-4647-8225-a7e2707df6a9.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>When I used Lighthouse to analyze my website, I found out that there was one criterion that my website could not meet, and that was PWA.</p>
<p>I'm starting to wonder what PWA is.</p>
<p>PWA (Progressive Web Apps) is a web app that uses progressive enhancement to provide users with a more reliable experience, uses new capabilities to provide a more integrated experience, and can be installed. And, because it's a web app, it can reach anyone, anywhere, on any device, all with a single codebase.</p>
<p>You can find out more about PWA <a target="_blank" href="https://web.dev/learn/pwa">here</a></p>
<h2 id="heading-why-are-pwas-necessary">Why are PWAs necessary?</h2>
<p>You can see today's people focus on their phones. Developing apps with compatibility on phones helps web developers make their products more accessible to the majority of users.</p>
<p>Some examples of websites that use PWA are: </p>
<ul>
<li>Instagram </li>
<li>X (Twitter) </li>
<li>Spotify </li>
<li>Pinterest</li>
</ul>
<p>PWAs offer a number of benefits over traditional websites and native mobile apps. For users, PWAs provide a faster and more reliable experience. They are also more engaging and easier to use. For developers, PWAs are easier and faster to develop than native mobile apps. They are also more cost-effective to maintain and deploy.</p>
<h2 id="heading-setup-pwa-for-your-website">Setup PWA for your website</h2>
<h3 id="heading-build-with-nextjs">Build with NextJS</h3>
<p>I currently have a website built with nextJS, so I'll explain how I built my app with nextJS. (<a target="_blank" href="https://github.com/shadowwalker/next-pwa">document</a>)</p>
<pre><code>yarn add next-pwa
</code></pre><p>Then, update or create next.config.js with</p>
<pre><code><span class="hljs-keyword">const</span> withPWA = <span class="hljs-built_in">require</span>(<span class="hljs-string">'next-pwa'</span>)({
  <span class="hljs-attr">dest</span>: <span class="hljs-string">'public'</span>
})

<span class="hljs-built_in">module</span>.exports = withPWA({
  <span class="hljs-comment">// next.js config</span>
})
</code></pre><p>After running <code>next build</code>, this will generate two files in your public: <code>workbox-*.js</code> and <code>sw.js</code>, which will automatically be served statically.</p>
<p>![next build(https://miro.medium.com/v2/resize:fit:1184/format:webp/1*HOK7eY_6wHrQWyIjYFwdTw.png)</p>
<h3 id="heading-add-manifestjson-and-service-workerjs-files">Add manifest.json and service worker.js files</h3>
<ul>
<li>manifest.json file contains information about your PWA application, such as name, icon and landing page. </li>
<li>The service worker.js file is a JavaScript script that a web browser uses to provide PWA features, such as offline accessibility and push notifications.</li>
</ul>
<p>The manifest.json and service worker.js files are two important files to have for any PWA application. You can create these files using a text editor or a PWA generator.</p>
<p>Manifesto file.json must contain the following information:</p>
<ul>
<li><strong>Name</strong> of your PWA application.</li>
<li><strong>short_name</strong>: Short name of your PWA application.</li>
<li><strong>start_url</strong>: The landing page of your PWA application.</li>
<li><strong>icons</strong>: A list of icons for your PWA application.</li>
<li><strong>background_color</strong>: The background color of your PWA application.</li>
<li><strong>display</strong>: The display mode of your PWA application.</li>
</ul>
<pre><code>{
    <span class="hljs-string">"short_name"</span>: <span class="hljs-string">"BunBlog"</span>,
    <span class="hljs-string">"name"</span>: <span class="hljs-string">"Bunhere: Discorvery technical with Emma N."</span>,
    <span class="hljs-string">"icons"</span>: [
        {
            <span class="hljs-string">"src"</span>: <span class="hljs-string">"/images/icons-vector.svg"</span>,
            <span class="hljs-string">"type"</span>: <span class="hljs-string">"image/svg+xml"</span>,
            <span class="hljs-string">"sizes"</span>: <span class="hljs-string">"512x512"</span>
        },
        {
            <span class="hljs-string">"src"</span>: <span class="hljs-string">"/images/icons-192.webp"</span>,
            <span class="hljs-string">"type"</span>: <span class="hljs-string">"image/webp"</span>,
            <span class="hljs-string">"sizes"</span>: <span class="hljs-string">"192x192"</span>
        },
        {
            <span class="hljs-string">"src"</span>: <span class="hljs-string">"/images/icons-512.webp"</span>,
            <span class="hljs-string">"type"</span>: <span class="hljs-string">"image/webp"</span>,
            <span class="hljs-string">"sizes"</span>: <span class="hljs-string">"512x512"</span>
        }
    ],
    <span class="hljs-string">"id"</span>: <span class="hljs-string">"/?source=pwa"</span>,
    <span class="hljs-string">"start_url"</span>: <span class="hljs-string">"/?source=pwa"</span>,
    <span class="hljs-string">"background_color"</span>: <span class="hljs-string">"#3367D6"</span>,
    <span class="hljs-string">"display"</span>: <span class="hljs-string">"standalone"</span>,
    <span class="hljs-string">"scope"</span>: <span class="hljs-string">"/"</span>,
    <span class="hljs-string">"theme_color"</span>: <span class="hljs-string">"#3367D6"</span>,
    <span class="hljs-string">"shortcuts"</span>: [
        {
            <span class="hljs-string">"name"</span>: <span class="hljs-string">"What's the new blog for today?"</span>,
            <span class="hljs-string">"short_name"</span>: <span class="hljs-string">"Today"</span>,
            <span class="hljs-string">"description"</span>: <span class="hljs-string">"View the latest blog."</span>,
            <span class="hljs-string">"url"</span>: <span class="hljs-string">"/today?source=pwa"</span>,
            <span class="hljs-string">"icons"</span>: [
                {
                    <span class="hljs-string">"src"</span>: <span class="hljs-string">"/images/today.webp"</span>,
                    <span class="hljs-string">"sizes"</span>: <span class="hljs-string">"192x192"</span>
                }
            ]
        }
    ],
    <span class="hljs-string">"description"</span>: <span class="hljs-string">"Author information"</span>,
    <span class="hljs-string">"screenshots"</span>: [
        {
            <span class="hljs-string">"src"</span>: <span class="hljs-string">"/images/screenshot1.webp"</span>,
            <span class="hljs-string">"type"</span>: <span class="hljs-string">"image/webp"</span>,
            <span class="hljs-string">"sizes"</span>: <span class="hljs-string">"540x720"</span>,
            <span class="hljs-string">"form_factor"</span>: <span class="hljs-string">"narrow"</span>
        },
        {
            <span class="hljs-string">"src"</span>: <span class="hljs-string">"/images/screenshot2.webp"</span>,
            <span class="hljs-string">"type"</span>: <span class="hljs-string">"image/webp"</span>,
            <span class="hljs-string">"sizes"</span>: <span class="hljs-string">"720x540"</span>,
            <span class="hljs-string">"form_factor"</span>: <span class="hljs-string">"wide"</span>
        }
    ]
}
</code></pre><p>The service worker.js file must contain the following function calls:</p>
<ul>
<li><strong>register()</strong>: Register service worker with the web browser.</li>
<li><strong>install()</strong>: Download the resources needed for your PWA application to work offline.</li>
<li><strong>activate()</strong>: Enable service worker.</li>
</ul>
<h3 id="heading-create-a-icon-for-your-web-app">Create a icon for your web app</h3>
<p>Create icons and miniatures for your PWA application. These icons and miniatures will be displayed on the main screen of your mobile device.</p>
<p>You can create icons and miniatures for your PWA application using a design tool or an online <a target="_blank" href="https://web.dev/articles/maskable-icon">icon generator</a>.</p>
<p>Icons and miniatures must have the following sizes:</p>
<ul>
<li>Icons: 512x512 pixels</li>
<li>Miniature: 192x192 pixels, 144x144 pixels and 96x96 pixels</li>
</ul>
<h3 id="heading-add-head-meta">Add Head Meta</h3>
<p>Add the following into _document.jsx or _app.tsx, in <code>&lt;Head&gt;</code>:</p>
<pre><code>&lt;meta name=<span class="hljs-string">"application-name"</span> content=<span class="hljs-string">"BunBlog"</span> /&gt;
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"apple-mobile-web-app-capable"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"yes"</span> /&gt;</span></span>
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"apple-mobile-web-app-status-bar-style"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"default"</span> /&gt;</span></span>
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"apple-mobile-web-app-title"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"BunBlog"</span> /&gt;</span></span>
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"description"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"With bun, you can learn and discover interesting things about web programming technologies."</span> /&gt;</span></span>
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"format-detection"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"telephone=no"</span> /&gt;</span></span>
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"mobile-web-app-capable"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"yes"</span> /&gt;</span></span>
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"msapplication-config"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"/icons/browserconfig.xml"</span> /&gt;</span></span>
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"msapplication-TileColor"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"#2B5797"</span> /&gt;</span></span>
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"msapplication-tap-highlight"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"no"</span> /&gt;</span></span>
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"theme-color"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"#000000"</span> /&gt;</span></span>

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"apple-touch-icon"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/static/favicons/touch-icon-iphone.webp"</span> /&gt;</span></span>
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"apple-touch-icon"</span> <span class="hljs-attr">sizes</span>=<span class="hljs-string">"152x152"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/static/favicons/touch-icon-ipad.webp"</span> /&gt;</span></span>
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"apple-touch-icon"</span> <span class="hljs-attr">sizes</span>=<span class="hljs-string">"180x180"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/static/favicons/touch-icon-iphone-retina.webp"</span> /&gt;</span></span>
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"apple-touch-icon"</span> <span class="hljs-attr">sizes</span>=<span class="hljs-string">"167x167"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/static/favicons/touch-icon-ipad-retina.webp"</span> /&gt;</span></span>

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"icon"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"image/webp"</span> <span class="hljs-attr">sizes</span>=<span class="hljs-string">"32x32"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/static/favicons/favicon-32x32.webp"</span> /&gt;</span></span>
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"icon"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"image/webp"</span> <span class="hljs-attr">sizes</span>=<span class="hljs-string">"16x16"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/static/favicons/favicon-16x16.webp"</span> /&gt;</span></span>
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"manifest"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/manifest.json"</span> /&gt;</span></span>
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"mask-icon"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/static/favicons/safari-pinned-tab.svg"</span> <span class="hljs-attr">color</span>=<span class="hljs-string">"#5bbad5"</span> /&gt;</span></span>
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"shortcut icon"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/favicon.ico"</span> /&gt;</span></span>
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"stylesheet"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"https://fonts.googleapis.com/css?family=Roboto:300,400,500"</span> /&gt;</span></span>

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"twitter:card"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"summary"</span> /&gt;</span></span>
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"twitter:url"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"https://bunhere.com"</span> /&gt;</span></span>
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"twitter:title"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"BunBlog"</span> /&gt;</span></span>
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"twitter:description"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"With bun, you can learn and discover interesting things about web programming technologies."</span> /&gt;</span></span>
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"twitter:image"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"/static/favicons/android-chrome-192x192.webp"</span> /&gt;</span></span>
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"twitter:creator"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"@EmmaNgo"</span> /&gt;</span></span>
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">property</span>=<span class="hljs-string">"og:type"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"website"</span> /&gt;</span></span>
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">property</span>=<span class="hljs-string">"og:title"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"BunBlog"</span> /&gt;</span></span>
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">property</span>=<span class="hljs-string">"og:description"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"With bun, you can learn and discover interesting things about web programming technologies."</span> /&gt;</span></span>
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">property</span>=<span class="hljs-string">"og:site_name"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"BunBlog"</span> /&gt;</span></span>
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">property</span>=<span class="hljs-string">"og:url"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"https://bunhere.com"</span> /&gt;</span></span>
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">property</span>=<span class="hljs-string">"og:image"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"/static/favicons/apple-touch-icon.webp"</span> /&gt;</span></span>
</code></pre><h3 id="heading-link-to-your-apps-installation-page">Link to your app's installation page</h3>
<p>These links will allow users to install your PWA app on their mobile devices. You can add installed links to your website by using the <code>&lt;link&gt;</code> tags.</p>
<p>Example:</p>
<pre><code>&lt;link rel=<span class="hljs-string">"manifest"</span> href=<span class="hljs-string">"manifest.json"</span>&gt;
&lt;link rel="icon" type="image/webp" href="icon.webp"&gt;
&lt;link rel="shortcut icon" type="image/webp" href="icon.webp"&gt;
</code></pre><h3 id="heading-verifying-pwa-validity-amp-tips">Verifying PWA validity &amp; Tips</h3>
<p>After following these steps, you can check if your PWA application is working properly. To do this, visit your website then Inspect (F12) &gt; Lighthouse tab &gt; Analyze page load. If the PWA application works correctly, you will see a message that the application has been installed.</p>
<p><img src="https://miro.medium.com/v2/resize:fit:1400/format:webp/1*h-JncPJDP2rL_HZMoRMoZw.png" alt="PWA valid" /></p>
<p>Here are some additional tips for building a PWA from the available web:</p>
<ul>
<li>Using modern technologies: PWA is built on modern technologies, such as HTML5, CSS3 and JavaScript. If your website is built using older technologies, you may need to update it to support PWA.</li>
<li>Mobile optimization: PWA must be mobile optimized. This means that your website must be responsive and designed for use on a small screen.</li>
<li>Add PWA Features: You can add additional PWA features to your application, such as push notifications and offline access</li>
</ul>
<h3 id="heading-how-to-download-a-progressive-web-app">How to download a Progressive Web App?</h3>
<p>Here is how to download a Progressive Web App on an Apple</p>
<p><img src="https://miro.medium.com/v2/resize:fit:1400/format:webp/1*rtQw_yCZ7Nu-UJw689IzCA.png" alt="download apple" /></p>
<p>For Android</p>
<p><img src="https://miro.medium.com/v2/resize:fit:1400/format:webp/1*Fx1dmfz2Vq-PwqRIv1vFbw.png" alt="download android" /></p>
<h1 id="heading-end">End.</h1>
<p>References: <a target="_blank" href="https://web.dev/articles/what-are-pwas">web.dev</a></p>
<blockquote>
<p><em>I am always looking for feedback on my writing, so please let me know what you think.</em> ❤️</p>
</blockquote>
<p><strong>Follow me on:</strong></p>
<ul>
<li>Website: <a target="_blank" href="https://bunhere.com">bunhere.com</a></li>
<li>Medium: <a target="_blank" href="https://bunhere.medium.com/">bunhere.medium.com</a></li>
</ul>
]]></content:encoded></item></channel></rss>