<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: chloe</title>
    <description>The latest articles on DEV Community by chloe (@chloe123190241).</description>
    <link>https://dev.to/chloe123190241</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1960731%2F584efdda-a37c-40b1-9855-09b17ec50687.png</url>
      <title>DEV Community: chloe</title>
      <link>https://dev.to/chloe123190241</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/chloe123190241"/>
    <language>en</language>
    <item>
      <title>Building and deploying a Generative AI Support Chatbot with Flask and Google's Gemini API</title>
      <dc:creator>chloe</dc:creator>
      <pubDate>Sat, 31 May 2025 13:04:54 +0000</pubDate>
      <link>https://dev.to/chloe123190241/building-and-deploying-a-non-generative-ai-support-chatbot-with-flask-and-googles-gemini-api-2h99</link>
      <guid>https://dev.to/chloe123190241/building-and-deploying-a-non-generative-ai-support-chatbot-with-flask-and-googles-gemini-api-2h99</guid>
      <description>&lt;p&gt;Example of what is being built: &lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmf0l2moxz41rdwhz6bc1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmf0l2moxz41rdwhz6bc1.png" alt="Image description" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Introduction&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;As a backend developer specialising in AI chatbots, I recently built a production-ready GenAI chatbot that demonstrates enterprise-level security, scalability, and user experience. This project showcases my ability to create full-stack solutions that clients can trust with their business communications.&lt;/p&gt;

