<?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>Forem: Amnish Singh Arora</title>
    <description>The latest articles on Forem by Amnish Singh Arora (@amnish04).</description>
    <link>https://forem.com/amnish04</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%2F1155776%2F83c20e55-e954-4a36-a792-ae5f20a71488.jpeg</url>
      <title>Forem: Amnish Singh Arora</title>
      <link>https://forem.com/amnish04</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/amnish04"/>
    <language>en</language>
    <item>
      <title>Never lose valuable error context in JavaScript</title>
      <dc:creator>Amnish Singh Arora</dc:creator>
      <pubDate>Thu, 22 May 2025 01:00:45 +0000</pubDate>
      <link>https://forem.com/amnish04/never-lose-valuable-error-context-in-javascript-3aco</link>
      <guid>https://forem.com/amnish04/never-lose-valuable-error-context-in-javascript-3aco</guid>
      <description>&lt;p&gt;If you are a &lt;strong&gt;JavaScript developer&lt;/strong&gt;, you might have ran into error messages like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Failed to extract text from the file&lt;/li&gt;
&lt;li&gt;Failed to save entity&lt;/li&gt;
&lt;li&gt;And so on...&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But what exactly led to failing of an entire high-level operation?&lt;/p&gt;

&lt;p&gt;We don't know the &lt;strong&gt;answer&lt;/strong&gt; because we didn't do a good enough job to preserve the error context, when throwing a specific error that is a &lt;strong&gt;symptom&lt;/strong&gt; of a more low level &lt;strong&gt;cause&lt;/strong&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%2Fehgo8joe2rfo65m48bo7.gif" 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%2Fehgo8joe2rfo65m48bo7.gif" alt="Frustrated developer breaking PC" width="480" height="356"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For years, JavaScript developers have struggled to preserve appropriate error context when throwing exceptions, as there was no in-built mechanism provided by the language to attach error cause, unlike other languages like &lt;a href="https://stackoverflow.com/a/5800718/15406872" rel="noopener noreferrer"&gt;Java&lt;/a&gt;, &lt;a href="https://docs.python.org/3/library/exceptions.html#exception-context" rel="noopener noreferrer"&gt;Python&lt;/a&gt; and &lt;a href="https://learn.microsoft.com/en-us/dotnet/api/system.exception.innerexception?view=net-9.0" rel="noopener noreferrer"&gt;C#&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This problem was solved by &lt;strong&gt;Node&lt;/strong&gt;, when they finally added a &lt;a href="https://nodejs.org/api/errors.html#errorcause" rel="noopener noreferrer"&gt;cause property&lt;/a&gt; to their Error constructor in &lt;code&gt;v16.9.0&lt;/code&gt;, which means you could now throw exceptions like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;some-resource&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="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="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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Specific error message&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="na"&gt;cause&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means you now get to preserve the entire context of the original error that led to throwing of a high level exception (including its &lt;strong&gt;message&lt;/strong&gt; and &lt;strong&gt;stack trace&lt;/strong&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;p&gt; 1. The problem&lt;br&gt;
 2. The solution?&lt;br&gt;
 3. Installation&lt;br&gt;
 4. Configuration&lt;br&gt;
       4.1. VSCode Extension&lt;br&gt;
 5. Conclusion&lt;/p&gt;
&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;This feature has been available in Node for a while now (added in 2022), and although it solves the &lt;strong&gt;big problem&lt;/strong&gt; of not having a standard way to preserve error context, it introduces a &lt;strong&gt;new problem&lt;/strong&gt; as its completely optional to attach the error cause to new exceptions.&lt;/p&gt;
&lt;h2&gt;
  
  
  The solution?
&lt;/h2&gt;

&lt;p&gt;What we need is a static analysis tool to enforce this as a coding convention — ensuring developers always attach available causes when throwing new errors.&lt;/p&gt;

&lt;p&gt;While &lt;a href="https://eslint.org/" rel="noopener noreferrer"&gt;ESLint&lt;/a&gt; is the go-to tool for code quality in JavaScript, it doesn’t provide any built-in rule for this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;So I decided to write one myself!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Introducing &lt;a href="https://www.npmjs.com/package/eslint-plugin-error-cause" rel="noopener noreferrer"&gt;eslint-plugin-error-cause&lt;/a&gt; — an &lt;strong&gt;ESLint plugin&lt;/strong&gt; that flags any &lt;code&gt;throw new Error(...)&lt;/code&gt; calls that omit the cause when inside a catch block.&lt;/p&gt;

&lt;p&gt;Let's talk about how we can &lt;strong&gt;set this up&lt;/strong&gt; in our projects.&lt;/p&gt;
&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;p&gt;The very first thing you need to do is install the plugin and &lt;code&gt;eslint&lt;/code&gt; as dev dependencies in your project. This example uses &lt;a href="https://pnpm.io/" rel="noopener noreferrer"&gt;pnpm&lt;/a&gt;, but you could use a &lt;a href="https://nodesource.com/blog/nodejs-package-manager-comparative-guide-2024" rel="noopener noreferrer"&gt;package manager&lt;/a&gt; of your choice.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm add &lt;span class="nt"&gt;-D&lt;/span&gt; eslint eslint-plugin-error-cause
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Configuration
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1.&lt;/strong&gt; Once installed, create an &lt;code&gt;eslint.config.js&lt;/code&gt; file at the root of your project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2.&lt;/strong&gt; There are &lt;strong&gt;two ways&lt;/strong&gt; to add this plugin to your &lt;strong&gt;eslint&lt;/strong&gt; configuration.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; Use the in-built &lt;code&gt;recommended&lt;/code&gt; config, that enables &lt;code&gt;no-swallowed-error-cause&lt;/code&gt; rule with a severity level of &lt;code&gt;warn&lt;/code&gt;.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;errorCause&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;eslint-plugin-error-cause&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&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;eslint/config&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="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;errorCause&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;configs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;recommended&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Enable the rule manually with a severity level of your choice.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;errorCause&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;eslint-plugin-error-cause&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&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;eslint/config&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="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&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-cause&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;errorCause&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;:&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-cause/no-swallowed-error-cause&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;warn&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;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3.&lt;/strong&gt; Setup &lt;strong&gt;lint&lt;/strong&gt; scripts in your &lt;code&gt;package.json&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"lint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eslint ."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"lint:fix"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;"pnpm lint --fix"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;4.&lt;/strong&gt; Use the &lt;code&gt;lint&lt;/code&gt; script to report all the lint errors in your code. 🎊&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; pnpm lint

3:10  error    &lt;span class="s1"&gt;'rootError'&lt;/span&gt; is defined but never used                                 no-unused-vars
4:5   warning  Include the original caught error as the &lt;span class="sb"&gt;`&lt;/span&gt;cause&lt;span class="sb"&gt;`&lt;/span&gt; of the custom error  error-cause/no-swallowed-error-cause
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;5.&lt;/strong&gt; Use &lt;code&gt;--fix&lt;/code&gt; to fix any instances on &lt;code&gt;no-swallowed-error-cause&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; pnpm lint:fix
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  VSCode Extension
&lt;/h3&gt;

&lt;p&gt;Almost everyone uses &lt;strong&gt;ESLint's&lt;/strong&gt; VSCode extension to detect and fix lint errors in-place when coding.&lt;/p&gt;

&lt;p&gt;If you don't have it setup, follow these steps:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1.&lt;/strong&gt; Install the &lt;a href="https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint" rel="noopener noreferrer"&gt;ESLint&lt;/a&gt; extension.&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%2F7xv3rwpjfiq9gnt8zwx9.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%2F7xv3rwpjfiq9gnt8zwx9.png" alt="Eslint VSCode Extension" width="800" height="316"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2.&lt;/strong&gt; Add this setting to your workspace/user &lt;code&gt;settings.json&lt;/code&gt;. You could add/omit the JavaScript flavours based on your project needs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"eslint.validate": [
  "javascript",
  "javascriptreact",
  "typescript",
  "typescriptreact"
  ],
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3.&lt;/strong&gt; Now you can detect and fix &lt;strong&gt;swallowed error causes&lt;/strong&gt; using VSCode's UI.&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%2F604sg9nkv2v7ae8p5tb8.gif" 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%2F604sg9nkv2v7ae8p5tb8.gif" alt="Plugin in Action" width="800" height="307"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;So that's all about how you could setup my new plugin in your projects, and &lt;strong&gt;never again worry&lt;/strong&gt; about missing important error context when throwing custom exceptions!&lt;/p&gt;

&lt;p&gt;This plugin is still very new and might not cover some edge-cases. I welcome any interested readers to visit the GitHub repo, open issues, send in pull requests, or even give it a &lt;strong&gt;star&lt;/strong&gt; and share with your connections if this helped you in any way 😁&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Github:&lt;/strong&gt; &lt;a href="https://github.com/Amnish04/eslint-plugin-error-cause" rel="noopener noreferrer"&gt;https://github.com/Amnish04/eslint-plugin-error-cause&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NPM:&lt;/strong&gt; &lt;a href="https://www.npmjs.com/package/eslint-plugin-error-cause" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/eslint-plugin-error-cause&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>eslint</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Concluding OSD700</title>
      <dc:creator>Amnish Singh Arora</dc:creator>
      <pubDate>Sun, 21 Apr 2024 02:26:26 +0000</pubDate>
      <link>https://forem.com/amnish04/concluding-osd700-3eoh</link>
      <guid>https://forem.com/amnish04/concluding-osd700-3eoh</guid>
      <description>&lt;p&gt;My open source journey started about 8 months ago when I enrolled myself in &lt;a href="https://www.senecapolytechnic.ca/cgi-bin/subject?s1=OSD600" rel="noopener noreferrer"&gt;OSD600&lt;/a&gt;, a course delivered by the one and only &lt;a href="https://github.com/humphd" rel="noopener noreferrer"&gt;David Humphrey&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In that course, we learned about the basics of open source, like how to make good PRs and contribute to random open-source projects, and how to use &lt;a href="https://git-scm.com/" rel="noopener noreferrer"&gt;Git&lt;/a&gt; effectively in the process. We participated in events like &lt;a href="https://hacktoberfest.com/" rel="noopener noreferrer"&gt;Hacktoberfest&lt;/a&gt; that helped us embrace the &lt;strong&gt;spirit of open source&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This term, I enrolled myself in &lt;a href="https://www.senecapolytechnic.ca/cgi-bin/subject?s1=OSD700" rel="noopener noreferrer"&gt;OSD700&lt;/a&gt;, that acts as a second part of 600. This time, instead of jumping across random projects every time, we stuck to &lt;strong&gt;one project&lt;/strong&gt; and stuck to that.&lt;/p&gt;

&lt;p&gt;That project was what I have been writing about for last 4 months - &lt;a href="https://github.com/tarasglek/chatcraft" rel="noopener noreferrer"&gt;ChatCraft&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;My journey with ChatCraft and OSD700 has been super rewarding.&lt;/p&gt;

&lt;p&gt;Not only was it the most fun course I ever had, but:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;I was able to learn about a lot of cool technologies that I had no idea of before this. Some of them being &lt;a href="https://github.com/humphd/osd700-winter-2024/wiki/Tech-Stack-%E2%80%90-Cloudflare-Pages" rel="noopener noreferrer"&gt;Cloudfare Pages&lt;/a&gt;, &lt;a href="https://github.com/humphd/osd700-winter-2024/wiki/Tech-Stack-%E2%80%90-Cloudflare-R2" rel="noopener noreferrer"&gt;Cloudfare R2&lt;/a&gt;, &lt;a href="https://github.com/humphd/osd700-winter-2024/wiki/Tech-Stack-%E2%80%90-PNpM" rel="noopener noreferrer"&gt;PnPM&lt;/a&gt;, Secret Management with &lt;a href="https://github.com/humphd/osd700-winter-2024/wiki/Tech-Stack-%E2%80%90-SOPS-(Secrets-OPerationS)" rel="noopener noreferrer"&gt;SOPS&lt;/a&gt;, &lt;a href="https://github.com/humphd/osd700-winter-2024/wiki/Tech-Stack-%E2%80%90-Vite" rel="noopener noreferrer"&gt;Vite&lt;/a&gt;, and &lt;a href="https://github.com/humphd/osd700-winter-2024/wiki/Tech-Stack-%E2%80%90-WebAssembly-(WASM)" rel="noopener noreferrer"&gt;Web Assembly&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Working on ChatCraft was a great introduction to leveraging the power of AI models using OpenAI's API, as I was able to apply my learnings to a real project and implement &lt;strong&gt;cool features&lt;/strong&gt; along the way.&lt;/li&gt;
&lt;li&gt;I learned about common open source practices like running a triage meeting, the role of a Sheriff, and &lt;strong&gt;the recipe&lt;/strong&gt; of how people are attracted to an open source project. 
People want to be involved with &lt;strong&gt;projects that are alive&lt;/strong&gt;, where there's a community that is active.&lt;/li&gt;
&lt;li&gt;And I realized the role blog posts play not only in an individual's career, but in the growth of your projects (if you write about them). Writing about your work and projects is one of the best way to marketing your skills and your work.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This has been a true privilege working on a &lt;a href="https://github.com/tarasglek/chatcraft.org" rel="noopener noreferrer"&gt;cool project&lt;/a&gt; with a &lt;a href="https://github.com/humphd/osd700-winter-2024/wiki" rel="noopener noreferrer"&gt;great team&lt;/a&gt;, and under an &lt;a href="https://github.com/humphd" rel="noopener noreferrer"&gt;exceptional mentor&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Of course, this is not the end of my open source journey, but I see it as the best beginning possible.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Its very common to get comfortable with doing one thing after graduating (at a job), keep doing it until it becomes obsolete, and end your career by making that mistake.&lt;/p&gt;

&lt;p&gt;- &lt;a href="https://github.com/tarasglek" rel="noopener noreferrer"&gt;Taras&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I feel like I have already had such an experience, even though I haven't officially started. I've been working as an Angular Developer for almost 2 years now, continuing at the place where I got my coops. Before this course, I had become too comfortable with doing that one thing (Angular UIs) that I felt &lt;strong&gt;uncomfortable&lt;/strong&gt; in exploring different things, and I knew that was not good.&lt;/p&gt;

&lt;p&gt;Working on ChatCraft &lt;strong&gt;pulled me out&lt;/strong&gt; of that pit, and helped me realize that &lt;strong&gt;Open Source&lt;/strong&gt; is a great way to explore different technologies and stay up to date with the latest trends even after graduating and starting a job.&lt;/p&gt;

&lt;p&gt;I would like to end this post by once again thanking all my peers, Taras, and David for helping me have the have the best learnings possible on this journey 😄&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>reflection</category>
      <category>osd700</category>
      <category>osd600</category>
    </item>
    <item>
      <title>Improve Download Speeds with Concurrency</title>
      <dc:creator>Amnish Singh Arora</dc:creator>
      <pubDate>Sun, 21 Apr 2024 02:26:02 +0000</pubDate>
      <link>https://forem.com/amnish04/improve-download-speeds-with-concurrency-58hk</link>
      <guid>https://forem.com/amnish04/improve-download-speeds-with-concurrency-58hk</guid>
      <description>&lt;p&gt;This week, I came across an interesting problem while working on &lt;a href="https://github.com/tarasglek/chatcraft.org/issues/525" rel="noopener noreferrer"&gt;an issue&lt;/a&gt; in &lt;a href="https://github.com/tarasglek/chatcraft.org" rel="noopener noreferrer"&gt;ChatCraft&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;A few weeks ago, I added &lt;strong&gt;text to speech&lt;/strong&gt; support to the app, which when combined with other features, leads to some 😎 use cases.&lt;/p&gt;

&lt;p&gt;I have already written a few posts about it. Feel free to take a look for more context.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/amnish04/openai-has-text-to-speech-support-now-4mlp"&gt;OpenAI has Text to Speech Support now!&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/amnish04/an-audio-player-hook-for-your-react-app-4gn9"&gt;An Audio Player hook for your React App&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this post, I'll talk about a problem that I encountered with Text to Speech, how I solved it and leveraged &lt;a href="https://medium.com/@onejohi/concurrency-in-javascript-f5bb387708d8" rel="noopener noreferrer"&gt;Concurrency&lt;/a&gt; to optimize the performance of end result.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;p&gt; 1. The Problem&lt;br&gt;
 2. Breaking larger messages into Chunks&lt;br&gt;
       2.1. Algorithm for Natural Chunks&lt;br&gt;
       2.2. Using Text Chunks to generate Audio&lt;br&gt;
       2.3. Optimizing Download Speeds&lt;br&gt;
 3. Results&lt;br&gt;
 4. Release 2.0&lt;/p&gt;
&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;Text to Speech&lt;/strong&gt; feature had been working really well, until I encountered this error a few days ago.&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%2F42i1rr25cbkv59gasafw.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%2F42i1rr25cbkv59gasafw.png" alt="TTS Error" width="800" height="173"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What I was trying to do was &lt;strong&gt;download&lt;/strong&gt; an interview &lt;strong&gt;as audio&lt;/strong&gt; after importing its transcriptions to &lt;a href="https://chatcraft.org/c" rel="noopener noreferrer"&gt;ChatCraft&lt;/a&gt; using &lt;a href="https://dev.to/amnish04/introducing-the-idea-of-web-handlers-in-chatcraft-1b4i"&gt;Web Handlers&lt;/a&gt; (as simple as pasting Youtube video's url in prompt).&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%2Ffl97uqqili4yicbw21jv.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%2Ffl97uqqili4yicbw21jv.png" alt="Interview Transcriptions" width="800" height="368"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But while implementing the &lt;strong&gt;Download&lt;/strong&gt; and &lt;strong&gt;Speak&lt;/strong&gt; features for chat messages, I forgot to consider that &lt;a href="https://platform.openai.com/docs/guides/text-to-speech" rel="noopener noreferrer"&gt;OpenAI's TTS API&lt;/a&gt; might have a limit on how much it can process in a single request (&lt;strong&gt;4096&lt;/strong&gt; characters).&lt;/p&gt;

&lt;p&gt;That is the reason I got this error as I was sending the entire chat content in one request for processing.&lt;/p&gt;
&lt;h2&gt;
  
  
  Breaking larger messages into Chunks
&lt;/h2&gt;

&lt;p&gt;An obvious solution for this problem was to &lt;strong&gt;break&lt;/strong&gt; larger messages into &lt;strong&gt;smaller chunks&lt;/strong&gt;, send those chunks for &lt;strong&gt;audio generation&lt;/strong&gt;, concatenate the generated audio &lt;strong&gt;blob pieces&lt;/strong&gt; into a single blob, and finally &lt;strong&gt;download&lt;/strong&gt; the weaved audio on user's file system.&lt;/p&gt;

&lt;p&gt;On surface, this may seem like a very straightforward problem to solve, but there is another aspect to it. &lt;/p&gt;

&lt;p&gt;For the TTS model to generate &lt;strong&gt;natural audio&lt;/strong&gt;, the source it &lt;strong&gt;ingests&lt;/strong&gt; also needs to be natural. In other words, I couldn't just break larger sentences to a certain max length without considering scenarios that could lead to &lt;strong&gt;inaccurate results&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For example,&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Parts of a single word could end up in different chunks.&lt;/li&gt;
&lt;li&gt;Parts of the same sentence could end up in different chunks.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  Algorithm for Natural Chunks
&lt;/h3&gt;