&lt;p&gt;You can see the live chatbot in action on [my website]&lt;a href="https://www.theoxforddeveloper.co.uk" rel="noopener noreferrer"&gt;https://www.theoxforddeveloper.co.uk&lt;/a&gt; - it's the orange chatbot on the bottom right (I also have a non-GenAI chatbot for comparison - the purple icon on the bottom right).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Challenge&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I needed to create a secure, embeddable GenAI chatbot that could:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Handle multiple clients with proper authentication&lt;/li&gt;
&lt;li&gt;Integrate seamlessly into any website&lt;/li&gt;
&lt;li&gt;Provide intelligent responses using Google's Gemini API&lt;/li&gt;
&lt;li&gt;Maintain British English consistency (important for UK business)&lt;/li&gt;
&lt;li&gt;Scale across multiple domains with CORS protection&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Architecture Overview&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Backend&lt;/strong&gt;: Flask + Google Gemini API (Deployed on Heroku)&lt;br&gt;
&lt;strong&gt;Frontend&lt;/strong&gt;: Vanilla JavaScript widget + Next.js integration&lt;br&gt;
&lt;strong&gt;Security&lt;/strong&gt;: Client ID authentication + CORS protection&lt;br&gt;
&lt;strong&gt;Deployment&lt;/strong&gt;: Heroku for backend, Vercel for website&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Backend Development&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Security-First Authentication System&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The foundation of any enterprise chatbot is robust security. I implemented a dual-layer authentication system:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Client ID validation decorator
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;require_client_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nd"&gt;@wraps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;decorated_function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;client_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

        &lt;span class="c1"&gt;# Multiple authentication methods
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;X-Client-ID&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;client_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;X-Client-ID&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;client_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;client_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;client_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_json&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;client_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;client_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;client_id&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;client_id&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;ALLOWED_CLIENT_IDS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;jsonify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Invalid client ID&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;botResponse&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;This chatbot is not authorised for your website.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="p"&gt;}),&lt;/span&gt; &lt;span class="mi"&gt;403&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;decorated_function&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key Features:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Client ID validation prevents unauthorised usage&lt;/li&gt;
&lt;li&gt;Flexible authentication (headers, query params, JSON body)&lt;/li&gt;
&lt;li&gt;Graceful error handling with user-friendly messages&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;CORS Configuration for Multi-Domain Support
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nc"&gt;CORS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;origins&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://oxford-genai-chatbot-9c5c571579c0.herokuapp.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://website-portfolio-iota-khaki.vercel.app&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost:8000&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost:3000&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;supports_credentials&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Intelligent Response Processing&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The chatbot integrates with Google's Gemini API while ensuring consistent British English output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;convert_to_british_english&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;conversions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;personalized&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;personalised&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;customization&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;customisation&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;organization&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;organisation&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="c1"&gt;# ... more conversions
&lt;/span&gt;    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;converted_text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;american&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;british&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;conversions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;regex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;\b&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;escape&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;american&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;\b&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;converted_text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;regex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;british&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;converted_text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;converted_text&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Business Value:&lt;/strong&gt; This ensures consistent brand voice for UK clients, demonstrating attention to detail in client requirements.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Dynamic Client Management
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/manage_clients&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;methods&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;POST&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;manage_clients&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="c1"&gt;# Admin authentication
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;admin_password&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;SECURE_PASSWORD_HERE&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;jsonify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Unauthorised&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}),&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt;

    &lt;span class="c1"&gt;# Dynamic client addition/removal
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;add&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;client_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;client_id&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;ALLOWED_CLIENT_IDS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;ALLOWED_CLIENT_IDS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;save_allowed_clients&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ALLOWED_CLIENT_IDS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Frontend Development&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Self-Contained Widget Architecture&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I created a completely self-contained JavaScript widget that can be embedded anywhere:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Widget configuration from script tag&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getScriptConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;scriptTag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;oxford-genai-chatbot&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;scriptTag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data-client-id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;scriptTag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data-theme&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;light&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;scriptTag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data-position&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bottom-right&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;apiEndpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;scriptTag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data-api-endpoint&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key Benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Zero external dependencies&lt;/li&gt;
&lt;li&gt;Configurable via data attributes&lt;/li&gt;
&lt;li&gt;Prevents conflicts with existing website code&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;Dynamic Styling System
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;styles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`
  .genai-chatbot-toggler {
    position: fixed !important;
    background: #f97316 !important;
    border-radius: 50% !important;
    z-index: 9999 !important;
    transition: all 0.3s ease !important;
  }

  .genai-chatbot {
    position: fixed !important;
    width: 420px !important;
    background: #fff !important;
    border-radius: 15px !important;
    box-shadow: 0 0 128px 0 rgba(0, 0, 0, 0.1) !important;
  }
`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Inject styles dynamically&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;styleElement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;style&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;styleElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;head&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;styleElement&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Robust Error Handling
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;generateResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chatElement&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;BACKEND_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;requestOptions&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;errorData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;errorData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;botResponse&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;messageElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;botResponse&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;messageElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;messageElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Sorry, I'm having trouble connecting. Please try again later.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Integration with Next.js
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Clean Component Architecture
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;use client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Script&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next/script&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;GenAIChatbotScript&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Script&lt;/span&gt;
      &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"https://oxford-genai-chatbot-9c5c571579c0.herokuapp.com/chatbot-widget.js"&lt;/span&gt;
      &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"oxford-genai-chatbot"&lt;/span&gt;
      &lt;span class="na"&gt;data-client-id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"oxford_developer_website"&lt;/span&gt;
      &lt;span class="na"&gt;data-theme&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"light"&lt;/span&gt;
      &lt;span class="na"&gt;data-position&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"bottom-left"&lt;/span&gt;
      &lt;span class="na"&gt;data-api-endpoint&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"https://oxford-genai-chatbot-9c5c571579c0.herokuapp.com"&lt;/span&gt;
      &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"afterInteractive"&lt;/span&gt;
      &lt;span class="na"&gt;onLoad&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GenAI Chatbot loaded successfully&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;onError&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Error loading GenAI chatbot:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Deployment Strategy&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Backend Deployment (Heroku)&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Environment Configuration&lt;/strong&gt;: Secure API keys via environment variables&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Production Optimisation&lt;/strong&gt;: Disabled debug mode, optimised CORS settings&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Health Monitoring&lt;/strong&gt;: Built-in error tracking and logging&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Frontend Integration (Vercel)&lt;/p&gt;