&lt;p&gt;To go around this problem, I leveraged the &lt;a href="https://www.databricks.com/resources/ebook/tap-full-potential-llm?scid=7018Y000001Fi1CQAS&amp;amp;utm_medium=paid+search&amp;amp;utm_source=google&amp;amp;utm_campaign=17102589780&amp;amp;utm_adgroup=156370061428&amp;amp;utm_content=ebook&amp;amp;utm_offer=tap-full-potential-llm&amp;amp;utm_ad=661606835956&amp;amp;utm_term=nlp&amp;amp;gad_source=1&amp;amp;gclid=CjwKCAjwz42xBhB9EiwA48pT7-gJCnsBTAHEMSlRYn9vH-8Re1DwlMP0pVfxyURHlsv8Gz3ZK_h4VRoCDmUQAvD_BwE" rel="noopener noreferrer"&gt;Natural Language Processing&lt;/a&gt; library we are using in the project (&lt;a href="https://github.com/spencermountain/compromise" rel="noopener noreferrer"&gt;Compromise&lt;/a&gt;) to make sure that each chunk was less than a specified character length, and &lt;br&gt;
the chunks ended with a full sentence (as long as the sentence itself wasn't longer than the limit).&lt;/p&gt;

&lt;p&gt;Here's the &lt;strong&gt;algorithm&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;First, I try to break the entire text into sentences using the &lt;strong&gt;NLP&lt;/strong&gt; library mentioned above.&lt;/li&gt;
&lt;li&gt;Then I initialize a text buffer variable and start iterating over each sentence.&lt;/li&gt;
&lt;li&gt;If the adding the current sentence to the text buffer does not exceed the preferred chunk length, I add it to the buffer.&lt;/li&gt;
&lt;li&gt;If it does exceed the limit, I push the buffer content as a new chunk to &lt;code&gt;chunks&lt;/code&gt; array and assign the text buffer with the current sentence's value.&lt;/li&gt;
&lt;li&gt;There is still one exception here. If the current sentence's length itself is greater than preferred chunk length, I have to force break it into smaller chunks as there is no way to have a natural meaningful chunk at that point.&lt;/li&gt;
&lt;li&gt;Lastly, I return the list of generated chunks.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The &lt;strong&gt;code&lt;/strong&gt; looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;nlp&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;compromise/one&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;tokenize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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;sentences&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;nlp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&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="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&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;terms&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;nlp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;terms&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;out&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;array&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="nx"&gt;sentences&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;terms&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="cm"&gt;/**
 *
 * Tries to split the provided text into
 * an array of text chunks where
 * each chunk is composed of one or more sentences.
 *
 * The function attempts to limit each chunk to maximum
 * preferred characters.
 * If a single sentence exceeds preferred character length,
 * that sentence will be force broken into chunks of preferred length
 * with no guarantee that individual chunks make sense.
 *
 * @param text The text content that needs to be split into Chunks
 * @param maxCharsPerSentence Maximum number of characters preferred per chunk
 * @returns Array of text chunks
 */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getSentenceChunksFrom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;maxCharsPerSentence&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4096&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;sentences&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;tokenize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&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;chunks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;currentText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;for &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;sentence&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;sentences&lt;/span&gt;&lt;span class="p"&gt;)&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="nx"&gt;sentence&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;maxCharsPerSentence&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// If the sentence itself is greater than maxCharsPerSentence&lt;/span&gt;

      &lt;span class="c1"&gt;// Flush existing text buffer as a chunk&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentText&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentText&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;currentText&lt;/span&gt; &lt;span class="o"&gt;=&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="c1"&gt;// Force break the long sentence without caring&lt;/span&gt;
      &lt;span class="c1"&gt;// about natural language&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sentencePieces&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="nx"&gt;sentence&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RegExp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`.{1,&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;maxCharsPerSentence&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;b`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;g&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="p"&gt;[];&lt;/span&gt;

      &lt;span class="nx"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;sentencePieces&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Check if adding the new sentence to the buffer&lt;/span&gt;
      &lt;span class="c1"&gt;// exceeds the allowed limit.&lt;/span&gt;

      &lt;span class="c1"&gt;// If not, add another sentence to the buffer&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentText&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;sentence&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;maxCharsPerSentence&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;currentText&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s2"&gt;` &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;sentence&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Flush the buffer as a chunk&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentText&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentText&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nx"&gt;currentText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sentence&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;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="nx"&gt;currentText&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentText&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;currentText&lt;/span&gt; &lt;span class="o"&gt;=&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;chunks&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;h3&gt;
  
  
  Using Text Chunks to generate Audio
&lt;/h3&gt;

&lt;p&gt;Now that I was able to break large messages into &lt;strong&gt;natural language&lt;/strong&gt; chunks, it was time to generate audio clips using those and &lt;strong&gt;weave them&lt;/strong&gt; together into &lt;strong&gt;full&lt;/strong&gt; audio message for the user.&lt;/p&gt;

&lt;p&gt;Here's what I did:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleDownloadAudio&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useCallback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;messageContent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&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;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;messageContent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&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="nx"&gt;text&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;closeLoading&lt;/span&gt; &lt;span class="p"&gt;}&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;utilizeAlert&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;alertId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Downloading...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Please wait while we prepare your audio download.&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="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;textChunks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getSentenceChunksFrom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4096&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;audioClips&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Blob&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

        &lt;span class="k"&gt;for &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;textChunk&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;textChunks&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;audioClipUrl&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;textToSpeech&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nx"&gt;textChunk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nx"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textToSpeech&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;voice&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tts-1-hd&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
          &lt;span class="p"&gt;);&lt;/span&gt;

          &lt;span class="nx"&gt;audioClips&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&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;audioClipUrl&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;r&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;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;blob&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;audioClip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Blob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;audioClips&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;audioClips&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

        &lt;span class="nf"&gt;download&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nx"&gt;audioClip&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_message.&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;audioClip&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nx"&gt;audioClip&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nf"&gt;closeLoading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;alertId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Downloaded&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Message was downloaded as Audio&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;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;err&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&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="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nf"&gt;closeLoading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;alertId&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="na"&gt;title&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 while downloading audio&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&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;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;},&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="nx"&gt;info&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textToSpeech&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;voice&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you are wondering about the &lt;code&gt;textToSpeech&lt;/code&gt; function, I wrote about it in &lt;a href="https://dev.to/amnish04/openai-has-text-to-speech-support-now-4mlp"&gt;this post&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;After this change, &lt;a href="https://chatcraft.org/c/" rel="noopener noreferrer"&gt;ChatCraft&lt;/a&gt; could download content of &lt;strong&gt;infinite length&lt;/strong&gt; as audio.&lt;/p&gt;

&lt;p&gt;But... there was still one more problem with this.&lt;/p&gt;

&lt;h3&gt;
  
  
  Optimizing Download Speeds
&lt;/h3&gt;

&lt;p&gt;Even though breaking large messages into chunks solved our problem, the user experience was still not optimal as download speed for long messages was &lt;strong&gt;painfully&lt;/strong&gt; low.&lt;/p&gt;

&lt;p&gt;The reason was that the current algorithm only processed &lt;strong&gt;one chunk at a time&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;for &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;textChunk&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;textChunks&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;audioClipUrl&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;textToSpeech&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;textChunk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textToSpeech&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;voice&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tts-1-hd&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;audioClips&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&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;audioClipUrl&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;r&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;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;blob&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;It never struck me before until &lt;a href="https://blog.humphd.org/" rel="noopener noreferrer"&gt;my professor&lt;/a&gt; suggested that we could run these promises concurrently!&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%2Fijlkjrcdro92ums4avse.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%2Fijlkjrcdro92ums4avse.png" alt="Dave's Suggestion" width="800" height="264"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;He also asked to use a library called &lt;a href="https://www.npmjs.com/package/p-limit" rel="noopener noreferrer"&gt;p-limit&lt;/a&gt; to limit the number of &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all#description" rel="noopener noreferrer"&gt;concurrent promises&lt;/a&gt; at a time.&lt;/p&gt;

&lt;p&gt;I am not that knowledgeable about concurrency, so I asked &lt;strong&gt;ChatCraft&lt;/strong&gt; why would we need to limit concurrent execution.&lt;/p&gt;

&lt;p&gt;This is &lt;a href="https://chatcraft.org/api/share/Amnish04/99GTdjIDz73dfV3lUGUdT" rel="noopener noreferrer"&gt;what I got&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%2F4m7vgpu3jg8vrc46tg76.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%2F4m7vgpu3jg8vrc46tg76.png" alt="ChatCraft's Knowledge" width="800" height="395"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After understanding certain nuances of this approach, I modified my approach to concurrently generate &lt;strong&gt;8 audio chunks&lt;/strong&gt; at a time, and limited each message's length to a maximum of &lt;strong&gt;500 characters&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Here's my implementation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;textChunks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getSentenceChunksFrom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;500&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;audioClips&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Blob&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Blob&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;textChunks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Limit the number of concurrent tasks&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pLimit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;p-limit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="k"&gt;default&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;limit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;pLimit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Adjust the concurrency limit as needed&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tasks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;textChunks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;textChunk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;index&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;return&lt;/span&gt; &lt;span class="nf"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &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;audioClipUrl&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;textToSpeech&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;textChunk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textToSpeech&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;voice&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tts-1-hd&lt;/span&gt;&lt;span class="dl"&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;audioClip&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;audioClipUrl&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;r&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;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="nx"&gt;audioClips&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;audioClip&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;span class="c1"&gt;// Wait for all the tasks to complete&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tasks&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;audioClip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Blob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;audioClips&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;audioClips&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nf"&gt;download&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;audioClip&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_message.&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;audioClip&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;audioClip&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll notice that I am using a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import" rel="noopener noreferrer"&gt;dynamic import&lt;/a&gt; for &lt;strong&gt;p-limit&lt;/strong&gt;, to avoid unnecessarily increasing initial bundle size of the application.&lt;/p&gt;

&lt;p&gt;And I also had to make sure the generated chunks were in order by keeping track of indices, now that the execution was &lt;strong&gt;not sequential&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;

&lt;p&gt;With all these changes, I was able to download an audio clip that was more than &lt;strong&gt;30 minutes long&lt;/strong&gt; in just &lt;a href="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/we3w1jf7iteg8ubd9uia.png" rel="noopener noreferrer"&gt;less than a minute&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%2Fwe3w1jf7iteg8ubd9uia.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%2Fwe3w1jf7iteg8ubd9uia.png" alt="Fast Download" width="800" height="419"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I guess &lt;a href="https://taras.glek.net/" rel="noopener noreferrer"&gt;Taras&lt;/a&gt; is &lt;a href="https://github.com/tarasglek/chatcraft.org/pull/602#issuecomment-2066410697" rel="noopener noreferrer"&gt;excited&lt;/a&gt; about downloading an Audio Book next 😝&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%2Fu4voehu5rqfcq9m515ii.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%2Fu4voehu5rqfcq9m515ii.png" alt="Taras Excited" width="663" height="232"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Release 2.0
&lt;/h2&gt;

&lt;p&gt;We have been building on ChatCraft 1.0 for about 4 months now. After &lt;a href="https://github.com/tarasglek/chatcraft.org/releases" rel="noopener noreferrer"&gt;9 minor releases&lt;/a&gt;, it was finally time to do a &lt;a href="https://github.com/tarasglek/chatcraft.org/releases/tag/v2.0.0" rel="noopener noreferrer"&gt;major release&lt;/a&gt; this week and I am glad this work could go in that 🥳&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%2Fw9jp73ab6oxap33srxry.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%2Fw9jp73ab6oxap33srxry.png" alt="Release 2.0" width="800" height="434"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>javascript</category>
      <category>concurrency</category>
      <category>nlp</category>
    </item>
    <item>
      <title>ChatCraft 2.0 is almost Here!</title>
      <dc:creator>Amnish Singh Arora</dc:creator>
      <pubDate>Sun, 14 Apr 2024 00:03:33 +0000</pubDate>
      <link>https://forem.com/amnish04/chatcraft-20-is-almost-here-3b8a</link>
      <guid>https://forem.com/amnish04/chatcraft-20-is-almost-here-3b8a</guid>
      <description>&lt;p&gt;That's right. The &lt;strong&gt;BIG&lt;/strong&gt; 2.0 release for &lt;a href="https://github.com/tarasglek/chatcraft.org" rel="noopener noreferrer"&gt;ChatCraft&lt;/a&gt; is scheduled by the end of next week. We have been working to build on ChatCraft's existing functionality in its &lt;a href="https://github.com/tarasglek/chatcraft.org/tree/v1.1.0" rel="noopener noreferrer"&gt;v1.0&lt;/a&gt; state for almost 13 weeks now, and have successfully added lots of &lt;a href="https://www.youtube.com/watch?v=iW3JMSIf-Xo" rel="noopener noreferrer"&gt;cool features&lt;/a&gt; (even this post's cover photo is generated by &lt;a href="https://chatcraft.org/" rel="noopener noreferrer"&gt;ChatCraft&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;However, adding these features in such a short span of time has also led to some &lt;a href="https://en.wikipedia.org/wiki/Technical_debt" rel="noopener noreferrer"&gt;technical debt&lt;/a&gt; that needs to be cleaned up and &lt;a href="https://github.com/tarasglek/chatcraft.org/issues" rel="noopener noreferrer"&gt;subtle bugs&lt;/a&gt; that need to be fixed so any future contributors have a good experience working on this project.&lt;/p&gt;

&lt;p&gt;This week, I did some of the refactoring work, fixed a bug and also helped my peers to do the same by reviewing their PRs and help wherever possible.&lt;/p&gt;

&lt;p&gt;In this post, I will be briefly talking about what I've been up to, and what I plan to do this upcoming week so we can make our 2.0 release a success!&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;p&gt; 1. Fixing a Web Handlers related bug&lt;br&gt;
 2. Refactoring PasswordInput Component&lt;br&gt;
 3. Reviewing other PRs&lt;br&gt;
 4. Next Plans&lt;/p&gt;
&lt;h2&gt;
  
  
  Fixing a Web Handlers related bug
&lt;/h2&gt;

&lt;p&gt;For the past few weeks, I had been working on adding a new feature called &lt;a href="https://dev.to/amnish04/iterating-on-web-handlers-implementation-1e30"&gt;Web Handlers&lt;/a&gt; to ChatCraft. Even though there are many more ways to still extend that feature, I think its a good time to fix any bugs introduced with it before for now since a major release is coming up.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/tarasglek/chatcraft.org/issues/541" rel="noopener noreferrer"&gt;The issue&lt;/a&gt; was that when the user changed any of his previous messages and pressed &lt;strong&gt;Re-Ask&lt;/strong&gt; button, it would not invoke any Web Handlers even if the updated prompt text matched with one of the registered handlers' match pattern.&lt;/p&gt;

&lt;p&gt;The reason for this happening was that prompt text was not at all being passed to the &lt;strong&gt;resubmit&lt;/strong&gt; function which led to the message being sent to LLM by default.&lt;/p&gt;

&lt;p&gt;Hence, &lt;a href="https://github.com/tarasglek/chatcraft.org/pull/581" rel="noopener noreferrer"&gt;the fix&lt;/a&gt; was as simple as follows:&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%2Fuqq0dff5e5ndcqso28t8.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%2Fuqq0dff5e5ndcqso28t8.png" alt="Reask bug reason" width="455" height="133"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Refactoring PasswordInput Component
&lt;/h2&gt;

&lt;p&gt;Recently, &lt;a href="https://github.com/tarasglek/chatcraft.org/pull/530" rel="noopener noreferrer"&gt;support for custom AI providers&lt;/a&gt; was added to ChatCraft and in implementing the UI for providers selection, changes were made to the existing &lt;code&gt;RevealablePasswordInput&lt;/code&gt; component to make the appearance ideal for a particular use case.&lt;/p&gt;

&lt;p&gt;However, I felt that the code had some design issues.&lt;/p&gt;

&lt;p&gt;This is what it looked like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ComponentPropsWithRef&lt;/span&gt; &lt;span class="p"&gt;}&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;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;InputGroup&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;InputRightElement&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Button&lt;/span&gt; &lt;span class="p"&gt;}&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;@chakra-ui/react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;PasswordInputProps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ComponentPropsWithRef&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;Input&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;inputSize&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;md&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;isInvalid&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;PasswordInput&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;inputSize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;md&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isInvalid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;PasswordInputProps&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;show&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setShow&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&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;paddingRight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;inputSize&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2.5rem&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;4.5rem&lt;/span&gt;&lt;span class="dl"&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;paddingLeft&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;inputSize&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0.4rem&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&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;inputFieldSize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;inputSize&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;md&lt;/span&gt;&lt;span class="dl"&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;buttonSize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;inputSize&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;xs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sm&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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;InputGroup&lt;/span&gt; &lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;inputFieldSize&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;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Input&lt;/span&gt;
        &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;pr&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;paddingRight&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;pl&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;paddingLeft&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;show&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;password&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;focusBorderColor&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;isInvalid&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;red.500&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;blue.500&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;borderColor&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;isInvalid&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;red.500&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gray.200&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;isInvalid&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;isInvalid&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;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;InputRightElement&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;paddingRight&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;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Button&lt;/span&gt; &lt;span class="na"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"ghost"&lt;/span&gt; &lt;span class="na"&gt;h&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"1.75rem"&lt;/span&gt; &lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;buttonSize&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;onClick&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="nf"&gt;setShow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;show&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="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;show&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hide&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Show&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;InputRightElement&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;InputGroup&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;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;PasswordInput&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;1.&lt;/strong&gt; Programatically determining styles based on other styles and hard coding values inside component caused tight coupling between different styles making it hard to reuse in other cases in the future. (Would need to add more if statements every time a new appearance was required)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2.&lt;/strong&gt; In a way, it overrode Chakra's in-built styling system which is not good.&lt;/p&gt;

&lt;p&gt;Which is why I created a &lt;a href="https://github.com/tarasglek/chatcraft.org/pull/582" rel="noopener noreferrer"&gt;Pull Request&lt;/a&gt; to refactor that part.&lt;br&gt;
I tried to make use of Chakra's own design system as much as possible turning it into a more generic wrapper around the regular &lt;a href="https://v2.chakra-ui.com/docs/components/input" rel="noopener noreferrer"&gt;Input&lt;/a&gt; component provided by Chakra.&lt;br&gt;
In other words, &lt;code&gt;PasswordInput&lt;/code&gt; now behaves more like a component provided by &lt;a href="https://v2.chakra-ui.com/" rel="noopener noreferrer"&gt;Chakra&lt;/a&gt; itself since most of the styles/props are being mapped directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reviewing other PRs
&lt;/h2&gt;

&lt;p&gt;I also reviewed some Pull Requests opened by other contributors. &lt;br&gt;
Here's a list with hyperlinks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1.&lt;/strong&gt; &lt;a href="https://github.com/tarasglek/chatcraft.org/pull/579" rel="noopener noreferrer"&gt;Replaced Show/Hide button with Eye ReactIcons&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;2.&lt;/strong&gt; &lt;a href="https://github.com/tarasglek/chatcraft.org/pull/573" rel="noopener noreferrer"&gt;Feed icon should open raw feed in a new tab&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;3.&lt;/strong&gt; &lt;a href="https://github.com/tarasglek/chatcraft.org/pull/577" rel="noopener noreferrer"&gt;Fix toast message width for mobile devices&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can take a look if interested.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next Plans
&lt;/h2&gt;

&lt;p&gt;With all these contributions and many more, we were able to do a minor &lt;a href="https://github.com/tarasglek/chatcraft.org/releases/tag/v1.9.0" rel="noopener noreferrer"&gt;v1.9.0 release&lt;/a&gt; taking us one step closer &lt;strong&gt;2.0&lt;/strong&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%2F0cst37uvde6wv0raho3f.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%2F0cst37uvde6wv0raho3f.png" alt="v1.9.0 release" width="800" height="409"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We are &lt;strong&gt;very close&lt;/strong&gt; to a successful major release 😎&lt;/p&gt;

&lt;p&gt;Who else is excited? Any ChatCraft users here? Share your thoughts in comments! &lt;/p&gt;

</description>
      <category>opensource</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>react</category>
    </item>
    <item>
      <title>Adding a Code Editor to your React App</title>
      <dc:creator>Amnish Singh Arora</dc:creator>
      <pubDate>Mon, 08 Apr 2024 01:36:14 +0000</pubDate>
      <link>https://forem.com/amnish04/adding-a-code-editor-to-your-react-app-48ih</link>
      <guid>https://forem.com/amnish04/adding-a-code-editor-to-your-react-app-48ih</guid>
      <description>&lt;p&gt;For the last couple of weeks, I have been working on implementing the idea of &lt;a href="https://dev.to/amnish04/iterating-on-web-handlers-implementation-1e30#how-it-works"&gt;Web Handlers&lt;/a&gt; in &lt;a href="https://chatcraft.org/" rel="noopener noreferrer"&gt;ChatCraft&lt;/a&gt;. And last week, I finished setting up the initial functionality where users can save their configurations to extend ChatCraft's web handling capabilities.&lt;/p&gt;

&lt;p&gt;Since the configuration is done in &lt;a href="https://www.redhat.com/en/topics/automation/what-is-yaml" rel="noopener noreferrer"&gt;YAML&lt;/a&gt;, this week I decided to enhance that feature by replacing the regular &lt;code&gt;TextArea&lt;/code&gt; element I was using for saving configurations with a dedicated &lt;strong&gt;Code Editor&lt;/strong&gt; for a developer friendly experience, which I'll be talking about &lt;strong&gt;in this post&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;p&gt; 1. How it looked before&lt;br&gt;
 2. Replacing with a Code Editor&lt;br&gt;
       2.1. Adding to ChatCraft&lt;br&gt;
 3. Conclusion&lt;/p&gt;
&lt;h2&gt;
  
  
  How it looked before
&lt;/h2&gt;

&lt;p&gt;Before talking about what code editor component I used for this purpose, here's what the configuration modal looked like before this change.&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%2Fxoczuvl6gbfwngknuyx6.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%2Fxoczuvl6gbfwngknuyx6.png" alt="Old Look for Web Handlers" width="640" height="508"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Replacing with a Code Editor
&lt;/h2&gt;

&lt;p&gt;Now imagine you were forced to write your code in notepad. Naturally, it wouldn't be a pleasant experience 😝&lt;/p&gt;

&lt;p&gt;So our users don't have to go through that pain, I used a library called &lt;a href="https://uiwjs.github.io/react-codemirror/" rel="noopener noreferrer"&gt;react-codemirror&lt;/a&gt; to replace it with a code editor written for the web.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;From the official &lt;a href="https://codemirror.net" rel="noopener noreferrer"&gt;CodeMirror&lt;/a&gt; documentation:&lt;/p&gt;

&lt;p&gt;CodeMirror is a code editor component for the web. It can be used in websites to implement a text input field with support for many editing features, and has a rich programming interface to allow further extension.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This editor is &lt;strong&gt;highly configurable&lt;/strong&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%2Fy7ggijb4o9bu7s8dx9up.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%2Fy7ggijb4o9bu7s8dx9up.png" alt="CodeMirror" width="800" height="510"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;and &lt;strong&gt;supports&lt;/strong&gt; a whole bunch of &lt;strong&gt;languages/frameworks&lt;/strong&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%2Flvufg0pyjd81aeyz4gd8.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%2Flvufg0pyjd81aeyz4gd8.png" alt="Language Support" width="800" height="323"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And it is &lt;strong&gt;super simple&lt;/strong&gt; to add it to our projects.&lt;/p&gt;
&lt;h3&gt;
  
  
  Adding to ChatCraft
&lt;/h3&gt;

&lt;p&gt;The first step whenever we want to use an external package is, of course 🥁🥁🥁&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1.&lt;/strong&gt; to &lt;strong&gt;install that package&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install @uiw/react-codemirror --save
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2.&lt;/strong&gt; Next, just use the component wherever you need in your code like so&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;ReactCodeMirror&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;@uiw/react-codemirror&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;


&lt;span class="nx"&gt;ReactCodeMirror&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;webHandlerConfig&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;colorMode&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;editorHeight&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;onChange&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleConfigValueChange&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3.&lt;/strong&gt; Lastly, you need to set the language extension that you want to be supported by the code editor.&lt;/p&gt;

&lt;p&gt;What I noticed is that these language extension packages follow a pattern of the format&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"@codemirror/lang-&amp;lt;language_name_here&amp;gt;"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since I was dealing with YAML, I installed the following package&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install @codemirror/lang-yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and in the &lt;strong&gt;JSX&lt;/strong&gt;, just add to the extensions property of ReactCodeMirror.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;yaml&lt;/span&gt; &lt;span class="p"&gt;}&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;@codemirror/lang-yaml&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;span class="nx"&gt;ReactCodeMirror&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;webHandlerConfig&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;colorMode&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;extensions&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{[&lt;/span&gt;&lt;span class="nf"&gt;yaml&lt;/span&gt;&lt;span class="p"&gt;()]}&lt;/span&gt;
  &lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;editorHeight&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;onChange&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleConfigValueChange&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that's all it took to make it look 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%2Fovye222dopea74tlm2tc.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%2Fovye222dopea74tlm2tc.png" alt="ChatCraft Web Handlers config" width="800" height="767"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This was a brief overview of how you can use &lt;strong&gt;CodeMirror&lt;/strong&gt; editor in your React Applications.&lt;/p&gt;

&lt;p&gt;For full documentation, you can visit&lt;br&gt;
&lt;a href="https://codemirror.net/docs/" rel="noopener noreferrer"&gt;https://codemirror.net/docs/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This also went to ChatCraft's &lt;a href="https://github.com/tarasglek/chatcraft.org/releases/tag/v1.8.0" rel="noopener noreferrer"&gt;1.8.0 release&lt;/a&gt; which happened this week.&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%2F5bmq1ij9az8ruxfnt2ky.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%2F5bmq1ij9az8ruxfnt2ky.png" alt="ChatCraft 1.8.0 Release" width="800" height="776"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Hope this post offered some value to you.&lt;br&gt;
I am planning to work on some other cool stuff next week.&lt;/p&gt;

&lt;p&gt;Will be writing on it soon!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>react</category>
      <category>opensource</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Iterating on Web Handlers Implementation</title>
      <dc:creator>Amnish Singh Arora</dc:creator>
      <pubDate>Sun, 31 Mar 2024 18:56:28 +0000</pubDate>
      <link>https://forem.com/amnish04/iterating-on-web-handlers-implementation-1e30</link>
      <guid>https://forem.com/amnish04/iterating-on-web-handlers-implementation-1e30</guid>
      <description>&lt;p&gt;Last week, I wrote about introducing the idea of &lt;a href="https://dev.to/amnish04/introducing-the-idea-of-web-handlers-in-chatcraft-1b4i"&gt;Web Handlers&lt;/a&gt; in &lt;a href="https://chatcraft.org/" rel="noopener noreferrer"&gt;ChatCraft&lt;/a&gt;, and how I implemented an initial proof of concept with a hard-coded Web Handler.&lt;/p&gt;

&lt;p&gt;This week, after receiving some feedback, I extended upon that functionality by adding a form that allows users to configure as many Web Handlers as they wish in YAML, which are ultimately persisted in their browser's &lt;code&gt;localStorage&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;p&gt; 1. How it works 🏃&lt;br&gt;
       1.1. The Model&lt;br&gt;
       1.2. Ability to Configure&lt;br&gt;
       1.3. Executing Handlers&lt;br&gt;
       1.4. Making Sense of it&lt;br&gt;
 2. Looking Ahead 👀&lt;/p&gt;
&lt;h2&gt;
  
  
  How it works 🏃
&lt;/h2&gt;

&lt;p&gt;At this point, you might have been a little confused about what exactly I've been talking about all this time. Trust me, I was also confused when I signed up for this task.&lt;/p&gt;

&lt;p&gt;So, here's a quick demo 🥂&lt;/p&gt;
&lt;h3&gt;
  
  
  The Model
&lt;/h3&gt;

&lt;p&gt;If you remember from my &lt;a href="https://dev.to/amnish04/introducing-the-idea-of-web-handlers-in-chatcraft-1b4i"&gt;last post&lt;/a&gt;, I was mostly discussing about the model of Web Handlers.&lt;/p&gt;

&lt;p&gt;It looks something like this in its current state.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WebHandler&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;handlerUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HttpMethod&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;matchPattern&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;RegExp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;handlerUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;matchPattern&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;span class="nl"&gt;handlerUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HttpMethod&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;matchPattern&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;RegExp&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kr"&gt;string&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;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handlerUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;handlerUrl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;matchPattern&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;matchPattern&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nb"&gt;RegExp&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;matchPattern&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RegExp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;matchPattern&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;isMatchingHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;matchPattern&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&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;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;h3&gt;
  
  
  Ability to Configure
&lt;/h3&gt;

&lt;p&gt;Users will now have the ability to configure their own Web Handlers in YAML. &lt;/p&gt;

&lt;p&gt;Here's an example:&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%2F6c47imd5hbneztnt5sgi.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%2F6c47imd5hbneztnt5sgi.png" alt="Web Handlers Config" width="649" height="500"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Executing Handlers
&lt;/h3&gt;

&lt;p&gt;And the first matching handler gets executed. In my case, I have configured YouTube URLs to be handled by a mock api just for demonstration purposes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prompt:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://www.youtube.com/watch?v=bnFa4Mq5PAM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&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%2F4m36938cl2cio4bsx9pm.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%2F4m36938cl2cio4bsx9pm.png" alt="Handler Result" width="800" height="150"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Making Sense of it
&lt;/h3&gt;

&lt;p&gt;This might not make sense at first, but essentially, you can extend the functionality of &lt;strong&gt;ChatCraft&lt;/strong&gt; without the project implementing new features. You can write your own &lt;a href="https://aws.amazon.com/microservices/" rel="noopener noreferrer"&gt;Microservices&lt;/a&gt; to provide some functionality and register &lt;strong&gt;Web Handlers&lt;/strong&gt; in ChatCraft to send HTTP requests to your Microservice with your entered message attached to it.&lt;/p&gt;

&lt;p&gt;A more realistic example would be using &lt;a href="https://github.com/tarasglek" rel="noopener noreferrer"&gt;Taras&lt;/a&gt;'s &lt;a href="https://taras-scrape2md.web.val.run/" rel="noopener noreferrer"&gt;Scrape2Md&lt;/a&gt; Microservice as &lt;code&gt;handlerUrl&lt;/code&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%2Fo72ghs9j5o3umpv38un5.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%2Fo72ghs9j5o3umpv38un5.png" alt="Taras Microservice" width="643" height="501"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's try another YouTube URL now with updated config:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prompt:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://www.youtube.com/watch?v=tfSS1e3kYeo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt;&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%2Fp8rk6eh4o7rvnoast6ob.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%2Fp8rk6eh4o7rvnoast6ob.png" alt="Web handler response" width="800" height="184"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It gave me the lyrics of the song I pasted, and since we have Text to Speech in ChatCraft, I can ask &lt;strong&gt;one of our singers&lt;/strong&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%2Fh2weqbbhboxpd07ler0c.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%2Fh2weqbbhboxpd07ler0c.png" alt="our singers" width="691" height="402"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;to sing or download an &lt;strong&gt;instantly remixed&lt;/strong&gt; song for me 🎶🪩🤩&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%2Fp53vdoicugzbkg2mtir1.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%2Fp53vdoicugzbkg2mtir1.png" alt="Download Song" width="800" height="155"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Looking Ahead 👀
&lt;/h2&gt;

&lt;p&gt;You must have noticed that it is already kinda powerful, and still holds a lot of potential as I said in &lt;a href="https://dev.to/amnish04/introducing-the-idea-of-web-handlers-in-chatcraft-1b4i"&gt;my last post&lt;/a&gt; as well.&lt;/p&gt;

&lt;p&gt;The coding part for this was fairly simple as I just had to implement a UI for the functionality I wrote last week.&lt;/p&gt;

&lt;p&gt;If you really want to follow along, you can look this PR.&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_github-liquid-tag"&gt;
  &lt;h1&gt;
    &lt;a href="https://github.com/tarasglek/chatcraft.org/pull/519" rel="noopener noreferrer"&gt;
      &lt;img class="github-logo" alt="GitHub logo" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg"&gt;
      &lt;span class="issue-title"&gt;
        Implement initial version of web handlers
      &lt;/span&gt;
      &lt;span class="issue-number"&gt;#519&lt;/span&gt;
    &lt;/a&gt;
  &lt;/h1&gt;
  &lt;div class="github-thread"&gt;
    &lt;div class="timeline-comment-header"&gt;
      &lt;a href="https://github.com/Amnish04" rel="noopener noreferrer"&gt;
        &lt;img class="github-liquid-tag-img" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Favatars.githubusercontent.com%2Fu%2F78865303%3Fv%3D4" alt="Amnish04 avatar"&gt;
      &lt;/a&gt;
      &lt;div class="timeline-comment-header-text"&gt;
        &lt;strong&gt;
          &lt;a href="https://github.com/Amnish04" rel="noopener noreferrer"&gt;Amnish04&lt;/a&gt;
        &lt;/strong&gt; posted on &lt;a href="https://github.com/tarasglek/chatcraft.org/pull/519" rel="noopener noreferrer"&gt;&lt;time&gt;Mar 21, 2024&lt;/time&gt;&lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag-github-body"&gt;
      &lt;p&gt;This is my initial attempt in implementing the idea of what we can call web handlers as discussed in #507 .&lt;/p&gt;
&lt;p&gt;I have:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Written a &lt;code&gt;WebHandler&lt;/code&gt; class registering/searching web handlers and executing their logic i.e. sending request to &lt;code&gt;handler url&lt;/code&gt; based on request config (so far request method)&lt;/li&gt;
&lt;li&gt;Added logic to check if the prompt matches with &lt;strong&gt;match pattern&lt;/strong&gt; of one of the registered Web Handlers. The search is sequential as mentioned in the issue, and the first matched web handler processes the prompt url. If there is no match, the prompt is released to the regular control flow (slash command or sent to AI).&lt;/li&gt;
&lt;li&gt;Currently, I have hard coded 2 Web Handlers as mentioned in issue, but we can later create UI to configure as many as user wants in &lt;code&gt;localStorage&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Demo:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Prompt: &lt;code&gt;https://github.com/tarasglek/chatcraft.org/pull/519&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/tarasglek/chatcraft.org/assets/78865303/54a8904a-8dd0-41c6-b291-0013146fb11e"&gt;&lt;img width="960" alt="image" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Ftarasglek%2Fchatcraft.org%2Fassets%2F78865303%2F54a8904a-8dd0-41c6-b291-0013146fb11e"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;What I don't like is how we are currently allowing prompt like &lt;code&gt;import &amp;lt;url&amp;gt;&lt;/code&gt; to be registered as match patterns. This causes ambiguity and I had to write specialized logic to handle edge cases like&lt;/p&gt;
&lt;div class="highlight highlight-source-ts js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-s1"&gt;url&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-en"&gt;extractFirstUrl&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s1"&gt;url&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt; &lt;span class="pl-c1"&gt;??&lt;/span&gt; &lt;span class="pl-s"&gt;""&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt; &lt;span class="pl-c"&gt;// When the input is not a url itself&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;as an example.&lt;/p&gt;
&lt;p&gt;I feel like we should restrict web handler match patterns to just urls, and let import command serve its own purpose.&lt;/p&gt;
&lt;p&gt;Maybe I am wrong and don't understand fully where we are going with this.&lt;/p&gt;
&lt;p&gt;@humphd @tarasglek Please let me know what you think.&lt;/p&gt;

    &lt;/div&gt;
    &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/tarasglek/chatcraft.org/pull/519" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;Next week, I am planning to replace the &lt;strong&gt;TextArea&lt;/strong&gt; being used for configuration with a &lt;strong&gt;dedicated code editor&lt;/strong&gt; called &lt;a href="https://github.com/uiwjs/react-codemirror" rel="noopener noreferrer"&gt;CodeMirror&lt;/a&gt; for a better developer experience.&lt;/p&gt;

&lt;p&gt;I may also work on making the configuration more flexible by allowing more options to be configured for the handler.&lt;/p&gt;

&lt;p&gt;If you have any questions or suggestions, feel free to start a conversation in the comments!&lt;/p&gt;

&lt;p&gt;I will follow up with another post 🔜&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>webdev</category>
      <category>react</category>
      <category>experiment</category>
    </item>
    <item>
      <title>Introducing the idea of Web Handlers in ChatCraft</title>
      <dc:creator>Amnish Singh Arora</dc:creator>
      <pubDate>Sun, 24 Mar 2024 17:23:52 +0000</pubDate>
      <link>https://forem.com/amnish04/introducing-the-idea-of-web-handlers-in-chatcraft-1b4i</link>
      <guid>https://forem.com/amnish04/introducing-the-idea-of-web-handlers-in-chatcraft-1b4i</guid>
      <description>&lt;p&gt;Have you ever considered the concept of a Chatbot that not only enables interaction with &lt;a href="https://www.cloudflare.com/en-ca/learning/ai/what-is-large-language-model/" rel="noopener noreferrer"&gt;Language Models&lt;/a&gt; (LLMs), but also offers the capability to integrate your own microservices for the most personalized experience imaginable?&lt;/p&gt;

&lt;p&gt;This week, I came across &lt;a href="https://github.com/tarasglek/chatcraft.org/issues/507" rel="noopener noreferrer"&gt;an issue&lt;/a&gt; in &lt;a href="https://chatcraft.org/" rel="noopener noreferrer"&gt;ChatCraft&lt;/a&gt; that was asking for something similar.&lt;/p&gt;