&lt;p&gt;The widget integrates seamlessly with my Next.js portfolio:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scss"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* Global styles for chatbot positioning */&lt;/span&gt;
&lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="nc"&gt;.genai-chatbot-toggler&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mh"&gt;#f97316&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;right&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;175px&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="nc"&gt;.chatbot-toggler&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mh"&gt;#724ae8&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;right&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100px&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Technical Highlights&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Multi-Chatbot Architecture&lt;br&gt;
I designed the system to support both GenAI and traditional rule-based chatbots on the same page, with distinct visual themes and positioning.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Security Through Obscurity + Authentication&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Client ID system prevents unauthorised API usage&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;CORS protection blocks cross-origin attacks&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Admin panel for dynamic client management&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;User Experience Focus&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Smooth animations and transitions&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Mobile-responsive design&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Consistent British English for UK market&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Visual text input feedback&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Production-Ready Error Handling&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Graceful API failure recovery&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;User-friendly error messages&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Comprehensive logging for debugging&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Results and Business Impact&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Technical Achievements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Zero-dependency, embeddable widget&lt;/li&gt;
&lt;li&gt;Sub-second response times&lt;/li&gt;
&lt;li&gt;100% uptime since deployment&lt;/li&gt;
&lt;li&gt;Scales across multiple client websites&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Business Value:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Demonstrates full-stack capabilities to potential clients&lt;/li&gt;
&lt;li&gt;Showcases AI integration expertise&lt;/li&gt;
&lt;li&gt;Proves ability to deliver production-ready solutions&lt;/li&gt;
&lt;li&gt;Shows attention to UK market requirements (British English)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Key Learnings&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Security First&lt;/strong&gt;: Implementing proper authentication from day one prevents future headaches&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Self-Contained Widgets&lt;/strong&gt;: Avoiding external dependencies makes integration seamless&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;User Experience Matters&lt;/strong&gt;: Small details like text visibility and smooth animations significantly impact perception&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Documentation Through Code&lt;/strong&gt;: Clean, readable code serves as its own documentation&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This GenAI chatbot project demonstrates my ability to create enterprise-level solutions that combine modern AI capabilities with robust security and excellent user experience. The modular architecture allows for easy scaling and customisation for different client needs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Technologies Used:&lt;/strong&gt; Flask, Google Gemini API, JavaScript ES6+, Next.js, SCSS, Heroku, Vercel&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Live Demo:&lt;/strong&gt; &lt;a href="https://www.theoxforddeveloper.co.uk" rel="noopener noreferrer"&gt;https://www.theoxforddeveloper.co.uk&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Looking for a developer who can deliver production-ready AI solutions? Feel free to connect with me to discuss your project requirements via the contact form at the bottom of my website &lt;a href="https://www.theoxforddeveloper.co.uk" rel="noopener noreferrer"&gt;https://www.theoxforddeveloper.co.uk&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Tags
&lt;/h2&gt;

&lt;h1&gt;
  
  
  ai #chatbot #flask #javascript #backend #frontend #heroku #nextjs
&lt;/h1&gt;

</description>
    </item>
    <item>
      <title>OCR: Digitise Physical Text Documents with Ease</title>
      <dc:creator>chloe</dc:creator>
      <pubDate>Tue, 29 Apr 2025 10:30:24 +0000</pubDate>
      <link>https://dev.to/chloe123190241/ocr-digitise-physical-text-documents-with-ease-8p</link>
      <guid>https://dev.to/chloe123190241/ocr-digitise-physical-text-documents-with-ease-8p</guid>
      <description>&lt;p&gt;Easily convert scanned or photographed documents into editable, machine-readable text using Optical Character Recognition (OCR).&lt;/p&gt;

&lt;p&gt;From this: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnk2er7hk20ypm457mcqd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnk2er7hk20ypm457mcqd.png" alt="Image description" width="800" height="1066"&gt;&lt;/a&gt;]&lt;/p&gt;

&lt;p&gt;To This:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F47vzdhyd46bn5pabu7sc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F47vzdhyd46bn5pabu7sc.png" alt="Image description" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Method 1: Using AWS Textract API&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This method leverages Amazon's powerful Textract OCR service to extract text from images. It works well for printed text, especially on structured documents.&lt;/p&gt;