&lt;p&gt;In this post, I'll discuss how I was able to design and implement an initial version of this feature.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;p&gt; 1. Background 💭&lt;br&gt;
       1.1. Spin Off 🧐&lt;br&gt;
 2. Implementation 🧑🏻‍💻&lt;br&gt;
 3. Upcoming 🔮&lt;/p&gt;
&lt;h2&gt;
  
  
  Background 💭
&lt;/h2&gt;

&lt;p&gt;I've been working on ChatCraft for a couple of months now and have already written &lt;a href="https://dev.to/amnish04/series/26122"&gt;many posts&lt;/a&gt; about it. It comes with many cool features apart from what a regular AI Chatbot offers, like ability to define custom &lt;a href="https://chatcraft.org/f/new" rel="noopener noreferrer"&gt;ChatCraft Functions&lt;/a&gt;, in-built &lt;strong&gt;utility commands&lt;/strong&gt; (use &lt;code&gt;/commands&lt;/code&gt; to get a list), text-to-speech, and many more.&lt;/p&gt;

&lt;p&gt;This is related to &lt;code&gt;/import&lt;/code&gt; command (an existing feature) that allows you to make request to any URL and loads the text response as a system chat message.&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%2Frbf9r5e3tfzouixdczt2.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%2Frbf9r5e3tfzouixdczt2.png" alt="Example Command" width="800" height="141"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&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%2Fkg63ai4pgtsxiag1z8ih.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%2Fkg63ai4pgtsxiag1z8ih.png" alt="Import Result 1" width="800" height="364"&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%2F7n2mqt9l9swi5vqzt75z.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%2F7n2mqt9l9swi5vqzt75z.png" alt="Import Result 2" width="800" height="626"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Spin Off 🧐
&lt;/h3&gt;

&lt;p&gt;The idea is to build something that extends this functionality.&lt;/p&gt;

&lt;p&gt;Here's how it would work:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We can allow the users to register various &lt;strong&gt;Web Handlers&lt;/strong&gt; with a &lt;strong&gt;match pattern&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;If the message matches any of the registered web handlers, an HTTP request is sent to the Web Handlers's &lt;strong&gt;handler url&lt;/strong&gt; based on the specified configuration.&lt;/li&gt;
&lt;li&gt;The received response is streamed into a new &lt;strong&gt;Chat Message&lt;/strong&gt; in &lt;a href="https://en.wikipedia.org/wiki/Markdown" rel="noopener noreferrer"&gt;Markdown&lt;/a&gt; format.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This might sound a little confusing, so let's try to understand with an example.&lt;/p&gt;

&lt;p&gt;Let's say you want the YouTube URLs to be handled in a certain way. You can write your own service or a &lt;a href="https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-function.html" rel="noopener noreferrer"&gt;serverless cloud function&lt;/a&gt; for processing. Then configure a &lt;strong&gt;ChatCraft Web Handler&lt;/strong&gt; matching YouTube URL chat messages (use match pattern) that makes a request to your service for custom functionality.&lt;br&gt;
This would allow you to &lt;strong&gt;plug and play&lt;/strong&gt; any custom behaviour in ChatCraft that is not supported by default. And if that feature becomes popular, maybe we can add if by default in ChatCraft 😉&lt;/p&gt;
&lt;h2&gt;
  
  
  Implementation 🧑🏻‍💻
&lt;/h2&gt;

&lt;p&gt;To implement an initial version of this concept, I started by designing a &lt;strong&gt;Web Handler&lt;/strong&gt; class defining the model and behaviour of this new entity.&lt;/p&gt;

&lt;p&gt;The model consists of 3 main properties - &lt;code&gt;handlerUrl&lt;/code&gt;, &lt;code&gt;request method&lt;/code&gt; and &lt;code&gt;matchPattern&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WebHandler&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;handlerUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HttpMethod&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;matchPattern&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;RegExp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;handlerUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;matchPattern&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;span class="nl"&gt;handlerUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HttpMethod&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;matchPattern&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;RegExp&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;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handlerUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;handlerUrl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;matchPattern&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;matchPattern&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;span class="p"&gt;...&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;enum&lt;/span&gt; &lt;span class="nx"&gt;HttpMethod&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;CONNECT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CONNECT&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;DELETE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;DELETE&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;GET&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GET&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;HEAD&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;HEAD&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;OPTIONS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;OPTIONS&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;PATCH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;PATCH&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;POST&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;PUT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;PUT&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;TRACE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;TRACE&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and some methods defining its behavior&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WebHandler&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;span class="nf"&gt;isMatchingHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;matchPattern&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;executeHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&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;requestUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handlerUrl&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;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URLSearchParams&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;params&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;url&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;requestUrl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;search&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&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;requestUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&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;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&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;text&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;trim&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;resultHeader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`**Web Handler**: [&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handlerUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;](&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handlerUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;)?url=[&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;](&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&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;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;resultHeader&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\n\n`&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="nf"&gt;getMatchingHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;WebHandler&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRegisteredHandlers&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;handler&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;handler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isMatchingHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="nf"&gt;getRegisteredHandlers&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;WebHandler&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// TODO: Fetch from localStorage&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;supportedHandlers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;WebHandler&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;handlerUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://taras-scrape2md.web.val.run/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HttpMethod&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;matchPattern&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/^https:&lt;/span&gt;&lt;span class="se"&gt;\/\/\S&lt;/span&gt;&lt;span class="sr"&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;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;supportedHandlers&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;This is a pretty simple model, and currently I just have a generic handler for all &lt;code&gt;https&lt;/code&gt; urls that redirects to a service written by &lt;strong&gt;Taras&lt;/strong&gt; (the product owner) i.e. &lt;a href="https://taras-scrape2md.web.val.run/" rel="noopener noreferrer"&gt;https://taras-scrape2md.web.val.run/&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here's a detailed blog post for this tool&lt;br&gt;
&lt;a href="https://www.val.town/v/taras/scrape2md" rel="noopener noreferrer"&gt;https://www.val.town/v/taras/scrape2md&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With Web Handlers, we can now use the power of this tool in ChatCraft itself. Isn't that cool?&lt;/p&gt;

&lt;h2&gt;
  
  
  Upcoming 🔮
&lt;/h2&gt;

&lt;p&gt;There's still a lot of potential in this idea that needs to be harnessed. I am thinking of introducing a custom UI for registering and configuring these web handlers, just like we currently do with &lt;a href="https://chatcraft.org/f/new" rel="noopener noreferrer"&gt;ChatCraft Functions&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%2Fnr125hz1nmkfu2cmippo.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%2Fnr125hz1nmkfu2cmippo.png" alt="ChatCraft Functions" width="800" height="382"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I am pretty sure, there are things I can do better and maybe a better direction I can take this in. This concept is still too young for &lt;a href="https://github.com/tarasglek/chatcraft.org" rel="noopener noreferrer"&gt;ChatCraft&lt;/a&gt; and if you guys have any suggestions, let me know in comments.&lt;/p&gt;

&lt;p&gt;I'll follow up with another post soon!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>opensource</category>
      <category>react</category>
      <category>programming</category>
    </item>
    <item>
      <title>Getting an Open Source Project ON Track for Release</title>
      <dc:creator>Amnish Singh Arora</dc:creator>
      <pubDate>Sat, 09 Mar 2024 22:52:54 +0000</pubDate>
      <link>https://forem.com/amnish04/getting-an-open-source-project-on-track-for-release-5hk5</link>
      <guid>https://forem.com/amnish04/getting-an-open-source-project-on-track-for-release-5hk5</guid>
      <description>&lt;p&gt;Over the past couple of months, we have &lt;a href="https://github.com/tarasglek/chatcraft.org/releases" rel="noopener noreferrer"&gt;released&lt;/a&gt; 4 minor versions of &lt;a href="https://github.com/tarasglek/chatcraft.org" rel="noopener noreferrer"&gt;ChatCraft&lt;/a&gt;, but this last release was a little different.&lt;/p&gt;

&lt;p&gt;Due to unforeseen circumstances, the release &lt;strong&gt;v1.4.0&lt;/strong&gt; had to be delayed and this week, we tried to cover up for the loss of one week.&lt;/p&gt;

&lt;p&gt;In this post, I'll discuss my &lt;strong&gt;contributions&lt;/strong&gt; towards the latest release.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;p&gt; 1. Refactoring sentence parsing using an NLP library&lt;br&gt;
 2. Fixing an annoying UI bug 🐞&lt;br&gt;
 3. Reviewing a PR aimed at Refactoring Providers&lt;br&gt;
 4. Release v1.4.0&lt;/p&gt;
&lt;h2&gt;
  
  
  Refactoring sentence parsing using an NLP library
&lt;/h2&gt;

&lt;p&gt;A few weeks ago, I &lt;a href="https://github.com/tarasglek/chatcraft.org/pull/357" rel="noopener noreferrer"&gt;implemented&lt;/a&gt; &lt;strong&gt;text-to-speech&lt;/strong&gt; for ChatCraft and I have been writing about the related follow-ups in my past few posts.&lt;/p&gt;

&lt;p&gt;One of the &lt;a href="https://github.com/tarasglek/chatcraft.org/issues/386" rel="noopener noreferrer"&gt;follow-ups&lt;/a&gt; required me to refactor the manual &lt;a href="https://github.com/tarasglek/chatcraft.org/pull/357#issuecomment-1913048396" rel="noopener noreferrer"&gt;sentence parsing algorithm&lt;/a&gt; I implemented with the help of a &lt;a href="https://www.ibm.com/topics/natural-language-processing" rel="noopener noreferrer"&gt;Natural Language Processing&lt;/a&gt; library called &lt;a href="https://github.com/spencermountain/compromise" rel="noopener noreferrer"&gt;compromise&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%2Fiiyx2cu6u1non5d9yij6.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%2Fiiyx2cu6u1non5d9yij6.png" alt="Compromise Library" width="800" height="487"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It was a good idea to use this library as it was already part of our &lt;strong&gt;project tree&lt;/strong&gt; and provided a human-friendly API that is easy to understand thanks to its &lt;a href="https://en.wikipedia.org/wiki/Declarative_programming" rel="noopener noreferrer"&gt;declarative nature&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Set a maximum words in a sentence that we need to wait for.&lt;/span&gt;
&lt;span class="c1"&gt;// This reduces latency and number of TTS api calls&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;TTS_BUFFER_THRESHOLD&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// To calculate the current position in the AI generated text stream&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;ttsCursor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;ttsWordsBuffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&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;chat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;chatWithLLM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;messages&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;span class="p"&gt;...&lt;/span&gt;
  &lt;span class="nf"&gt;onPause&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;setPaused&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nf"&gt;onResume&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;setPaused&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nf"&gt;onData&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;currentText&lt;/span&gt; &lt;span class="p"&gt;})&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;pausedRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Hook tts code here&lt;/span&gt;
      &lt;span class="nx"&gt;ttsWordsBuffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;currentText&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ttsCursor&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;sentences&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;tokenize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ttsWordsBuffer&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="nx"&gt;ttsSupported&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nf"&gt;getSettings&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;announceMessages&lt;/span&gt;&lt;span class="p"&gt;)&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="nx"&gt;sentences&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="c1"&gt;// Has one full sentence&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="c1"&gt;// Pass the sentence to tts api for processing&lt;/span&gt;
          &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;textToBeProcessed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sentences&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&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;audioClipUri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;textToSpeech&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;textToBeProcessed&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="nf"&gt;addToAudioQueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;audioClipUri&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

          &lt;span class="c1"&gt;// Update the tts Cursor&lt;/span&gt;
          &lt;span class="nx"&gt;ttsCursor&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;sentences&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;nlp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ttsWordsBuffer&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;terms&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;out&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;array&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;TTS_BUFFER_THRESHOLD&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="c1"&gt;// Try to break the large sentence into clauses&lt;/span&gt;
          &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;clauseToProcess&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;nlp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ttsWordsBuffer&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;clauses&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;out&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;array&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&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;audioClipUri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;textToSpeech&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;clauseToProcess&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="nf"&gt;addToAudioQueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;audioClipUri&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

          &lt;span class="nx"&gt;ttsCursor&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;clauseToProcess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&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;span class="nf"&gt;setStreamingMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ChatCraftAiMessage&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;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nf"&gt;incrementScrollProgress&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;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There was no change in behaviour, but it helped make the code easy to maintain. For more details, check out this &lt;a href="https://github.com/tarasglek/chatcraft.org/pull/486" rel="noopener noreferrer"&gt;Pull Request&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%2F7pvaz93ktics5lqerhjs.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%2F7pvaz93ktics5lqerhjs.png" alt="NLP PR" width="800" height="477"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Fixing an annoying UI bug 🐞
&lt;/h2&gt;

&lt;p&gt;A few weeks ago, &lt;a href="https://github.com/tarasglek/chatcraft.org/pull/374" rel="noopener noreferrer"&gt;we replaced&lt;/a&gt; the existing &lt;a href="https://chakra-ui.com/docs/components/menu" rel="noopener noreferrer"&gt;Chakra Menus&lt;/a&gt; with the one provided by &lt;a href="https://szhsin.github.io/react-menu/" rel="noopener noreferrer"&gt;szhsin/react-menu&lt;/a&gt; library.&lt;/p&gt;

&lt;p&gt;While we were able to successfully create a wrapper that very closely mimicked the previous menus, it introduced a pretty annoying auto-scroll bug, as demonstrated in &lt;a href="https://github.com/tarasglek/chatcraft.org/pull/374#issuecomment-1920344354" rel="noopener noreferrer"&gt;this issue&lt;/a&gt; with the help of &lt;strong&gt;GIFs&lt;/strong&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%2Feiwycgyufhudtk9xofqm.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%2Feiwycgyufhudtk9xofqm.png" alt="Autoscroll bug" width="800" height="574"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is what I found with my research:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1.&lt;/strong&gt; The scrollbar issue was happening as the menu was positioned in a manner that made if overflow horizontal in its parent container. To fix that I have used the align property to make sure it stays inside the container's available space.&lt;br&gt;
&lt;strong&gt;2.&lt;/strong&gt; The auto-scroll issue was occurring when the list of options in the submenu was pretty long and the first list element tried to gain focus automatically. I have capped the max-height of submenus and added vertical scrollbars if the list grows beyond that limit. I made sure that it aligns with &lt;a href="https://balsamiq.com/learn/ui-control-guidelines/scrollbars/#scrolling-in-a-dropdown" rel="noopener noreferrer"&gt;recommended UX guidelines&lt;/a&gt; before making the decision.&lt;/p&gt;

&lt;p&gt;For more details, visit this &lt;a href="https://github.com/tarasglek/chatcraft.org/pull/487" rel="noopener noreferrer"&gt;Pull Request&lt;/a&gt; that fixes the issue.&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%2Fuxc2e05d03o1x6m35l4t.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%2Fuxc2e05d03o1x6m35l4t.png" alt="UI bug PR" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Reviewing a PR aimed at Refactoring Providers
&lt;/h2&gt;

&lt;p&gt;Alright, this week's been full of refactoring. I guess we do have to stop and pay our technical debts at some point :D&lt;/p&gt;

&lt;p&gt;The work on this PR has been going on for 2 weeks, and I have been reviewing it as more changes come in.&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%2F4dgddd8dh5ki7lyx4z98.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%2F4dgddd8dh5ki7lyx4z98.png" alt="Reviewing Katie's PR" width="800" height="590"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/katiel"&gt;Katie&lt;/a&gt; has written about this &lt;a href="https://github.com/tarasglek/chatcraft.org/pull/479" rel="noopener noreferrer"&gt;Pull Request&lt;/a&gt; in &lt;a href="https://dev.to/katiel/chatcraft-week-8-successful-refactoring-pr-h2d"&gt;this post&lt;/a&gt;. Feel free to check out for more details.&lt;/p&gt;

&lt;h2&gt;
  
  
  Release v1.4.0
&lt;/h2&gt;

&lt;p&gt;Finally, even after a week of delay, we were able to &lt;a href="https://github.com/tarasglek/chatcraft.org/releases/tag/v1.4.0" rel="noopener noreferrer"&gt;release v.1.4.0&lt;/a&gt; for ChatCraft.&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%2Ff3gep02cgey3gmf5dswv.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%2Ff3gep02cgey3gmf5dswv.png" alt="Release v1.4.0" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Although I was mostly focused on refactoring and follow-ups, this release added pretty cool features to the app, like support for running python in the browser with the help of a library called &lt;a href="https://github.com/nalgeon/python-wasi" rel="noopener noreferrer"&gt;python-wasi&lt;/a&gt; that leverages &lt;a href="https://webassembly.org/" rel="noopener noreferrer"&gt;web assembly&lt;/a&gt; to make it happen.&lt;/p&gt;

&lt;p&gt;I have been planning to work on a cool feature for next week.   Will write about it when it's done.&lt;/p&gt;

&lt;p&gt;In the meantime, &lt;strong&gt;STAY TUNED&lt;/strong&gt;!!!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>opensource</category>
      <category>react</category>
      <category>nlp</category>
    </item>
    <item>
      <title>An Audio Player hook for your React App</title>
      <dc:creator>Amnish Singh Arora</dc:creator>
      <pubDate>Sun, 25 Feb 2024 01:37:18 +0000</pubDate>
      <link>https://forem.com/amnish04/an-audio-player-hook-for-your-react-app-4gn9</link>
      <guid>https://forem.com/amnish04/an-audio-player-hook-for-your-react-app-4gn9</guid>
      <description>&lt;p&gt;This was my 7th week of contributing to &lt;a href="https://github.com/tarasglek/chatcraft.org" rel="noopener noreferrer"&gt;ChatCraft&lt;/a&gt;, and so far, I've successfully shipped various improvements to the existing system. Every week, I've tried to add new features to the app and since we are shipping code quickly, many time its not possible to handle each aspect of the issue perfectly and follow ups have to be filed.&lt;/p&gt;

&lt;p&gt;A few weeks ago, I used OpenAI's &lt;a href="https://platform.openai.com/docs/guides/text-to-speech" rel="noopener noreferrer"&gt;text to speech&lt;/a&gt; API enabling &lt;a href="https://chatcraft.org/" rel="noopener noreferrer"&gt;ChatCraft&lt;/a&gt; to announce &lt;a href="https://en.wikipedia.org/wiki/Large_language_model" rel="noopener noreferrer"&gt;LLM&lt;/a&gt; responses as they were generated.&lt;/p&gt;

&lt;p&gt;I tried to parse the responses into sentences, pass those chunks to tts api for audio generation, and ultimately play the generated audio clips &lt;strong&gt;in sequence&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I've already written about the entire process in &lt;a href="https://dev.to/amnish04/openai-has-text-to-speech-support-now-4mlp"&gt;another post&lt;/a&gt;. In this post, I'll discuss one of the shortcomings of that feature, and how I updated my &lt;strong&gt;AudioPlayer&lt;/strong&gt; hook to manage and orchestrate the ordered playback of audio chunks throughout the application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;p&gt; 1. The Problem ⁉️&lt;br&gt;
 2. Implementing a global audio Queue&lt;br&gt;
       2.1. Defining an Audio Player Context&lt;br&gt;
       2.2. Wrapping the audio queue in a Context Provider&lt;br&gt;
       2.3. Providing the context&lt;br&gt;
       2.4. Defining useAudioPlayer Hook&lt;br&gt;
       2.5. Using the Hook 🪝&lt;br&gt;
 3. The Pull Request 📝&lt;br&gt;
 4. Reviewing a PR that adds Custom Provider Classes&lt;br&gt;
 5. What's Ahead 🔜&lt;/p&gt;