&lt;p&gt;Features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Batch process multiple files&lt;/li&gt;
&lt;li&gt;Automatically saves extracted content to .txt files&lt;/li&gt;
&lt;li&gt;Supports line-by-line text extraction&lt;/li&gt;
&lt;li&gt;Easy to extend and integrate&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sample code: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F30zf2elgl07niap5u6n1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F30zf2elgl07niap5u6n1.png" alt="Image description" width="800" height="883"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Method 2: Pytesseract&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tools We Use&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Python&lt;/li&gt;
&lt;li&gt;OpenCV – For image processing&lt;/li&gt;
&lt;li&gt;Pytesseract – A Python wrapper for Google’s Tesseract-OCR Engine&lt;/li&gt;
&lt;li&gt;Pillow (PIL) – For additional image support&lt;/li&gt;
&lt;li&gt;CSV – To export results&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Before diving into the code, make sure you have Tesseract installed and properly linked in your script. For macOS users:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;pytesseract.pytesseract.tesseract_cmd = r'/opt/homebrew/bin/tesseract'&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Step 1: Image Preprocessing Pipeline&lt;/p&gt;

&lt;p&gt;OCR engines work best when the input text is clear, contrast-rich, and isolated from background noise. Here's the sequence of transformations applied to each image:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Grayscale Conversion&lt;br&gt;
Converting the image to grayscale helps reduce complexity and makes it easier to isolate text.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Sharpening&lt;br&gt;
We apply a Laplacian filter to enhance edges and make the text pop.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Inversion&lt;br&gt;
OCR engines often perform better when text is black on a white background.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Thresholding&lt;br&gt;
We binarise the image using Otsu’s method, separating text from background.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Denoising&lt;br&gt;
Removing small artifacts with a median blur filter. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Font Smoothing (Optional)&lt;br&gt;
If text appears too thin or thick, you can apply morphological operations (dilation/erosion).&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Step 2: OCR Text Extraction&lt;/p&gt;

&lt;p&gt;Once the image is processed, we convert it back to RGB (required by Tesseract) and extract the text. &lt;/p&gt;

&lt;p&gt;Step 3: Save to CSV&lt;/p&gt;

&lt;p&gt;Finally, we store the extracted text in a CSV file for later use. &lt;/p&gt;

&lt;p&gt;Sample code: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuxstcdv24g0cgf5a1z9m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuxstcdv24g0cgf5a1z9m.png" alt="Image description" width="800" height="735"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxad7jca38vo72vx35log.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxad7jca38vo72vx35log.png" alt="Image description" width="800" height="580"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What Next?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Once your text is extracted, check out my companion repository:&lt;br&gt;
&lt;a href="https://github.com/TheOxfordDeveloper/Parsing-unstructured-data.git" rel="noopener noreferrer"&gt;https://github.com/TheOxfordDeveloper/Parsing-unstructured-data.git&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;It shows how to clean and convert raw OCR text into structured, tabular formats, like this: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjqy60yo3hjqfa7xsjca5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjqy60yo3hjqfa7xsjca5.png" alt="Image description" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Parsing Unstructured Text Into Clean, Structured Data — A Regex-Powered Approach</title>
      <dc:creator>chloe</dc:creator>
      <pubDate>Tue, 29 Apr 2025 10:09:27 +0000</pubDate>
      <link>https://dev.to/chloe123190241/parsing-unstructured-text-into-clean-structured-data-a-regex-powered-approach-1e65</link>
      <guid>https://dev.to/chloe123190241/parsing-unstructured-text-into-clean-structured-data-a-regex-powered-approach-1e65</guid>
      <description>&lt;p&gt;Transforming messy, human-readable text into clean, structured rows and columns is one of the most tedious but necessary tasks in data science, especially when you're working with legacy formats or domain-specific archives. Recently, I built a custom parser to do exactly that—converting chaotic, delimiter-laden horse racing records into clean, structured CSV files using Python and regular expressions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Problem:&lt;/strong&gt;&lt;br&gt;