&lt;h2&gt;
  
  
  The Problem ⁉️
&lt;/h2&gt;

&lt;p&gt;If you went through &lt;a href="https://dev.to/amnish04/openai-has-text-to-speech-support-now-4mlp"&gt;my post&lt;/a&gt;, you might have noticed that in order to preserve the order of audio chunks, I was maintaining a classic queue data structure inside my hook.&lt;/p&gt;

&lt;p&gt;Here's the code for quick recap:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt; &lt;span class="p"&gt;}&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;react&lt;/span&gt;&lt;span class="dl"&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;useAudioPlayer&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="c1"&gt;// We are managing promises of audio urls instead of directly storing strings&lt;/span&gt;
  &lt;span class="c1"&gt;// because there is no guarantee when openai tts api finishes processing and resolves a specific url&lt;/span&gt;
  &lt;span class="c1"&gt;// For more info, check this comment:&lt;/span&gt;
  &lt;span class="c1"&gt;// https://github.com/tarasglek/chatcraft.org/pull/357#discussion_r1473470003&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setQueue&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;isPlaying&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setIsPlaying&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&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;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;isPlaying&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;playAudio&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&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;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isPlaying&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;playAudio&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="na"&gt;audioClipUri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;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="nf"&gt;setIsPlaying&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;audioUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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;audioClipUri&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;audio&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Audio&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;audioUrl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;audio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onended&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="nx"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;revokeObjectURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;audioUrl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// To avoid memory leaks&lt;/span&gt;
      &lt;span class="nf"&gt;setQueue&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;oldQueue&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;oldQueue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
      &lt;span class="nf"&gt;setIsPlaying&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="nx"&gt;audio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;play&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;addToAudioQueue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;audioClipUri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;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="nf"&gt;setQueue&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;oldQueue&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="nx"&gt;oldQueue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;audioClipUri&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="nx"&gt;addToAudioQueue&lt;/span&gt; &lt;span class="p"&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="nx"&gt;useAudioPlayer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Now this &lt;strong&gt;works like a Charm&lt;/strong&gt;, when you are only using this hook at just one place in the app. But imagine &lt;strong&gt;what would happen&lt;/strong&gt; if I was to use this hook from some other part of the app as well.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;addToAudioQueue&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useAudioPlayer&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;audioClipUri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;textToSpeech&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ttsWordsBuffer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;addToAudioQueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;audioClipUri&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;Would it still maintain the order? &lt;/li&gt;
&lt;li&gt;Leave the order aside, is there a guarantee there is only a single audio clip playing at a point of time.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The answer, as you might have guessed is a &lt;strong&gt;NO&lt;/strong&gt; ❌&lt;/p&gt;

&lt;p&gt;Reason being, every time we call the &lt;code&gt;useAudioPlayer&lt;/code&gt; hook, it returns a &lt;strong&gt;fresh instance&lt;/strong&gt; of an audio queue. Let's say you use it at 3 different places in your app, you now have 3 audio players active at the same time.&lt;/p&gt;

&lt;p&gt;And if you push audio clips to one of them while another is already playing something, you get into a similar situation as if you there are 3 speakers in a room, and each one is playing a different song.&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%2Fk7rp8vpygal6jouis6i2.jpg" 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%2Fk7rp8vpygal6jouis6i2.jpg" alt="3 Speakers" width="800" height="330"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This didn't prevent me from merging the existing code as I was only initializing an audio queue at &lt;a href="https://github.com/tarasglek/chatcraft.org/pull/357/files#diff-6d071d338ac4fef38a1aff98d7885eb7dcdbfbd07984436fca89685520bd9225" rel="noopener noreferrer"&gt;one place&lt;/a&gt; throughout the application.&lt;/p&gt;

&lt;p&gt;But with this, there was no way to access and operate with the audio queue from other parts of the application, like stopping the audio playback when a new question is asked.&lt;/p&gt;

&lt;p&gt;The details are documented in this follow up.&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_github-liquid-tag"&gt;
  &lt;h1&gt;
    &lt;a href="https://github.com/tarasglek/chatcraft.org/issues/391" rel="noopener noreferrer"&gt;
      &lt;img class="github-logo" alt="GitHub logo" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg"&gt;
      &lt;span class="issue-title"&gt;
        Stop playing audio when tts is turned off
      &lt;/span&gt;
      &lt;span class="issue-number"&gt;#391&lt;/span&gt;
    &lt;/a&gt;
  &lt;/h1&gt;
  &lt;div class="github-thread"&gt;
    &lt;div class="timeline-comment-header"&gt;
      &lt;a href="https://github.com/Amnish04" rel="noopener noreferrer"&gt;
        &lt;img class="github-liquid-tag-img" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Favatars.githubusercontent.com%2Fu%2F78865303%3Fv%3D4" alt="Amnish04 avatar"&gt;
      &lt;/a&gt;
      &lt;div class="timeline-comment-header-text"&gt;
        &lt;strong&gt;
          &lt;a href="https://github.com/Amnish04" rel="noopener noreferrer"&gt;Amnish04&lt;/a&gt;
        &lt;/strong&gt; posted on &lt;a href="https://github.com/tarasglek/chatcraft.org/issues/391" rel="noopener noreferrer"&gt;&lt;time&gt;Jan 31, 2024&lt;/time&gt;&lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag-github-body"&gt;
      &lt;p&gt;Follow up for #357&lt;/p&gt;
&lt;p&gt;Currently, when text to speech is turned off
&lt;a rel="noopener noreferrer" href="https://github.com/tarasglek/chatcraft.org/assets/78865303/ada963be-f4e4-496b-be3b-07724c2335a6"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Ftarasglek%2Fchatcraft.org%2Fassets%2F78865303%2Fada963be-f4e4-496b-be3b-07724c2335a6" alt="image"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The announcement of response does not stop.
We need to make sure any tts audio playing is stopped and audio player queue needs to be cleared.&lt;/p&gt;

    &lt;/div&gt;
    &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/tarasglek/chatcraft.org/issues/391" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;



&lt;h2&gt;
  
  
  Implementing a global audio Queue
&lt;/h2&gt;

&lt;p&gt;To fix this issue, I had to make sure that there was always only &lt;strong&gt;one instance&lt;/strong&gt; of the audio queue throughout the application, while the core logic and interface would remain the same.&lt;/p&gt;

&lt;p&gt;This had to be done in the following steps.&lt;/p&gt;

&lt;h3&gt;
  
  
  Defining an Audio Player Context
&lt;/h3&gt;

&lt;p&gt;The first step was to define a &lt;a href="https://react.dev/reference/react/createContext" rel="noopener noreferrer"&gt;React Context&lt;/a&gt;, that would expose the relevant functions to operate with the audio queue.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;AudioPlayerContextType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;addToAudioQueue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;audioClipUri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;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="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;clearAudioQueue&lt;/span&gt;&lt;span class="p"&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="k"&gt;void&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;AudioPlayerContext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;createContext&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;AudioPlayerContextType&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;addToAudioQueue&lt;/span&gt;&lt;span class="p"&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="na"&gt;clearAudioQueue&lt;/span&gt;&lt;span class="p"&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="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Notice that I am also exposing a new method called &lt;code&gt;clearAudioQueue&lt;/code&gt; now. We'll look at the implementation soon.&lt;/p&gt;
&lt;h3&gt;
  
  
  Wrapping the audio queue in a Context Provider
&lt;/h3&gt;

&lt;p&gt;For any of the components in a React Application to read from a context, it first needs to be provided to one of its ancestors.&lt;/p&gt;

&lt;p&gt;This is done with the help of a &lt;a href="https://react.dev/reference/react/createContext#provider" rel="noopener noreferrer"&gt;React Context Provider&lt;/a&gt;. I wrapped the existing audio player logic into a function that returns an &lt;code&gt;AudioPlayerContext&lt;/code&gt; Provider exposing the functions that will be used to operate on the &lt;strong&gt;single&lt;/strong&gt; Audio Queue in the app.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;createContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ReactNode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;FC&lt;/span&gt; &lt;span class="p"&gt;}&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;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;AudioClip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;audioUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;audioElement&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HTMLAudioElement&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;AudioPlayerProvider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FC&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ReactNode&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;children&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="c1"&gt;// We are managing promises of audio urls instead of directly storing strings&lt;/span&gt;
  &lt;span class="c1"&gt;// because there is no guarantee when openai tts api finishes processing and resolves a specific url&lt;/span&gt;
  &lt;span class="c1"&gt;// For more info, check this comment:&lt;/span&gt;
  &lt;span class="c1"&gt;// https://github.com/tarasglek/chatcraft.org/pull/357#discussion_r1473470003&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setQueue&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;isPlaying&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setIsPlaying&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;currentAudioClip&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setCurrentAudioClip&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;AudioClip&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&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;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;isPlaying&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;playAudio&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&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;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isPlaying&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;playAudio&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="na"&gt;audioClipUri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;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="nf"&gt;setIsPlaying&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;audioUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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;audioClipUri&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;audio&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Audio&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;audioUrl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;audio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;preload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;auto&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;audio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onended&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="nx"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;revokeObjectURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;audioUrl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nf"&gt;setQueue&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;oldQueue&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;oldQueue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
      &lt;span class="nf"&gt;setIsPlaying&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="nf"&gt;setCurrentAudioClip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="nx"&gt;audio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;play&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nf"&gt;setCurrentAudioClip&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;audioElement&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;audio&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;audioUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;audioUrl&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;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;addToAudioQueue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;audioClipUri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;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="nf"&gt;setQueue&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;oldQueue&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="nx"&gt;oldQueue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;audioClipUri&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;clearAudioQueue&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentAudioClip&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Stop currently playing audio&lt;/span&gt;
      &lt;span class="nx"&gt;currentAudioClip&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;audioElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pause&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;revokeObjectURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentAudioClip&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;audioUrl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="nf"&gt;setCurrentAudioClip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nf"&gt;setIsPlaying&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Flush all the remaining audio clips&lt;/span&gt;
    &lt;span class="nf"&gt;setQueue&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;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;addToAudioQueue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;clearAudioQueue&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;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;AudioPlayerContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Provider&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;AudioPlayerContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Provider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;You'll notice the implementation of the &lt;code&gt;clearAudioQueue&lt;/code&gt; function, which is meant to be called from any application component to pause the current audio clip, and flush the remaining clips in the queue.&lt;/p&gt;

&lt;p&gt;I am also managing a new state now&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;AudioClip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;audioUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;audioElement&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HTMLAudioElement&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;currentAudioClip&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setCurrentAudioClip&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;AudioClip&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This allows me to &lt;code&gt;pause&lt;/code&gt; the current playing audio as soon as the &lt;code&gt;clearAudioQueue&lt;/code&gt; function is called.&lt;/p&gt;
&lt;h3&gt;
  
  
  Providing the context
&lt;/h3&gt;

&lt;p&gt;The next step is to provide the &lt;code&gt;AudioPlayerContext&lt;/code&gt; at the root of the application.&lt;/p&gt;

&lt;p&gt;In &lt;strong&gt;main.tsx&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AudioPlayerProvider&lt;/span&gt; &lt;span class="p"&gt;}&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;./hooks/use-audio-player&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;ReactDOM&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createRoot&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="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;main&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;HTMLElement&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;render&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;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;StrictMode&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ChakraProvider&lt;/span&gt; &lt;span class="na"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;theme&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;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;AudioPlayerProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;SettingsProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;CostProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ModelsProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
              &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;UserProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ColorModeScript&lt;/span&gt; &lt;span class="na"&gt;initialColorMode&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;initialColorMode&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;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;RouterProvider&lt;/span&gt; &lt;span class="na"&gt;router&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;router&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;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;UserProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;ModelsProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;CostProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;SettingsProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;AudioPlayerProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;ChakraProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;StrictMode&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Defining useAudioPlayer Hook
&lt;/h3&gt;

&lt;p&gt;After defining the context and provider, we can finally write the hook to use the &lt;code&gt;AudioPlayerContext&lt;/code&gt; provided by the closest context provider.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;useAudioPlayer&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="nf"&gt;useContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;AudioPlayerContext&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="nx"&gt;useAudioPlayer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Using the Hook 🪝
&lt;/h3&gt;

&lt;p&gt;And the final step was to actually use the global Audio Player context at places I would like to.&lt;/p&gt;

&lt;p&gt;Currently, I am using it for 2 scenarios:&lt;br&gt;
&lt;strong&gt;1.&lt;/strong&gt; The audio for previous message will instantly stop playing as soon as the user asks a new question.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;useAudioPlayer&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;../../hooks/use-audio-player&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;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;clearAudioQueue&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useAudioPlayer&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;span class="c1"&gt;// NOTE: we strip out the ChatCraft App messages before sending to OpenAI.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;includeAppMessages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// Clear any previous audio clips&lt;/span&gt;
&lt;span class="nf"&gt;clearAudioQueue&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;callChatApi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;functions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;functionToCall&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;2.&lt;/strong&gt; Any audio clips being played via useAudioPlayer will instantly stop as soon as the user disables the TTS setting.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;useAudioPlayer&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;../../hooks/use-audio-player&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;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;clearAudioQueue&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useAudioPlayer&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;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;isTtsSupported&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Tooltip&lt;/span&gt;
    &lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;announceMessages&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Text-to-Speech Enabled&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Text-to-Speech Disabled&lt;/span&gt;&lt;span class="dl"&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;IconButton&lt;/span&gt;
      &lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;lg&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="nx"&gt;variant&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;solid&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="nx"&gt;aria&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;announceMessages&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Text-to-Speech Enabled&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Text-to-Speech Disabled&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nx"&gt;icon&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;announceMessages&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;MdVolumeUp&lt;/span&gt; &lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&amp;gt; : &amp;lt;MdVolumeOff size=&lt;/span&gt;&lt;span class="se"&gt;{25}&lt;/span&gt;&lt;span class="sr"&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="nx"&gt;onClick&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;announceMessages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="c1"&gt;// Flush any remaining audio clips being announced&lt;/span&gt;
          &lt;span class="nf"&gt;clearAudioQueue&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nf"&gt;setSettings&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;announceMessages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;announceMessages&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="p"&gt;}}&lt;/span&gt;
    &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Tooltip&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;And that is all it takes to create and use a custom audio player hook in a React Application. I am pretty sure there are audio player hooks already published on npm, but I doubt any of them will fit my needs as I need to maintain a queue.&lt;/p&gt;

&lt;p&gt;Let me know in the comments if you know a better approach.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Pull Request 📝
&lt;/h2&gt;

&lt;p&gt;My PR for this improvement is already up awaiting reviews before it lands.&lt;br&gt;
You can look at the entire source code and a detailed explanation here.&lt;/p&gt;


&lt;div class="ltag_github-liquid-tag"&gt;
  &lt;h1&gt;
    &lt;a href="https://github.com/tarasglek/chatcraft.org/pull/484" rel="noopener noreferrer"&gt;
      &lt;img class="github-logo" alt="GitHub logo" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg"&gt;
      &lt;span class="issue-title"&gt;
        Make AudioPlayer queue global
      &lt;/span&gt;
      &lt;span class="issue-number"&gt;#484&lt;/span&gt;
    &lt;/a&gt;
  &lt;/h1&gt;
  &lt;div class="github-thread"&gt;
    &lt;div class="timeline-comment-header"&gt;
      &lt;a href="https://github.com/Amnish04" rel="noopener noreferrer"&gt;
        &lt;img class="github-liquid-tag-img" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Favatars.githubusercontent.com%2Fu%2F78865303%3Fv%3D4" alt="Amnish04 avatar"&gt;
      &lt;/a&gt;
      &lt;div class="timeline-comment-header-text"&gt;
        &lt;strong&gt;
          &lt;a href="https://github.com/Amnish04" rel="noopener noreferrer"&gt;Amnish04&lt;/a&gt;
        &lt;/strong&gt; posted on &lt;a href="https://github.com/tarasglek/chatcraft.org/pull/484" rel="noopener noreferrer"&gt;&lt;time&gt;Feb 23, 2024&lt;/time&gt;&lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag-github-body"&gt;
      &lt;p&gt;In #357, I was able to stream audio responses as the LLM response was generated and also optimize the playback.&lt;/p&gt;
&lt;p&gt;But there were some problems with it, and follow ups were filed.&lt;/p&gt;
&lt;p&gt;This is regarding one of those follow ups - #391.&lt;/p&gt;
&lt;p&gt;The problem was that once an audio clip was added to the audio queue, there was no way to stop it from other parts of the application since every invocation to &lt;code&gt;useAudioPlayer&lt;/code&gt; hook resulted in a fresh audio queue. This was pretty annoying and made the TTS feature unusable as users had to wait for the previous message announcement to finish before it started playing for the next one.&lt;/p&gt;
&lt;p&gt;To fix this issue, I have made the Audio Player queue global, by creating and providing an &lt;code&gt;AudioPlayerContext&lt;/code&gt; at the root of the application.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/tarasglek/chatcraft.org/compare/amnish04/global-audio-player?expand=1#diff-1cd8b18798a1a103bfe13bef54354c1f3a3bea29a31c8eea1a0c67a3a839b811" rel="noopener noreferrer"&gt;https://github.com/tarasglek/chatcraft.org/compare/amnish04/global-audio-player?expand=1#diff-1cd8b18798a1a103bfe13bef54354c1f3a3bea29a31c8eea1a0c67a3a839b811&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This allowed me to expose another function from the hook called &lt;code&gt;clearAudioQueue&lt;/code&gt; which can now be called from any application component to pause the current audio clip, and flush the remaining clips in the queue.&lt;/p&gt;
&lt;p&gt;Currently, I am using it for 2 scenarios:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The audio for previous message will instantly stop playing as soon as the user asks a new question.&lt;/li&gt;
&lt;li&gt;Any audio clips being played via &lt;code&gt;useAudioPlayer&lt;/code&gt; will instantly stop as soon as the user disables the TTS setting.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This approach also makes the audio system more robust any we now have a single source of truth for audio clips, and the risk to play multiple audio clips at once is also reduced.&lt;/p&gt;
&lt;p&gt;This fixes #391&lt;/p&gt;

    &lt;/div&gt;
    &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/tarasglek/chatcraft.org/pull/484" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;



&lt;h2&gt;
  
  
  Reviewing a PR that adds Custom Provider Classes
&lt;/h2&gt;

&lt;p&gt;I also reviewed a PR from &lt;a href="https://dev.to/katiel"&gt;Katie&lt;/a&gt; this week.&lt;/p&gt;

&lt;p&gt;Right now, we just have to supported AI providers - &lt;strong&gt;OpenAI&lt;/strong&gt; and &lt;strong&gt;OpenRouter&lt;/strong&gt;. But since we are expecting to have more in the future, it would become impractical to manage all of them in a generic &lt;code&gt;ChatCraftProvider&lt;/code&gt; class.&lt;br&gt;
Katie is working on creating a separate concrete class for each AI provider inheriting from the current &lt;code&gt;ChatCraftProvider&lt;/code&gt; class, such that if new providers do some things differently, existing methods can be overridden and new methods can be added to their own classes.&lt;/p&gt;


&lt;div class="ltag__user ltag__user__id__1156769"&gt;
    &lt;a href="/katiel" class="ltag__user__link profile-image-link"&gt;
      &lt;div class="ltag__user__pic"&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%2Fuser%2Fprofile_image%2F1156769%2F84aaefe1-248b-4635-8853-b3c75d9dd078.png" alt="katiel image"&gt;
      &lt;/div&gt;
    &lt;/a&gt;
  &lt;div class="ltag__user__content"&gt;
    &lt;h2&gt;
&lt;a class="ltag__user__link" href="/katiel"&gt;Katie Liu&lt;/a&gt;Follow
&lt;/h2&gt;
    &lt;div class="ltag__user__summary"&gt;
      &lt;a class="ltag__user__link" href="/katiel"&gt;I am a software developer with an interest in open source and web development :)&lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;I went through the code and left a few comments for changes. The PR is still up. If the PR is still up at the time of your reading, please take a look and let us know if you can come up with a better approach!&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Ahead 🔜
&lt;/h2&gt;

&lt;p&gt;The TTS functionality I've been working on still needs some more work to make best use of the API OpenAI provides.&lt;/p&gt;

&lt;p&gt;I'll start out by allowing the users to &lt;strong&gt;choose from different voices&lt;/strong&gt;, ability to re-announce a message, and even downloading the audio narration of a response.&lt;/p&gt;

&lt;p&gt;Everything is documented in this issue&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_github-liquid-tag"&gt;
  &lt;h1&gt;
    &lt;a href="https://github.com/tarasglek/chatcraft.org/issues/400" rel="noopener noreferrer"&gt;
      &lt;img class="github-logo" alt="GitHub logo" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg"&gt;
      &lt;span class="issue-title"&gt;
        Make TTS more flexible
      &lt;/span&gt;
      &lt;span class="issue-number"&gt;#400&lt;/span&gt;
    &lt;/a&gt;
  &lt;/h1&gt;
  &lt;div class="github-thread"&gt;
    &lt;div class="timeline-comment-header"&gt;
      &lt;a href="https://github.com/tarasglek" rel="noopener noreferrer"&gt;
        &lt;img class="github-liquid-tag-img" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Favatars.githubusercontent.com%2Fu%2F857083%3Fv%3D4" alt="tarasglek avatar"&gt;
      &lt;/a&gt;
      &lt;div class="timeline-comment-header-text"&gt;
        &lt;strong&gt;
          &lt;a href="https://github.com/tarasglek" rel="noopener noreferrer"&gt;tarasglek&lt;/a&gt;
        &lt;/strong&gt; posted on &lt;a href="https://github.com/tarasglek/chatcraft.org/issues/400" rel="noopener noreferrer"&gt;&lt;time&gt;Feb 03, 2024&lt;/time&gt;&lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag-github-body"&gt;
      &lt;ol&gt;
&lt;li&gt;Add a Speak submenu to both human and bot messages&lt;/li&gt;
&lt;li&gt;In the Speak submenu allow one to select voice, which causes message to be spoken,&lt;/li&gt;
&lt;li&gt;once message is spoke, allow generated message to be downloaded..can add a download to the speak submenu after speech completes. It's ugly but not sure re other options&lt;/li&gt;
&lt;li&gt;Changing voice in speech menu also changes it for the speak icon when automatic tts is on&lt;/li&gt;
&lt;li&gt;should move speak icon to the menu on the left and change it to be "options" or 3 vertical dots on mobile&lt;/li&gt;
&lt;/ol&gt;

    &lt;/div&gt;
    &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/tarasglek/chatcraft.org/issues/400" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;In this post, I shared my approach of creating and managing an &lt;strong&gt;audio queue&lt;/strong&gt; with a custom React Hook, and how I am leveraging it to stream OpenAI's LLM responses to their TTS API, and ultimately playing the generated clips in order.&lt;/p&gt;

&lt;p&gt;I'll follow up about the other planned functionalities soon.&lt;/p&gt;

&lt;p&gt;In the meantime, STAY TUNED!&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>openai</category>
      <category>react</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Sheriffing in Open Source</title>
      <dc:creator>Amnish Singh Arora</dc:creator>
      <pubDate>Sun, 18 Feb 2024 01:33:36 +0000</pubDate>
      <link>https://forem.com/amnish04/sheriffing-in-open-source-3mcf</link>
      <guid>https://forem.com/amnish04/sheriffing-in-open-source-3mcf</guid>
      <description>&lt;p&gt;For past few weeks, I've been writing about my experiences working on &lt;a href="https://github.com/tarasglek/chatcraft.org" rel="noopener noreferrer"&gt;ChatCraft&lt;/a&gt;, an &lt;strong&gt;open source&lt;/strong&gt; AI chat bot, specifically catering to Software Developers.&lt;/p&gt;

&lt;p&gt;So far, my weeks were pretty similar with little change in routine. I worked on some feature or fixing bugs, and reviewed a couple of Pull Requests opened by others. But this week was a little &lt;strong&gt;different&lt;/strong&gt; as I got to be &lt;strong&gt;the Sheriff&lt;/strong&gt; and was responsible for &lt;a href="https://github.com/tarasglek/chatcraft.org/releases/tag/v1.3.0" rel="noopener noreferrer"&gt;Release v1.3.0&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a Sheriff ⭐
&lt;/h2&gt;

&lt;p&gt;Managing a serious open source project is &lt;strong&gt;not&lt;/strong&gt; a walk in the park by any means. With various team and community members continuously contributing to the project, it gets pretty challenging to maintain the new and existing issues, incoming pull requests and the quality of code they bring that will ultimately &lt;strong&gt;land&lt;/strong&gt; in production.&lt;/p&gt;

&lt;p&gt;Which is why we need a &lt;strong&gt;Sheriff&lt;/strong&gt; to help developers shepherd their code through the development and review process so that it can be landed cleanly in git, and to do so without breaking any tests, CI automation, or infrastructure. (See &lt;a href="https://github.com/humphd/osd700-winter-2024/wiki/How-to-be-a-Sheriff" rel="noopener noreferrer"&gt;How to be a Sheriff&lt;/a&gt;)&lt;/p&gt;

&lt;h2&gt;
  
  
  Responsibilities
&lt;/h2&gt;

&lt;p&gt;People often confuse a Sheriff as someone being responsible for fixing each and &lt;strong&gt;every problem&lt;/strong&gt;, which is &lt;strong&gt;not&lt;/strong&gt; the case.&lt;br&gt;
&lt;strong&gt;Open Source&lt;/strong&gt; is driven by &lt;strong&gt;community effort&lt;/strong&gt;, and a Sheriff is responsible for coordinating those community efforts to quickly land fixes, and make decisions to keep the project going.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://github.com/tarasglek/chatcraft.org" rel="noopener noreferrer"&gt;ChatCraft&lt;/a&gt;, we have a &lt;strong&gt;rotation policy&lt;/strong&gt; and every week there are &lt;strong&gt;two Sheriffs&lt;/strong&gt; - one Primary and a Backup sheriff in case the person scheduled for the week is unable to perform duties. The primary responsibility is &lt;strong&gt;being available&lt;/strong&gt; as the first line of defence in case anything anything goes wrong, keeping concerned people aware of ongoing problems, and getting necessary help to resolve them.&lt;/p&gt;

&lt;p&gt;There are many responsibilities of a Sheriff and a &lt;strong&gt;good list&lt;/strong&gt; of those duties can be found in this wiki:&lt;br&gt;
&lt;a href="https://github.com/humphd/osd700-winter-2024/wiki/How-to-be-a-Sheriff#responsibilities" rel="noopener noreferrer"&gt;https://github.com/humphd/osd700-winter-2024/wiki/How-to-be-a-Sheriff#responsibilities&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Me as a Sheriff
&lt;/h2&gt;

&lt;p&gt;I mentioned at the very beginning that I was scheduled to be &lt;a href="https://github.com/tarasglek/chatcraft.org" rel="noopener noreferrer"&gt;ChatCraft&lt;/a&gt;'s Sheriff this week. And now that we know the associated responsibilities, I'd like to share my experience being in the role.&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%2Fscyyioyhudqvcfbzjgyg.gif" 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%2Fscyyioyhudqvcfbzjgyg.gif" alt="Sheriff" width="245" height="138"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Running the weekly Triage
&lt;/h3&gt;

&lt;p&gt;Every &lt;strong&gt;Wednesday&lt;/strong&gt;, we have a &lt;a href="https://github.com/humphd/osd700-winter-2024/wiki/How-to-Run-a-Triage-Meeting" rel="noopener noreferrer"&gt;Weekly Triage&lt;/a&gt; meeting where the entire team gathers to discuss existing issues, pull requests and subsequently plan for the upcoming release.&lt;/p&gt;

&lt;p&gt;This was when the Sheriff from previous week handed over the responsibilities to me, and I was to run the Triage.&lt;/p&gt;

&lt;p&gt;Here's how it went:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;I started by looking at the existing &lt;strong&gt;Pull Requests&lt;/strong&gt; opened for issues in the &lt;a href="https://github.com/tarasglek/chatcraft.org/milestone/3" rel="noopener noreferrer"&gt;current release cycle&lt;/a&gt; (v1.3.0). We discussed 3 main things - how was the progress, if there are any roadblocks, and if need to schedule it for a further milestone.&lt;/li&gt;
&lt;li&gt;Next, I opened the list of issues scheduled for the &lt;a href="https://github.com/tarasglek/chatcraft.org/milestone/3?closed=1" rel="noopener noreferrer"&gt;week's milestone&lt;/a&gt; and again, we discussed if the issues could be closed within the milestone or if there were any showstoppers.&lt;/li&gt;
&lt;li&gt;The last step was to &lt;strong&gt;plan&lt;/strong&gt; for the &lt;strong&gt;upcoming release&lt;/strong&gt; v1.4.0. I asked everyone to move any of their assigned issues that they would like to Milestone 1.4 and also did it for myself. Once we had some initial issues scheduled, I started going through the list of existing &lt;strong&gt;unassigned&lt;/strong&gt; issues - anyone aware of it explained the context, anyone interested asked to be assigned and the issue was moved to the upcoming Milestone if the assignee was comfortable with it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Going through issues and pull requests also sparked some &lt;strong&gt;healthy discussions&lt;/strong&gt; keeping the team engaged and up to date with what's happening with the project.&lt;/p&gt;

&lt;p&gt;Here are the meeting notes if you're interested:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/humphd/osd700-winter-2024/wiki/Meeting-Minutes-%E2%80%90-Milestone-1.3" rel="noopener noreferrer"&gt;https://github.com/humphd/osd700-winter-2024/wiki/Meeting-Minutes-%E2%80%90-Milestone-1.3&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Making Contributions
&lt;/h3&gt;

&lt;p&gt;Being a Sheriff does not mean you're exempt from contributing yourself. You need to put some effort in striking a &lt;strong&gt;balance&lt;/strong&gt; between helping others and delivering your own work.&lt;/p&gt;

&lt;p&gt;In total, I got 3 contributions this week and a breakdown is as follows:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1.&lt;/strong&gt; &lt;a href="https://github.com/tarasglek/chatcraft.org/issues/413" rel="noopener noreferrer"&gt;Improve audio recording UX&lt;/a&gt;: This one was a follow up to another issue in which I changed the mic recording behaviour to click instead of drag. All I had to do was add better indicators to the mic button, so users could easily identify the recording state of the control. So I added a Tooltip, a basic size and color transition to fix the problem.&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%2Fjy6624up9bt1i3txff7a.gif" 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%2Fjy6624up9bt1i3txff7a.gif" alt="Better Mic UX" width="1268" height="372"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2.&lt;/strong&gt; &lt;a href="https://github.com/tarasglek/chatcraft.org/pull/445" rel="noopener noreferrer"&gt;Fix viewport pushed by switching chats&lt;/a&gt;: This was another instance of a pretty &lt;a href="https://github.com/tarasglek/chatcraft.org/issues/263" rel="noopener noreferrer"&gt;old issue&lt;/a&gt; that I fixed back last October. Since I had almost forgotten what I did back then, I revisited by &lt;a href="https://dev.to/amnish04/no-backing-away-when-hacking-away-2h27"&gt;blog post&lt;/a&gt; where the solution was documented, and quickly opened a PR with the fix. As this was essentially a regression, Dave (my professor) suggested we need to find a better fix for this. So I filed a &lt;a href="https://github.com/tarasglek/chatcraft.org/issues/452" rel="noopener noreferrer"&gt;new issue&lt;/a&gt; for it.&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%2F686htwzohs9elmtuskpw.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%2F686htwzohs9elmtuskpw.png" alt="Found the fix" width="800" height="554"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3.&lt;/strong&gt; &lt;a href="https://github.com/tarasglek/chatcraft.org/pull/437" rel="noopener noreferrer"&gt;Make Sidebar Responsive&lt;/a&gt;: This was a continuation to an old Pull Request that I had put up last November. I have blogged about the problem and solution in my &lt;a href="https://dev.to/amnish04/refactor-and-reboot-time-to-clear-backlog-in-open-source-4e3g#sidebar-enhancement-reboot"&gt;last post&lt;/a&gt;. But this was the final result on mobile.&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%2Fduf3t9i9atri39z05hzk.gif" 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%2Fduf3t9i9atri39z05hzk.gif" alt="On Mobile" width="477" height="962"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Refactoring:&lt;/strong&gt; Since this introduced a lot of duplication for sidebar related styles and logic, I tried to minimize that duplication by creating separate components for mobile and desktop sidebars and exposing them through a common &lt;code&gt;Sidebar&lt;/code&gt; component.&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%2F9vs32wnqqgze6xxo9f1m.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%2F9vs32wnqqgze6xxo9f1m.png" alt="Refactoring" width="382" height="218"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I opened the &lt;a href="https://github.com/tarasglek/chatcraft.org/pull/463" rel="noopener noreferrer"&gt;Pull Request&lt;/a&gt; as soon as the sidebar changes were merged, but since it was late in the week and review were pending, I decided to move it to the next release.&lt;/p&gt;

&lt;h3&gt;
  
  
  Getting the PRs Reviewed 👁️
&lt;/h3&gt;

&lt;p&gt;This was not too different for me as even normally we're expected to do code review for PRs that request our review.&lt;/p&gt;

&lt;p&gt;But since a Sheriff is the one responsible for the either reviewing or getting the code reviewed as soon as possible, I had to make myself available all time. And as soon as a new PR was up, I went through the it to make myself of the changes and assigned reviewers appropriately based on their expertise.&lt;/p&gt;

&lt;p&gt;I don't know WHY, but I felt like I wasn't entitled to request people for reviews if I didn't do myself as well. So I tried to review as much as I could. Normally I do around 2 code reviews each week. But this week was a little crazy as I reviewed &lt;strong&gt;9 PRs&lt;/strong&gt; in total and didn't realize until I counted.&lt;/p&gt;

&lt;p&gt;Here's a list if you're interested:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://github.com/tarasglek/chatcraft.org/pull/431" rel="noopener noreferrer"&gt;Command button enhancements&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/tarasglek/chatcraft.org/pull/448" rel="noopener noreferrer"&gt;Search Bar Placeholder Text&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/tarasglek/chatcraft.org/pull/449" rel="noopener noreferrer"&gt;Created list of supported ChatCraftProviders&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/tarasglek/chatcraft.org/pull/414" rel="noopener noreferrer"&gt;Allow users to authenticate with Google OAuth&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/tarasglek/chatcraft.org/pull/462" rel="noopener noreferrer"&gt;Replaced padding to use relative value&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/tarasglek/chatcraft.org/pull/456" rel="noopener noreferrer"&gt;Shorten mobile placeholder&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/tarasglek/chatcraft.org/pull/461" rel="noopener noreferrer"&gt;show delete submenu only when there is more than 1 delete option&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/tarasglek/chatcraft.org/pull/403" rel="noopener noreferrer"&gt;Allow running JS/TS remotely via val.town's eval endpoint&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/tarasglek/chatcraft.org/pull/469" rel="noopener noreferrer"&gt;Fix search/share id loading bug&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Isn't that Crazy!&lt;/p&gt;

&lt;h2&gt;
  
  
  Time for the Release 🚀
&lt;/h2&gt;

&lt;p&gt;All that hard work finally paid off as we were able to close most of the planned issues&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%2Fmm94c59w4fp8qlzuajmj.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%2Fmm94c59w4fp8qlzuajmj.png" alt="Milestone 1.3.0" width="800" height="340"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And got to release &lt;a href="https://github.com/tarasglek/chatcraft.org/releases/tag/v1.3.0" rel="noopener noreferrer"&gt;ChatCraft v1.3.0&lt;/a&gt; at the end of the week.&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%2F6r619dsml67khnvwrgim.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%2F6r619dsml67khnvwrgim.png" alt="ChatCraft v1.3.0" width="800" height="452"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this post, I discussed about the roles and responsibilities of a Sheriff in Open Source, and my experience &lt;strong&gt;Sheriffing&lt;/strong&gt; a quality open source project for this week 🚀&lt;/p&gt;

&lt;p&gt;Hope you enjoyed the post!&lt;/p&gt;

&lt;p&gt;And here's one for me creating my first Software Release 🍻&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>webdev</category>
      <category>programming</category>
      <category>react</category>
    </item>
    <item>
      <title>Refactor and Reboot: Time to Clear Backlog in Open Source</title>
      <dc:creator>Amnish Singh Arora</dc:creator>
      <pubDate>Sat, 10 Feb 2024 22:10:42 +0000</pubDate>
      <link>https://forem.com/amnish04/refactor-and-reboot-time-to-clear-backlog-in-open-source-4e3g</link>
      <guid>https://forem.com/amnish04/refactor-and-reboot-time-to-clear-backlog-in-open-source-4e3g</guid>
      <description>&lt;p&gt;Last week, I filed and fixed follow up issues for &lt;a href="https://dev.to/amnish04/openai-has-text-to-speech-support-now-4mlp"&gt;Text to Speech&lt;/a&gt; feature I recently integrated with &lt;a href="https://github.com/tarasglek/chatcraft.org" rel="noopener noreferrer"&gt;ChatCraft&lt;/a&gt;.There are still many issues open related to Text to Speech that I need to address.&lt;/p&gt;

&lt;p&gt;But this week, I decided to prioritize some smaller issues and pull requests I had under my name, as my backlog was growing out of control. In &lt;strong&gt;this post&lt;/strong&gt;, I will discuss how I was able to get many of those issues closed, and review other Pull Requests, effectively contributing to ChatCraft's &lt;a href="https://github.com/tarasglek/chatcraft.org/releases/tag/v1.2.0" rel="noopener noreferrer"&gt;Release 1.2&lt;/a&gt; this week.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;p&gt; 1. Improving Mic UX&lt;br&gt;
 2. Helping with Image Styling&lt;br&gt;
 3. Working on a Custom Command 💻&lt;br&gt;
 4. Refactoring the approach to determine TTS Support 🔧&lt;br&gt;
 5. Sidebar Enhancement REBOOT&lt;br&gt;
       5.1. Challenges with Git&lt;br&gt;
       5.2. Sidebar Changes&lt;br&gt;
 6. Reviewing Other PRs&lt;br&gt;
 7. Release 1.2.0 🚀&lt;/p&gt;