Imagine trying to extract meaningful data from a block of text that’s littered with inconsistent delimiters like hyphens, tabs, ellipses, and bullet points. That’s what the raw input looked like—unstructured text files full of valuable racing data, but formatted in a way that made it nearly impossible to work with programmatically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Tools: Python + Regex + Logic&lt;/strong&gt;&lt;br&gt;
Here’s how the process was broken down:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Preprocessing the Delimiters&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The first script scanned through each line in the raw .txt file and replaced noisy or ambiguous delimiters (-, ..., •, tabs, etc.) with clean commas. It also took care of collapsing multiple commas into one, providing a more consistent surface for parsing.&lt;/p&gt;

&lt;p&gt;i.e.,: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsd0i5p011pdikc1pwaj9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsd0i5p011pdikc1pwaj9.png" alt="Image description" width="800" height="985"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This produced something like this: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa10yf8s8ddooo8o7exhz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa10yf8s8ddooo8o7exhz.png" alt="Image description" width="800" height="500"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Regex Extraction&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Using a set of carefully crafted regular expressions, I extracted meaningful fields from each line—such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Horse name and year of birth&lt;/li&gt;
&lt;li&gt;Colour and sex&lt;/li&gt;
&lt;li&gt;Starts, wins, earnings&lt;/li&gt;
&lt;li&gt;Bloodline information (sire, dam, damsire)&lt;/li&gt;
&lt;li&gt;Breeding stats (foals, runners, winners, SW)&lt;/li&gt;
&lt;li&gt;Career length&lt;/li&gt;
&lt;li&gt;Stakes wins by age&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This wasn't just string matching—it included handling missing values, intelligently inferring information based on previous entries, and parsing complex compound phrases like At 3 won the Abadan Derby (G2), Winter Classic.&lt;/p&gt;

&lt;p&gt;i.e.,: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fybbztllbcfsk8gjrb2vt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fybbztllbcfsk8gjrb2vt.png" alt="Image description" width="800" height="464"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fevww6tr8w3p2azznxng3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fevww6tr8w3p2azznxng3.png" alt="Image description" width="800" height="867"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Multi-line Contextual Inference&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Some information wasn’t explicit in every row. For example, if a horse’s dam or damsire wasn’t listed, the script would backtrack up to 15 previous entries to find the most recent relevant female horse and assign her as the dam. Similarly, if the damsire was missing, it was inferred from the "previous-but-one" sire.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Export to CSV&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Finally, all structured data was written into a clean CSV file ready for downstream analysis.&lt;/p&gt;

&lt;p&gt;The Result&lt;br&gt;
By the end, I had a structured CSV with clearly defined columns for each key attribute of the horses' performance and lineage. This opens up all sorts of data-driven possibilities—analytics, visualisation, modelling, or feeding into other horse racing applications.&lt;/p&gt;

&lt;p&gt;i.e.,: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyxr2lbabcrp552xvqa00.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyxr2lbabcrp552xvqa00.png" alt="Image description" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why This Matters&lt;/strong&gt;&lt;br&gt;
Turning unstructured data into structured datasets is a fundamental skill in real-world data projects. This parser is tailored for horse racing records, but the principles—preprocessing, pattern recognition, intelligent inference—apply to any domain where data comes in messy formats.&lt;/p&gt;

&lt;p&gt;If you’re wrangling legacy text files, don’t underestimate what a few good regex patterns and a bit of logic can do.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Building a Headless URL Checker for Large-Scale Web Data Screening</title>
      <dc:creator>chloe</dc:creator>
      <pubDate>Tue, 29 Apr 2025 09:54:08 +0000</pubDate>
      <link>https://dev.to/chloe123190241/building-a-headless-url-checker-for-large-scale-web-data-screening-509o</link>
      <guid>https://dev.to/chloe123190241/building-a-headless-url-checker-for-large-scale-web-data-screening-509o</guid>
      <description>&lt;p&gt;When dealing with massive datasets of URLs—especially in industries like sports analytics, finance, or research—it’s not uncommon to encounter a frustrating pattern: a web page loads, appears valid to the server, but contains a polite “Sorry, we couldn’t find what you’re looking for” message. These kinds of false positives are time-consuming to verify manually, especially when the dataset spans tens of thousands of links.&lt;/p&gt;

&lt;p&gt;To solve this problem, I built a headless URL checker that scans web pages for meaningful content, distinguishing between truly valuable pages and placeholder or “no data” pages. This post outlines how the tool works, the features that make it cloud-ready and scalable, and how it handles the challenges of bot detection.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Problem: Valid URLs, Useless Content&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The URLs I worked with pointed to profile pages—some of which loaded correctly but returned a message like:&lt;/p&gt;

&lt;p&gt;"The Graded Stakes Profile you were searching for could not be found"&lt;br&gt;
These pages technically loaded (i.e., status code 200), but they didn’t actually contain the content I needed. I needed a way to automate the process of identifying these cases so I could focus only on URLs with actual data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Solution: Headless URL Checker&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The solution is a Python-based script (url_checker_HEADLESS.py) that uses Selenium with headless Chrome to programmatically load and inspect pages.&lt;/p&gt;

&lt;p&gt;Main Capabilities:&lt;br&gt;
Reads from a CSV: Input is a master file (Equibase_URLS.csv) containing ~30,000 URLs.&lt;br&gt;
Searches page content: Scans the page for a specific error message indicating no relevant data.&lt;br&gt;
Saves results: Records either "yes" (content found) or "no" (error message detected) in an output CSV.&lt;br&gt;
Here’s the logic in a nutshell:&lt;/p&gt;

&lt;p&gt;If the page contains the "could not be found" message → record "no".&lt;br&gt;
If the message is absent → record "yes", indicating this URL contains data we are interested in. &lt;/p&gt;

&lt;p&gt;Sample code: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faa15enge8xgqb4bztbq1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faa15enge8xgqb4bztbq1.png" alt="Image description" width="800" height="498"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwv9kvkie1yai5vbral8z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwv9kvkie1yai5vbral8z.png" alt="Image description" width="800" height="709"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F14h43vxqhnkf7bujgub3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F14h43vxqhnkf7bujgub3.png" alt="Image description" width="800" height="665"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Built for the Cloud (and for Stealth)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Scraping at scale comes with risks: IP blocking, captchas, and bot protection mechanisms. This checker includes several countermeasures:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Headless Chrome Browsing: Lightweight and fast for cloud-based execution.&lt;/li&gt;
&lt;li&gt;Randomised User Agents: Prevents fingerprinting.&lt;/li&gt;
&lt;li&gt;Session Cookie Injection: Uses a cookies.pkl file generated from a real browser session via generate_cookies.py.&lt;/li&gt;
&lt;li&gt;Random Delays: Introduces human-like delays between URL batches.&lt;/li&gt;
&lt;li&gt;Manual Captcha Mode: Supports fallback if a captcha appears during execution.&lt;/li&gt;
&lt;li&gt;Batch Processing: Splits the workload across smaller CSV chunks to avoid memory or session issues.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Output: Simple, Clear, and Actionable&lt;/p&gt;

&lt;p&gt;Each row in the output CSV includes:&lt;/p&gt;

&lt;p&gt;The original URL.&lt;br&gt;
A "yes" or "no" label indicating whether the page is worth further inspection. i.e.,: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq0xw2x2z7wptz1i4yosl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq0xw2x2z7wptz1i4yosl.png" alt="Image description" width="800" height="765"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Final Thoughts&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This tool has already saved me hours of manual inspection and allowed me to filter out tens of thousands of dead-end URLs with high precision. Whether you’re dealing with scraped web data or any large volume of dynamic content, having a smart, headless filter like this in your toolkit can make your workflow significantly more efficient—and a lot less painful.&lt;/p&gt;

&lt;p&gt;Full repo can be found here:  &lt;a href="https://github.com/TheOxfordDeveloper/URL-checker.git" rel="noopener noreferrer"&gt;https://github.com/TheOxfordDeveloper/URL-checker.git&lt;/a&gt; &lt;/p&gt;