&lt;h2&gt;
  
  
  Improving Mic UX
&lt;/h2&gt;

&lt;p&gt;I had a couple of ongoing PRs, but I chose to kick off the week with an easy one. I opened a &lt;a href="https://github.com/tarasglek/chatcraft.org/pull/369" rel="noopener noreferrer"&gt;Pull Request&lt;/a&gt; last week to change the Audio Recording behaviour of ChatCraft to "Click to Start, Click to Stop" from "Drag and Hold" as it was pretty challenging to make drag behaviour work seamlessly on mobile.&lt;/p&gt;

&lt;p&gt;Last week, I only changed it for smaller screens as I did not want to dump all the &lt;a href="https://github.com/tarasglek/chatcraft.org/commit/44654f9738104e6388e3d8a1ea1889aaf7d389d4" rel="noopener noreferrer"&gt;work done&lt;/a&gt; by &lt;a href="https://github.com/humphd" rel="noopener noreferrer"&gt;David&lt;/a&gt; to implement the drag behaviour.&lt;/p&gt;

&lt;p&gt;However, everyone believed that click/touch was a better approach&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%2Fte84vvgehtchq9gor33q.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%2Fte84vvgehtchq9gor33q.png" alt="Review" width="800" height="388"&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%2Fvd8oomo8etj27hhvky7l.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%2Fvd8oomo8etj27hhvky7l.png" alt="Review" width="800" height="309"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I &lt;a href="https://github.com/tarasglek/chatcraft.org/pull/369/commits/b4076510a6d43d4bf512beaa8c3c2506f0a82fd2" rel="noopener noreferrer"&gt;made the change&lt;/a&gt; for all screen sizes. This was pretty easy as all I had to do was &lt;strong&gt;REMOVE&lt;/strong&gt; drag code, and add &lt;strong&gt;SIMPLE&lt;/strong&gt; click handler for recording toggle.&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%2Fp25zunbtsp05lz5fuakv.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%2Fp25zunbtsp05lz5fuakv.png" alt="Remove Code" width="800" height="368"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This was merged as it was good enough for the PR, but I got the following recommendation for further improvement:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://github.com/tarasglek/chatcraft.org/pull/369#pullrequestreview-1863817353" rel="noopener noreferrer"&gt;Better status indicators for users&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/tarasglek/chatcraft.org/pull/369#issuecomment-1928960237" rel="noopener noreferrer"&gt;Animation of mic button when recording&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So I opened a &lt;a href="https://github.com/tarasglek/chatcraft.org/issues/413" rel="noopener noreferrer"&gt;follow up issue&lt;/a&gt; for that,&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%2Fyooq81z34am0ly9m090m.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%2Fyooq81z34am0ly9m090m.png" alt="follow up issue" width="800" height="384"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;and started looking for the next thing to work on.&lt;/p&gt;
&lt;h2&gt;
  
  
  Helping with Image Styling
&lt;/h2&gt;

&lt;p&gt;During this week's &lt;a href="https://www.chromium.org/for-testers/bug-reporting-guidelines/triage-best-practices/" rel="noopener noreferrer"&gt;Triage&lt;/a&gt; meeting, we were discussing about a &lt;a href="https://github.com/tarasglek/chatcraft.org/pull/286" rel="noopener noreferrer"&gt;Pull Request&lt;/a&gt; made by &lt;a href="https://github.com/mingming-ma" rel="noopener noreferrer"&gt;Mingming&lt;/a&gt; that would allow ChatCraft to accept &lt;strong&gt;images as input&lt;/strong&gt; and use OpenAI's &lt;a href="https://platform.openai.com/docs/guides/vision" rel="noopener noreferrer"&gt;Vision&lt;/a&gt; model to generate responses based on that. This was a truly killer feature and the PR &lt;strong&gt;had had&lt;/strong&gt; been in progress for about 4 months now. While almost ready to merge, there were some final &lt;strong&gt;touch ups&lt;/strong&gt; left and one of those was that image previews were not centred in the chat and the width was fixed to 50%.&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%2Ff34axeot5nnrwb8xnvfa.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%2Ff34axeot5nnrwb8xnvfa.png" alt="Old Image" width="800" height="305"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;and in the &lt;strong&gt;Modal Preview&lt;/strong&gt;, it was overlapping with the &lt;strong&gt;close button&lt;/strong&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%2Fwdjcwx6ak333136m1oqs.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%2Fwdjcwx6ak333136m1oqs.png" alt="Old Image Modal" width="800" height="492"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Everyone decided to fix that in a follow up issue. But I thought that could be a &lt;strong&gt;quick fix&lt;/strong&gt;, so immediately started working on that. &lt;/p&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%2Fcv4fw21vghcpk1dkgy44.png" alt="A memento for Call of Duty fans" width="440" height="322"&gt;For Call of Duty fans 😉
  


&lt;p&gt;By the end of the meeting, I was able to come up with some style changes and opened a &lt;a href="https://github.com/tarasglek/chatcraft.org/pull/419" rel="noopener noreferrer"&gt;Pull Request&lt;/a&gt; to be merged in to &lt;a href="https://github.com/tarasglek/chatcraft.org/tree/issue-285" rel="noopener noreferrer"&gt;Mingming's branch&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now the images looked like this:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In the Chat:&lt;/strong&gt;&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%2Futknsra9aju9qjf6dzr5.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%2Futknsra9aju9qjf6dzr5.png" alt="New Image" width="800" height="514"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In Modal Preview:&lt;/strong&gt;&lt;/p&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%2Fsugs0dld128p4xke5bqc.png" alt="Modal preview" width="800" height="668"&gt;No more overlap with close button
  


&lt;p&gt;&lt;a href="https://github.com/kliu57" rel="noopener noreferrer"&gt;Katie&lt;/a&gt; asked me to make a &lt;a href="https://github.com/tarasglek/chatcraft.org/pull/419#pullrequestreview-1868664848" rel="noopener noreferrer"&gt;minor change&lt;/a&gt; which I did shortly.&lt;/p&gt;

&lt;p&gt;And since everyone was happy with the fix,&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%2Fcjk02osymc6vj0r15wrd.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%2Fcjk02osymc6vj0r15wrd.png" alt="everyone was happy with the fix" width="435" height="356"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;it was merged shortly after, closing the corresponding &lt;a href="https://github.com/tarasglek/chatcraft.org/issues/418" rel="noopener noreferrer"&gt;follow up issue&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%2Fijvx5w62chcqztic1qwr.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%2Fijvx5w62chcqztic1qwr.png" alt="shortly merged" width="800" height="330"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Working on a Custom Command 💻
&lt;/h2&gt;

&lt;p&gt;ChatCraft has some in-built commands to quickly execute certain operation like opening a new chat, deleting existing etc. without the need to even touch your 🖱️&lt;br&gt;
A few weeks ago, I opened an &lt;a href="https://github.com/tarasglek/chatcraft.org/issues/356" rel="noopener noreferrer"&gt;issue&lt;/a&gt; to show a list of commands when someone entered an invalid command, i.e., not in ChatCraft Command Registry.&lt;/p&gt;

&lt;p&gt;I opened the &lt;a href="https://github.com/tarasglek/chatcraft.org/pull/396" rel="noopener noreferrer"&gt;Pull Request&lt;/a&gt; last week, and it had been in review since.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/rjwignar" rel="noopener noreferrer"&gt;Roy&lt;/a&gt; did an initial review and was curious why we need error handling in the following code since we know that &lt;code&gt;/commands&lt;/code&gt; should run without any problems.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// We are sure that this won't return null&lt;/span&gt;
&lt;span class="c1"&gt;// since prompt is definitely a command&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ChatCraftCommand&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parseCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&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;commandFunction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ChatCraftCommandRegistry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/commands &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nf"&gt;setShouldAutoScroll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&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="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;commandFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;forceScroll&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;err&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&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="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Error Running Command`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`There was an error running the command: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&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;which &lt;a href="https://github.com/humphd" rel="noopener noreferrer"&gt;Professor&lt;/a&gt; clarified. He also &lt;a href="https://github.com/tarasglek/chatcraft.org/pull/396#discussion_r1475423167" rel="noopener noreferrer"&gt;helped me notice&lt;/a&gt; that I forgot to add the new command to the text for &lt;code&gt;general help&lt;/code&gt; text.&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%2F1bwebg6txmzucbnvxa8e.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%2F1bwebg6txmzucbnvxa8e.png" alt="Roy Help" width="800" height="459"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Mingming later &lt;a href="https://github.com/tarasglek/chatcraft.org/pull/396#discussion_r1476806976" rel="noopener noreferrer"&gt;suggested&lt;/a&gt; we should have a &lt;strong&gt;Single Source of Truth&lt;/strong&gt; for the commands help text so we don't have to change it twice.&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%2F0pgxsi01qte683gxvqp6.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%2F0pgxsi01qte683gxvqp6.png" alt="Mingming help" width="800" height="459"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;which I fixed in &lt;a href="https://github.com/tarasglek/chatcraft.org/pull/396/commits/84273dc1c06df6ac733f3607d9ee97c1528a778d" rel="noopener noreferrer"&gt;this commit&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;There were some &lt;a href="https://github.com/tarasglek/chatcraft.org/pull/396#discussion_r1475470280" rel="noopener noreferrer"&gt;more discussions&lt;/a&gt; after which we decided to also show the command which failed in a &lt;strong&gt;toast message&lt;/strong&gt;, so the user knows if they typed something wrong.&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%2F59k7e67wz478jfr1tqmj.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%2F59k7e67wz478jfr1tqmj.png" alt="Toast Message" width="800" height="435"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But &lt;a href="https://github.com/tarasglek" rel="noopener noreferrer"&gt;Taras&lt;/a&gt; thought that it the unrecognized command should show up in inline with the list of commands instead of toasting.&lt;/p&gt;

&lt;p&gt;Now as simple as it seems, it &lt;strong&gt;WASN'T&lt;/strong&gt;. I had to understand the complicated system of how commands were accepting and using arguments to get this done. But finally I succeeded and this was &lt;a href="https://github.com/tarasglek/chatcraft.org/pull/396#issuecomment-1931321259" rel="noopener noreferrer"&gt;the result&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%2Fasaz1xig019sje7mjs4p.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%2Fasaz1xig019sje7mjs4p.png" alt="the result" width="800" height="454"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Passing arguments to this command would allow us to add a feature later on to query a specific command like so&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/commands some-command
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But for now, everyone seemed to like it and hence, this was also &lt;strong&gt;merged&lt;/strong&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%2Fw4iob0mvnlluwue5we5q.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%2Fw4iob0mvnlluwue5we5q.png" alt="merged" width="800" height="305"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Refactoring the approach to determine TTS Support 🔧
&lt;/h2&gt;

&lt;p&gt;As a follow up for &lt;a href="https://github.com/tarasglek/chatcraft.org/pull/357" rel="noopener noreferrer"&gt;Add TTS Support&lt;/a&gt; issue, I was supposed to modify my approach for determining if TTS was supported by the user's AI provider.&lt;/p&gt;

&lt;p&gt;Originally, I was checking if the user had chosen OpenAI as their provider, but this wasn't a good approach as in the future even OpenRouter could allow using the TTS mode.&lt;/p&gt;

&lt;p&gt;So the approach suggested by &lt;a href="https://github.com/tarasglek/chatcraft.org/pull/357#issuecomment-1905443626" rel="noopener noreferrer"&gt;Taras&lt;/a&gt; was that I should rather check if the list of current models included the model I was using for audio streaming.&lt;/p&gt;

&lt;p&gt;I spent a while in addressing this issue and opened a &lt;a href="https://github.com/tarasglek/chatcraft.org/pull/420" rel="noopener noreferrer"&gt;Pull Request&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This was also a long conversation and if you're interested in code changes, you can follow this link.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/tarasglek/chatcraft.org/pull/420/files" rel="noopener noreferrer"&gt;https://github.com/tarasglek/chatcraft.org/pull/420/files&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I was able to get it merged yesterday.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sidebar Enhancement REBOOT
&lt;/h2&gt;

&lt;p&gt;I had a &lt;a href="https://github.com/tarasglek/chatcraft.org/pull/300" rel="noopener noreferrer"&gt;Pull Request&lt;/a&gt; regarding &lt;strong&gt;Sidebar enhancement&lt;/strong&gt; opened since November, but the requirements kept changing and it became really hard to continue working on the same branch when it was decided to drop a lot of things.&lt;/p&gt;

&lt;h3&gt;
  
  
  Challenges with Git
&lt;/h3&gt;

&lt;p&gt;I initially tried to continue in the same PR, by following this plan.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Branch off of main&lt;/li&gt;
&lt;li&gt;Work now new changes&lt;/li&gt;
&lt;li&gt;Rebase on the original sidebar branch and fast-forward merge into it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;But the problem at hand was that my original sidebar branch was in my &lt;a href="https://stackoverflow.com/questions/2739376/definition-of-downstream-and-upstream" rel="noopener noreferrer"&gt;downstream&lt;/a&gt; fork, while my new branch was in the original repository. And I wasn't sure if I could merge into a downstream repo branch from an upstream repo branch.&lt;/p&gt;

&lt;p&gt;One approach I could use would be:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Make sure both sidebar branches (downstream and upstream) are rebased on upstream main.&lt;/li&gt;
&lt;li&gt;Create a new branch in downstream based on the sidebar branch in upstream.&lt;/li&gt;
&lt;li&gt;Fast-forward the new downstream branch into the downstream sidebar branch.&lt;/li&gt;
&lt;li&gt;Push new commits to downstream Gitub repo&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And this should have done it. But I thought &lt;strong&gt;NAHH&lt;/strong&gt;, its easier to create a new PR from the branch in upstream as this would be cleaner and we could also benefit from &lt;a href="https://www.cloudflare.com/en-ca/lp/ppc/cdn-x/?utm_source=google&amp;amp;utm_medium=cpc&amp;amp;utm_campaign=ao-fy-acq-namer_en_na-umbrella-ge-ge-prospecting-sch_g_brand_alpha&amp;amp;utm_content=Alpha_Brand_Applications_CDN&amp;amp;utm_term=cloudflare+cdn&amp;amp;campaignid=71700000110566648&amp;amp;adgroupid=58700008395369359&amp;amp;creativeid=669237101722&amp;amp;&amp;amp;_bt=669237101722&amp;amp;_bk=cloudflare%20cdn&amp;amp;_bm=b&amp;amp;_bn=g&amp;amp;_bg=152212902427&amp;amp;_placement=&amp;amp;_target=&amp;amp;_loc=9000845&amp;amp;_dv=c&amp;amp;awsearchcpc=1&amp;amp;gad_source=1&amp;amp;gclid=CjwKCAiA2pyuBhBKEiwApLaIO4KXE7bhAlO2iH6q0d6GjqKkLaFswa8teeG6DecuDpyNIdIXqNRI7BoCLYQQAvD_BwE&amp;amp;gclsrc=aw.ds" rel="noopener noreferrer"&gt;Cloudfare Previews&lt;/a&gt; in reviews.&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%2Fxhltsbp83nyn4kuummhp.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%2Fxhltsbp83nyn4kuummhp.png" alt="PR Closed" width="800" height="273"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Sidebar Changes
&lt;/h3&gt;

&lt;p&gt;But anyways, lets take a quick look at the changes I made.&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%2F2lo22i9amehhpyfvm80q.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%2F2lo22i9amehhpyfvm80q.png" alt="PR Reboot" width="800" height="206"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The overlay effect is only applied to mobile devices now, and &lt;a href="https://chakra-ui.com/docs/components/drawer/usage" rel="noopener noreferrer"&gt;Chakra Drawer&lt;/a&gt; is being used for that purpose.&lt;/li&gt;
&lt;li&gt;The search field has also been moved to the sidebar on mobile devices due to the lack of real estate in the header.&lt;/li&gt;
&lt;li&gt;The behaviour on desktop remains the same, except a few animations have been added to make the push content effect smoother.&lt;/li&gt;
&lt;li&gt;It has also been ensured that there is no redundant animation on page reloads, as it was one of the problems in the last PR.&lt;/li&gt;
&lt;li&gt;The &lt;a href="https://github.com/tarasglek/chatcraft.org/pull/300/commits/3348d079742d46c144238d931967c1ca2fdbd82c" rel="noopener noreferrer"&gt;Pin Sidebar feature&lt;/a&gt; has also been removed.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here's a Demo 🎬&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;On Desktop:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The gif was too large so couln't attach&lt;br&gt;
&lt;a href="https://private-user-images.githubusercontent.com/78865303/303812283-dfaa70e8-dbbe-4ce1-8035-e77b75937b85.gif?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MDc2MDExNDYsIm5iZiI6MTcwNzYwMDg0NiwicGF0aCI6Ii83ODg2NTMwMy8zMDM4MTIyODMtZGZhYTcwZTgtZGJiZS00Y2UxLTgwMzUtZTc3Yjc1OTM3Yjg1LmdpZj9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNDAyMTAlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjQwMjEwVDIxMzQwNlomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPWE1NjA0NzE3MjU2MWE5N2ViZTMwZGM3ZmMzNWQzMDUwNTBmMzAzNWI3MmExODRhYWU5NDQ5NTJiOWUxNzY2NjUmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JmFjdG9yX2lkPTAma2V5X2lkPTAmcmVwb19pZD0wIn0.B72R8IFkHHdKnqmSCy4gyb325tSUmZ4I7hSIzwpyZso" rel="noopener noreferrer"&gt;Link&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;On Mobile:&lt;/strong&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%2Fgmaylzq5725xl6enlg4f.gif" 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%2Fgmaylzq5725xl6enlg4f.gif" alt="On Mobile" width="477" height="962"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I would suggest looking at the PR directly.&lt;br&gt;
&lt;a href="https://github.com/tarasglek/chatcraft.org/pull/437" rel="noopener noreferrer"&gt;https://github.com/tarasglek/chatcraft.org/pull/437&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Reviewing Other PRs
&lt;/h2&gt;

&lt;p&gt;These were all my direct contributions to this week's release, but &lt;br&gt;
I also reviewed Pull Requests made by others.&lt;/p&gt;

&lt;p&gt;The first one was by Professor, to &lt;a href="https://github.com/tarasglek/chatcraft.org/pull/402" rel="noopener noreferrer"&gt;allow image downloads&lt;/a&gt; of messages.&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%2F70yv851ng8ub74b1wqu5.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%2F70yv851ng8ub74b1wqu5.png" alt="Image Downloads" width="800" height="416"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The second was by &lt;a href="https://github.com/kliu57" rel="noopener noreferrer"&gt;Katie&lt;/a&gt;, that would allow users to &lt;a href="https://github.com/tarasglek/chatcraft.org/pull/421" rel="noopener noreferrer"&gt;store multiple providers&lt;/a&gt; at once.&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%2Fde81pmqdph0w02d9ec2r.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%2Fde81pmqdph0w02d9ec2r.png" alt="store multiple providers" width="800" height="305"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Release 1.2.0 🚀
&lt;/h2&gt;

&lt;p&gt;This sure was a &lt;strong&gt;pretty&lt;/strong&gt; busy week. I got &lt;strong&gt;4&lt;/strong&gt; pull requests &lt;strong&gt;merged&lt;/strong&gt;, &lt;a href="https://github.com/tarasglek/chatcraft.org/pull/437" rel="noopener noreferrer"&gt;1 Reboot&lt;/a&gt;, and &lt;strong&gt;2 Reviews&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Everyone worked pretty hard, and with all contributions, we were able to successfully release &lt;a href="https://github.com/tarasglek/chatcraft.org/releases/tag/v1.2.0" rel="noopener noreferrer"&gt;ChatCraft v1.2.0&lt;/a&gt; this week.&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%2Fp3jt8a6wlss0xmo7as5n.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%2Fp3jt8a6wlss0xmo7as5n.png" alt="ChatCraft v1.2.0" width="800" height="635"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Cheers, and looking forward to 1.3 next week 💪&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>react</category>
      <category>opensource</category>
      <category>css</category>
    </item>
    <item>
      <title>Taking an Open Source Project to Release 1.1 🚀</title>
      <dc:creator>Amnish Singh Arora</dc:creator>
      <pubDate>Sat, 03 Feb 2024 22:59:25 +0000</pubDate>
      <link>https://forem.com/amnish04/taking-an-open-source-project-to-release-11-2ngg</link>
      <guid>https://forem.com/amnish04/taking-an-open-source-project-to-release-11-2ngg</guid>
      <description>&lt;p&gt;In my &lt;a href="https://dev.to/amnish04/openai-has-text-to-speech-support-now-4mlp"&gt;last post&lt;/a&gt;, I discussed how I integrated &lt;strong&gt;Text to Speech&lt;/strong&gt; support into &lt;a href="https://chatcraft.org/" rel="noopener noreferrer"&gt;ChatCraft&lt;/a&gt; using &lt;strong&gt;OpenAI's&lt;/strong&gt; &lt;a href="https://platform.openai.com/docs/guides/text-to-speech" rel="noopener noreferrer"&gt;TTS API&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In this post, I'll share my progress on that &lt;a href="https://github.com/tarasglek/chatcraft.org/pull/357" rel="noopener noreferrer"&gt;Pull Request&lt;/a&gt; and other contributions I made as a part of releasing &lt;a href="https://github.com/tarasglek/chatcraft.org/releases/tag/v1.1.0" rel="noopener noreferrer"&gt;v1.1&lt;/a&gt; of the project.&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%2Ffk8atag8ppatsc0sp3oo.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%2Ffk8atag8ppatsc0sp3oo.png" alt="1.1" width="800" height="390"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;p&gt; 1. Text to Speech Support&lt;br&gt;
 2. Filing and fixing follow ups&lt;br&gt;
       2.1. Using Better icons for TTS button&lt;br&gt;
       2.2. Fixing TTS toggle behaviour&lt;br&gt;
       2.3. TTS should abort when switched off&lt;br&gt;
 3. Helping with migrating the Menu Component&lt;br&gt;
       3.1. Suggesting changes on GitHub&lt;br&gt;
       3.2. Pushing Fixes&lt;br&gt;
 4. Reviewing a Pull Request&lt;br&gt;
 5. Release v1.1 🚀&lt;/p&gt;
&lt;h2&gt;
  
  
  Text to Speech Support
&lt;/h2&gt;

&lt;p&gt;Before talking about other contributions, I was able to get an initial version of text to speech &lt;a href="https://github.com/tarasglek/chatcraft.org/pull/357" rel="noopener noreferrer"&gt;landed&lt;/a&gt; successfully after a few discussions and changes.&lt;/p&gt;

&lt;p&gt;My professor was a little confused why I was managing &lt;strong&gt;Promises&lt;/strong&gt; of &lt;strong&gt;audio urls&lt;/strong&gt; instead of directly storing url strings in my audio queue. I tried to explain it with the help of an example.&lt;/p&gt;

&lt;p&gt;Here's the link if you're interested:&lt;br&gt;
&lt;a href="https://github.com/tarasglek/chatcraft.org/pull/357#discussion_r1473470003" rel="noopener noreferrer"&gt;https://github.com/tarasglek/chatcraft.org/pull/357#discussion_r1473470003&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%2F9dgiix1p04hh6duuzi2i.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%2F9dgiix1p04hh6duuzi2i.png" alt="Explanation" width="800" height="641"&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%2Fqmec223hzx5ahtm6mse3.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%2Fqmec223hzx5ahtm6mse3.png" alt="PR Merged" width="800" height="318"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I embedded a &lt;a href="https://www.youtube.com/watch?v=cj0ZnChDIQE" rel="noopener noreferrer"&gt;video demo&lt;/a&gt; in my &lt;a href="https://dev.to/amnish04/openai-has-text-to-speech-support-now-4mlp"&gt;last post&lt;/a&gt;, and you can try it yourself by visiting &lt;a href="https://chatcraft.org/" rel="noopener noreferrer"&gt;ChatCraft&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It was &lt;strong&gt;not perfect&lt;/strong&gt; by any chance, and so I had to open a couple of &lt;strong&gt;follow up issues&lt;/strong&gt; aimed to fix the technical shortcomings.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://github.com/tarasglek/chatcraft.org/issues/386" rel="noopener noreferrer"&gt;https://github.com/tarasglek/chatcraft.org/issues/386&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/tarasglek/chatcraft.org/issues/387" rel="noopener noreferrer"&gt;https://github.com/tarasglek/chatcraft.org/issues/387&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;As soon as it was merged, I started receiving more &lt;a href="https://github.com/tarasglek/chatcraft.org/pull/393#pullrequestreview-1855157158" rel="noopener noreferrer"&gt;feedback&lt;/a&gt; that I couldn't even get in original reviews because now people were forced to use it 😉.&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%2Fhf5bwdv2oso6fqmansyt.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%2Fhf5bwdv2oso6fqmansyt.png" alt="Feedback" width="800" height="567"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And so, it was time to file &lt;strong&gt;even more&lt;/strong&gt; follow ups aimed to address the issues &lt;a href="https://github.com/tarasglek/chatcraft.org/pull/357#discussion_r1473577322" rel="noopener noreferrer"&gt;brought up&lt;/a&gt; by Taras.&lt;/p&gt;
&lt;h2&gt;
  
  
  Filing and fixing follow ups
&lt;/h2&gt;

&lt;p&gt;Various issues were encountered after my PR was merged.&lt;/p&gt;
&lt;h3&gt;
  
  
  Using Better icons for TTS button
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://github.com/tarasglek/chatcraft.org/pull/357#discussion_r1473577322" rel="noopener noreferrer"&gt;first one&lt;/a&gt; was that the icons I used for TTS enable/disable button didn't do a good job at indicating the state of the feature.&lt;/p&gt;

&lt;p&gt;I quickly opened &lt;a href="https://github.com/tarasglek/chatcraft.org/issues/392" rel="noopener noreferrer"&gt;an issue&lt;/a&gt; for that&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%2F34rk9og9duxudavrotn3.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%2F34rk9og9duxudavrotn3.png" alt="Button Issue" width="800" height="847"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;and replaced the icons with &lt;a href="https://react-icons.github.io/react-icons/icons/md/" rel="noopener noreferrer"&gt;Material Design variants&lt;/a&gt; as suggested.&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%2F1s7g2np35lfddpyeam2v.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%2F1s7g2np35lfddpyeam2v.png" alt="New Icons" width="800" height="584"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After a quick review, the &lt;a href="https://github.com/tarasglek/chatcraft.org/pull/393" rel="noopener noreferrer"&gt;Pull Request&lt;/a&gt; was merged.&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%2Favr355ywm0h86zefk6f1.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%2Favr355ywm0h86zefk6f1.png" alt="Quick Review" width="800" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Time to look at the other issues!&lt;/p&gt;
&lt;h3&gt;
  
  
  Fixing TTS toggle behaviour
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://github.com/tarasglek/chatcraft.org/pull/357#issuecomment-1920127073" rel="noopener noreferrer"&gt;next thing&lt;/a&gt; on the list was a little annoying for the users.&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%2F5yrrizmsf7tq5gdbu0r0.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%2F5yrrizmsf7tq5gdbu0r0.png" alt="TTS complaint" width="800" height="155"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You read that &lt;strong&gt;RIGHT&lt;/strong&gt;! For some reason, the text to speech functionality was &lt;strong&gt;always enabled&lt;/strong&gt; no matter if the state of the toggle button.&lt;/p&gt;

&lt;p&gt;Another follow up &lt;a href="https://github.com/tarasglek/chatcraft.org/issues/389" rel="noopener noreferrer"&gt;issue&lt;/a&gt; had to be opened.&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%2Fxk0xqmw5oi7ke92gqx34.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%2Fxk0xqmw5oi7ke92gqx34.png" alt="Button Toggle Bug" width="800" height="458"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The problem was that I wasn't checking for the TTS setting in the else if branch in the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isTtsSupported&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nf"&gt;getSettings&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;announceMessages&lt;/span&gt;&lt;span class="p"&gt;)&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="nx"&gt;sentenceEndRegex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ttsWordsBuffer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// Has full sentence&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Reset lastIndex before calling exec&lt;/span&gt;
    &lt;span class="nx"&gt;sentenceEndRegex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lastIndex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&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;sentenceEndIndex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sentenceEndRegex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ttsWordsBuffer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Pass the sentence to tts api for processing&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;textToBeProcessed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ttsWordsBuffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sentenceEndIndex&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&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;audioClipUri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;textToSpeech&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;textToBeProcessed&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;addToAudioQueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;audioClipUri&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Update the tts Cursor&lt;/span&gt;
    &lt;span class="nx"&gt;ttsCursor&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;sentenceEndIndex&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ttsWordsBuffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;TTS_BUFFER_THRESHOLD&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Flush the entire buffer into tts api&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;audioClipUri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;textToSpeech&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ttsWordsBuffer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;addToAudioQueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;audioClipUri&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;ttsCursor&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;ttsWordsBuffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&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;I quickly opened a &lt;a href="https://github.com/tarasglek/chatcraft.org/pull/390" rel="noopener noreferrer"&gt;Pull Request&lt;/a&gt; for the fix.&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%2Fihohm4yvro2ztad642ee.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%2Fihohm4yvro2ztad642ee.png" alt="Hotfix PR" width="800" height="395"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;and felt the need for adding a &lt;code&gt;hotfix&lt;/code&gt; label to the repo&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%2Fi5sg1bf4v1cunry9e886.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%2Fi5sg1bf4v1cunry9e886.png" alt="hotfix label" width="800" height="108"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;as this sure was one.&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%2Feri73ock1h5b71eb56pn.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%2Feri73ock1h5b71eb56pn.png" alt="hotfix label discussion" width="800" height="370"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  TTS should abort when switched off
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://github.com/tarasglek/chatcraft.org/pull/357#issuecomment-1920127560" rel="noopener noreferrer"&gt;third problem&lt;/a&gt; was that the TTS announcement always kept playing, even when the feature was turned off using the toggle button.&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%2F49pi60slk1vab55q1zt9.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%2F49pi60slk1vab55q1zt9.png" alt="Third problem" width="800" height="149"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I filed an &lt;a href="https://github.com/tarasglek/chatcraft.org/issues/391" rel="noopener noreferrer"&gt;issue&lt;/a&gt; for that&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%2Fg748vlil4hmvepsqb255.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%2Fg748vlil4hmvepsqb255.png" alt="tts abort issue" width="800" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;but still need to work on it.&lt;/p&gt;

&lt;p&gt;Here's the state of &lt;strong&gt;follow ups&lt;/strong&gt; so far.&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%2F2hh2xtt6rfxaljnfwu30.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%2F2hh2xtt6rfxaljnfwu30.png" alt="State of follow ups 1" width="800" height="203"&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%2F35v4vvbr5z0yhipz2xzn.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%2F35v4vvbr5z0yhipz2xzn.png" alt="State of follow ups 2" width="800" height="113"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Helping with migrating the Menu Component
&lt;/h2&gt;

&lt;p&gt;The next big thing I worked on this week was helping &lt;a href="https://github.com/Rachit1313" rel="noopener noreferrer"&gt;Rachit&lt;/a&gt; with the creation of a new Menu Component for the application using the &lt;a href="https://szhsin.github.io/react-menu/" rel="noopener noreferrer"&gt;react-menu&lt;/a&gt; package.&lt;/p&gt;

&lt;p&gt;Here's some &lt;a href="https://github.com/tarasglek/chatcraft.org/issues/359" rel="noopener noreferrer"&gt;context&lt;/a&gt; for you guys.&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%2F764exbberammjiyswyi1.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%2F764exbberammjiyswyi1.png" alt="Context for React Menu" width="800" height="396"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Rachit was able to get the &lt;strong&gt;functionality&lt;/strong&gt; working for our &lt;strong&gt;component wrapper&lt;/strong&gt;, but &lt;a href="https://github.com/tarasglek/chatcraft.org/pull/374#issuecomment-1917737299" rel="noopener noreferrer"&gt;needed some help&lt;/a&gt; with the styling part.&lt;/p&gt;

&lt;p&gt;Since I was asked for 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%2Fd1qa5pfmt51j8fbrquls.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%2Fd1qa5pfmt51j8fbrquls.png" alt="Need Help" width="800" height="384"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I had to &lt;strong&gt;get into action&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Suggesting changes on GitHub
&lt;/h3&gt;

&lt;p&gt;The first step was to review behaviour and suggest any initial changes that came to my mind.&lt;/p&gt;

&lt;p&gt;I quickly noticed a &lt;a href="https://github.com/tarasglek/chatcraft.org/pull/374#issuecomment-1918133731" rel="noopener noreferrer"&gt;weird behaviour&lt;/a&gt; that did not exist before&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%2Fr3lbsecgtq72mrpcxuhl.gif" 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%2Fr3lbsecgtq72mrpcxuhl.gif" alt="weird behaviour" width="1321" height="504"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I posted a &lt;a href="https://github.com/tarasglek/chatcraft.org/pull/374#discussion_r1472150023" rel="noopener noreferrer"&gt;suggestion&lt;/a&gt; on GitHub that could be &lt;a href="https://haacked.com/archive/2019/06/03/suggested-changes/" rel="noopener noreferrer"&gt;directly committed&lt;/a&gt; to fix that issue.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fixed Behaviour:&lt;/strong&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%2Flouypc9dx2nz8f0acaqb.gif" 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%2Flouypc9dx2nz8f0acaqb.gif" alt="Fixed Behaviour" width="1320" height="504"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We continued with this &lt;a href="https://haacked.com/archive/2019/06/03/suggested-changes/" rel="noopener noreferrer"&gt;GitHub Suggestions&lt;/a&gt; for a while&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%2Fx1px4q18st3nbxgqryyn.gif" 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%2Fx1px4q18st3nbxgqryyn.gif" alt="GitHub Suggestions" width="720" height="496"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Pushing Fixes
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Until&lt;/strong&gt; &lt;a href="https://github.com/humphd" rel="noopener noreferrer"&gt;professor&lt;/a&gt; suggested us &lt;a href="https://github.com/tarasglek/chatcraft.org/pull/374#issuecomment-1918203141" rel="noopener noreferrer"&gt;another approach&lt;/a&gt; of collaboration.&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%2Fpqojtd9bi8shft1duao0.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%2Fpqojtd9bi8shft1duao0.png" alt="another approach" width="800" height="579"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This was exactly what I wanted to do, and now that I knew it was acceptable, I asked Rachit if could push directly to his branch. And &lt;strong&gt;pushed the fixes&lt;/strong&gt; for anything I could think of.&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%2Fus6jjgl2fz5ji4t1nbnq.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%2Fus6jjgl2fz5ji4t1nbnq.png" alt="pushed the fixes" width="800" height="330"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For full details, please follow the &lt;a href="https://github.com/tarasglek/chatcraft.org/pull/374" rel="noopener noreferrer"&gt;Pull Request&lt;/a&gt; directly. This was the &lt;strong&gt;biggest conversation&lt;/strong&gt; I ever I had in a PR.&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%2Frgs4ccdushxfkn6gtt3x.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%2Frgs4ccdushxfkn6gtt3x.png" alt="biggest conversation" width="800" height="87"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Reviewing a Pull Request
&lt;/h2&gt;

&lt;p&gt;Last but not least, I reviewed a &lt;a href="https://github.com/tarasglek/chatcraft.org/pull/371" rel="noopener noreferrer"&gt;Pull Request&lt;/a&gt; &lt;a href="https://github.com/kliu57" rel="noopener noreferrer"&gt;Katie&lt;/a&gt; was working on, that would allow users to store &lt;a href="https://www.techtarget.com/whatis/definition/metadata" rel="noopener noreferrer"&gt;metaData&lt;/a&gt; for multiple AI providers at once.&lt;/p&gt;

&lt;p&gt;I made a simple suggestion on this one to create a &lt;code&gt;url&lt;/code&gt; to &lt;code&gt;provider name&lt;/code&gt; &lt;strong&gt;mapping&lt;/strong&gt; instead of manually determining provider name with a &lt;strong&gt;switch&lt;/strong&gt; statement.&lt;/p&gt;

&lt;p&gt;This suggestion was &lt;a href="https://github.com/tarasglek/chatcraft.org/pull/371#issuecomment-1920368076" rel="noopener noreferrer"&gt;accepted&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%2F6caxjsj9s0j42jdqvkyv.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%2F6caxjsj9s0j42jdqvkyv.png" alt="suggestion was accepted" width="800" height="365"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And after reviews from &lt;a href="https://github.com/humphd" rel="noopener noreferrer"&gt;professor&lt;/a&gt;, this PR was also merged 🎊&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%2Fnl61nc0v7vb86pnu6hou.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%2Fnl61nc0v7vb86pnu6hou.png" alt="this PR was also merged" width="800" height="225"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Release v1.1 🚀
&lt;/h2&gt;

&lt;p&gt;All this work, was just a part of &lt;strong&gt;my&lt;/strong&gt; contributions for &lt;a href="https://github.com/tarasglek/chatcraft.org/releases/tag/v1.1.0" rel="noopener noreferrer"&gt;Release 1.1&lt;/a&gt; that happened yesterday.&lt;/p&gt;

&lt;p&gt;We have made significant progress if count changes made by all others&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%2Fl8qxnw6v2ddm59kdmm4n.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%2Fl8qxnw6v2ddm59kdmm4n.png" alt="significant progress" width="800" height="560"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;and here's one for the new contributors 🍾🥂&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%2Fvo5tpj58njvjxskb3cqy.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%2Fvo5tpj58njvjxskb3cqy.png" alt="one to the new contributors" width="793" height="582"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>react</category>
      <category>openai</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