</description>
    </item>
    <item>
      <title>Building and deploying a Non-Generative AI Support Chatbot with Flask</title>
      <dc:creator>chloe</dc:creator>
      <pubDate>Sat, 26 Apr 2025 19:26:54 +0000</pubDate>
      <link>https://dev.to/chloe123190241/building-a-non-generative-ai-support-chatbot-with-flask-oc5</link>
      <guid>https://dev.to/chloe123190241/building-a-non-generative-ai-support-chatbot-with-flask-oc5</guid>
      <description>&lt;p&gt;Here are some images demoing the bot: &lt;/p&gt;

&lt;p&gt;Step 1: ask it a question &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx3ujvtp4rgn4uvp3aj75.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx3ujvtp4rgn4uvp3aj75.png" alt="Image description" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Step 2: tell the bot whether the answer was useful or not. If no, support ticket process begins &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4i6xj7x4l1h7ehksotb5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4i6xj7x4l1h7ehksotb5.png" alt="Image description" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Step 3: you will receive an email confirming your support ticket has been received and that the support team will get back to you soon. The support team also receives an email so that they know you need help. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feqa0fyqksd67j7cpokdp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feqa0fyqksd67j7cpokdp.png" alt="Image description" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can test the bot on my website for yourself: &lt;a href="https://www.theoxforddeveloper.co.uk" rel="noopener noreferrer"&gt;https://www.theoxforddeveloper.co.uk&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In customer support, fast and reliable answers matter. I wanted to create a controlled, efficient support chatbot — one that doesn't rely on generative AI but instead uses traditional machine learning, information retrieval, and a custom-designed user interface for an interactive, predictible experience.&lt;/p&gt;

&lt;p&gt;In this project, I built a Flask web application that uses SVM classification, TF-IDF vectorisation, and Cosine Similarity to serve accurate responses from a custom knowledge base, and a fully custom frontend built in HTML, CSS, and JavaScript.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Problem to Solve&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Modern LLM-based bots are powerful, but they come with risks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hallucinated responses.&lt;/li&gt;
&lt;li&gt;Inconsistent tone.&lt;/li&gt;
&lt;li&gt;Data privacy concerns.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Many small businesses actually need a cheaper, smaller, safer, and more deterministic chatbot. &lt;br&gt;
This project addresses that gap.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Backend Tech Stack&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Python 3&lt;/li&gt;
&lt;li&gt;Flask (web server and API)&lt;/li&gt;
&lt;li&gt;Scikit-learn (for machine learning models)&lt;/li&gt;
&lt;li&gt;Pandas / Numpy (for data manipulation)&lt;/li&gt;
&lt;li&gt;TF-IDF vectorisation (for text representation)&lt;/li&gt;
&lt;li&gt;SVM (Support Vector Machine) (for classification)&lt;/li&gt;
&lt;li&gt;Cosine Similarity (for fine-grained matching)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Workflow and the Maths Behind It&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Preprocessing the Knowledge Base
The core of the chatbot is a CSV file containing common customer support questions and answers for a bank.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Before training:&lt;/p&gt;

&lt;p&gt;Stemming: Normalise words like "running" to "run".&lt;br&gt;
TF-IDF Vectorisation: Convert text into a matrix that captures the importance of terms.&lt;/p&gt;

&lt;p&gt;Here’s a simple example of the preprocessing pipeline:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fana1rfznueo45mlekv73.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fana1rfznueo45mlekv73.png" alt="Image description" width="800" height="782"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Classifying User Queries
Once the data is preprocessed, I trained a Support Vector Machine (SVM) classifier with a linear kernel:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqvg61jjoscjqjlojg016.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqvg61jjoscjqjlojg016.png" alt="Image description" width="800" height="879"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When a user submits a query:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;It’s preprocessed the same way (stemming + TF-IDF).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Then classified into a question category.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;Fine-Grained Matching within the Class
After finding the class, I zoom into the subset of related questions and use Cosine Similarity to identify the closest match. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This ensures fast and accurate matching even if user phrasing varies.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Handling Escalations: Automated Support Ticket System&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Even with the best machine learning models, some user queries might not be answered perfectly. To handle these cases professionally, I built an automated support ticket creation system inside the chatbot flow. When a user indicates that the chatbot's answer was not helpful, the bot automatically initiates a support ticket creation dialogue. &lt;/p&gt;

&lt;p&gt;How the Support Ticket Flow Works&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ask for the user's email:&lt;/li&gt;
&lt;li&gt;The bot prompts the user to input a valid email address. A simple email format check (@ and . present) is performed.&lt;/li&gt;
&lt;li&gt;Confirm or Update the Question: The user can either confirm the original query or provide additional detailed information.&lt;/li&gt;
&lt;li&gt;Generate and Log the Ticket: A unique ticket ID is created based on the timestamp.&lt;/li&gt;
&lt;li&gt;The ticket is saved using a helper function (save_support_ticket()).&lt;/li&gt;
&lt;li&gt;An email notification is sent to the user using send_email_notification() and sent to the support team. &lt;/li&gt;
&lt;li&gt;Handle Errors Gracefully: If something fails, users are informed politely, and the system logs the error and support ticket locally for admins.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sample code: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb2npnon2pgdiyiaf2ztj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb2npnon2pgdiyiaf2ztj.png" alt="Image description" width="800" height="726"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Each step is stateful — meaning the chatbot remembers where the user is in the conversation flow — and the system automatically progresses based on user input.&lt;/p&gt;

&lt;p&gt;This support ticket system enables the user to contact the support team correctly if the bot fails to answer their query to their liking. By combining smart automation with human fallback options, I built a chatbot that doesn't just answer questions — it ensures no customer is ever left behind.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Frontend Tech Stack and deployment&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HTML5 (structure)&lt;/li&gt;
&lt;li&gt;CSS3 (styling, animations)&lt;/li&gt;
&lt;li&gt;Vanilla JavaScript (dynamic behavior)&lt;/li&gt;
&lt;li&gt;Google Fonts and Material Symbols (icons)&lt;/li&gt;
&lt;li&gt;Responsive Design (for mobile and desktop&lt;/li&gt;
&lt;li&gt;Heroku to deploy&lt;/li&gt;
&lt;li&gt;JS widget for deployment &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Chatbot UI Features&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Clean, Toggleable Interface&lt;br&gt;
The chatbot toggles open and closed with a floating button, creating a minimal footprint when not in use. I used CSS transitions for smooth animations between states.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Modern, Card-Style Chat Window&lt;br&gt;
The main chat interface is styled like a mobile chat app. Messages are dynamically appended to the chatbox using JavaScript.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Fully Responsive Design&lt;br&gt;
On mobile screens, the chatbot resizes to full-screen mode automatically for better usability.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Asynchronous Communication with Backend&lt;br&gt;
When the user submits a message, JavaScript sends an HTTP request to the Flask server. &lt;br&gt;
The chatbot displays the server’s reply without reloading the page, creating a smooth, real-time experience.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Example of the JS widget: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7aehn1tf5uvih1dx3hsv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7aehn1tf5uvih1dx3hsv.png" alt="Image description" width="800" height="538"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Design Philosophy&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Speed: Lightweight frontend and backend.&lt;/li&gt;
&lt;li&gt;Simplicity: No frameworks; pure HTML/CSS/JS for maximum control.&lt;/li&gt;
&lt;li&gt;Professionalism: Clean UX and error handling.&lt;/li&gt;
&lt;li&gt;Expandability: Easy to add new questions, classes, or UI features.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What I Learned&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Classic machine learning techniques can deliver incredible value in well-defined domains.&lt;/li&gt;
&lt;li&gt;Frontend design plays a crucial role in user trust and experience.&lt;/li&gt;
&lt;li&gt;Building debug tools and fallback strategies improves reliability.&lt;/li&gt;
&lt;li&gt;Full-stack integration (Flask + Vanilla JS) made me appreciate how different layers must work together for a seamless product.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Final Thoughts&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This project shows that you don't always need the biggest AI models to solve real problems.&lt;br&gt;
With thoughtful backend logic and a polished, user-friendly frontend, you can build robust, safe, and beautiful ML experiences.&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
