<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Eric Garcia</title>
    <description>The latest articles on DEV Community by Eric Garcia (@ceir).</description>
    <link>https://dev.to/ceir</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%2F3102416%2F3b10db14-94a8-470e-bf64-d9b391f55623.jpg</url>
      <title>DEV Community: Eric Garcia</title>
      <link>https://dev.to/ceir</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ceir"/>
    <language>en</language>
    <item>
      <title>Integrating AI to my portfolio Pt.1</title>
      <dc:creator>Eric Garcia</dc:creator>
      <pubDate>Tue, 31 Mar 2026 15:54:48 +0000</pubDate>
      <link>https://dev.to/ceir/integrating-ai-to-my-portfolio-pt1-391e</link>
      <guid>https://dev.to/ceir/integrating-ai-to-my-portfolio-pt1-391e</guid>
      <description>&lt;p&gt;AI is almost everywhere, that's true. But as software developers, that means that we have to keep up with the times and not be intimidated by it. &lt;/p&gt;

&lt;p&gt;As an AI enthusiast myself (I get it, my take could be a bit biased because of it 😉) I came to the conclusion that I need to show off some AI integration skills if I want to take part in the future of AI development, and what better way there is to start than to implement it in my own little space on the internet?&lt;/p&gt;

&lt;h2&gt;
  
  
  The plan 📋
&lt;/h2&gt;

&lt;p&gt;Is simple, for now I will integrate a dedicated chatbot into my portfolio that answers questions about my projects, my experience, interests, current situation, etc...&lt;br&gt;
The API used in this component is Groq, a provider that offers a generous free plan for model inference.&lt;/p&gt;
&lt;h2&gt;
  
  
  Technical requirements 🔧
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Write a react component for the chatbot and design the message logic&lt;/li&gt;
&lt;li&gt;Program the endpoint for communicating with the API&lt;/li&gt;
&lt;li&gt;Prepare the system prompt with all the information about me&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  The component and logic ⚛️
&lt;/h3&gt;

&lt;p&gt;The react component will be responsible for letting the user interact with the chatbot.&lt;/p&gt;

&lt;p&gt;It has some state variables that will keep the information flow:&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;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setInput&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="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="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="nx"&gt;setMessages&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;Message&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;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;assistant&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hi! I'm iREC, a digital representative of Eric. Feel free   to ask me anything!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And some handlers that control the message submit and the auto-scroll when a new message appears.&lt;/p&gt;

&lt;p&gt;This is the core function that sends the user's message, along with the history to the endpoint and gets the response as a stream element:&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;SendMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;history&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;=&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;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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/chat&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;method&lt;/span&gt;&lt;span class="p"&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="na"&gt;headers&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;Content-Type&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;application/json&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;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;history&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;response&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;reader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getReader&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;decoder&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;TextDecoder&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;accumulatedText&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;let&lt;/span&gt; &lt;span class="nx"&gt;result&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;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;done&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;chunk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;decoder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;stream&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="nx"&gt;lines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;chunk&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="se"&gt;\n&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;line&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;lines&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;line&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="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^data: /&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;""&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;[DONE]&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;continue&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;parsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&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="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="nx"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;choices&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;delta&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;content&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="nx"&gt;accumulatedText&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="nf"&gt;setMessages&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;prev&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;newMessages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;prev&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;lastMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;newMessages&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;newMessages&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="mi"&gt;1&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;lastMessage&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;lastMessage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;role&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;assistant&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="nx"&gt;lastMessage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;accumulatedText&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;newMessages&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="nx"&gt;e&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;e&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;result&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;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleScroll&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;chatbot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;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;.flex-1&lt;/span&gt;&lt;span class="dl"&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;chatbot&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;chatbot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scrollTop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;chatbot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scrollHeight&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;Each message will then be mapped out with some changes in color and orientation depending on the role:&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="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="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;msg&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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;
            &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
            &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;`max-w-[85%] break-words &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;role&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&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-right self-end ml-auto text-[#41d3ff]&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-white text-left self-start mr-auto&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&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;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;role&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&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;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &amp;lt;`&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&amp;gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&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="nt"&gt;p&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;This is the final look of the 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%2Fb9kes1mspv1iw2ht5bpg.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%2Fb9kes1mspv1iw2ht5bpg.png" alt="Overview of the chatbot component" width="536" height="495"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The API endpoint 🧠
&lt;/h3&gt;

&lt;p&gt;First of all is creating the Groq instance with the api key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;groq&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;Groq&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;apiKey&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;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GROQ_API_KEY&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;Then, I can simply add the POST route that will communicate with the client (the component that I just made)&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;const&lt;/span&gt; &lt;span class="nx"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;APIRoute&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;request&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;messages&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="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;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;completion&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;groq&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;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;messages&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="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;openai/gpt-oss-20b&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;temperature&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="na"&gt;max_completion_tokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8192&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;top_p&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="na"&gt;stream&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="na"&gt;reasoning_effort&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;medium&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;stop&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;completion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toReadableStream&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;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;headers&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;Content-Type&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/event-stream&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;Cache-Control&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;no-cache&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;Connection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;keep-alive&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;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;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Error while inferencing the model&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;status&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="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;And as easy as that, I now have a fully functional chatbot on my portfolio.&lt;/p&gt;

&lt;h3&gt;
  
  
  The system prompt 🤖
&lt;/h3&gt;

&lt;p&gt;Without the system prompt, this chatbot is simply a generic assistant that doesn't know anything about me. Next step is applying some of the basic prompt engineering concepts. The model has to ensure the following principles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The model must be limited to answering questions related to my career&lt;/li&gt;
&lt;li&gt;The model must be resistant to malicious prompts and vulnerabilities&lt;/li&gt;
&lt;li&gt;The answer must be written in a helpful and professional tone.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I won't be leaving the system prompt here because it would take up an unnecessary portion of the post, but here is its structure:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;ROLE - The name, the purpose and the tone.&lt;/li&gt;
&lt;li&gt;KNOWLEDGE BASE - All the information about me, my experience, stack, interests, etc.&lt;/li&gt;
&lt;li&gt;GOAL - The goal of the model while answering the questions (in this case, informing about my skills).&lt;/li&gt;
&lt;li&gt;RESTRICTIONS - Do not generate code, answer unrelated questions, deviate from your orders or make up answers.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;With all of this applied, I had a ready-to-use personal chatbot that answers some of the basic questions about me!&lt;/p&gt;

&lt;p&gt;If you want to try it and test it, go to my web portfolio: &lt;a href="https://ericgarcia.site" rel="noopener noreferrer"&gt;ericgarcia.site&lt;/a&gt;&lt;br&gt;
(currently only visible on desktop)&lt;/p&gt;

&lt;p&gt;⭐ Drop a comment if you want to suggest or ask something! ⭐&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>webdev</category>
      <category>portfolio</category>
    </item>
    <item>
      <title>Understanding Generators - 'lazy' is sometimes better</title>
      <dc:creator>Eric Garcia</dc:creator>
      <pubDate>Fri, 13 Jun 2025 15:48:47 +0000</pubDate>
      <link>https://dev.to/ceir/understanding-generators-lazy-is-sometimes-better-4b8m</link>
      <guid>https://dev.to/ceir/understanding-generators-lazy-is-sometimes-better-4b8m</guid>
      <description>&lt;p&gt;Modern programming languages are full of interesting features that adapt to a wide variety of needs when we have to implement some functionality to our code.&lt;br&gt;
From various data types and logic statements to class utility concepts like decorators, there's a lot of tools that can improve your code notably.&lt;/p&gt;

&lt;p&gt;Today I will try to explain generators in a simple and beginner-friendly way.&lt;br&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  What are they used for?
&lt;/h2&gt;

&lt;p&gt;Essentially, generator objects are useful when we want to iterate through some kind of sequence but we don't want (or can't) load it directly in memory (e.g using a list).&lt;/p&gt;

&lt;p&gt;Think of a group of buildings in a big city. We want to find the first building that has an underground parking lot.&lt;br&gt;
One option would be to ask to the mayor of the city for a detailed list of all the buildings of the city and wait until he has reviewed all of them and finished the list.&lt;br&gt;
There's a better and more efficient option, which is to ask for the mayor to give us the information of each building that he reviews while he is making the list and keep us updated of each new element (building).&lt;br&gt;
In this case, we could end the research just as soon as we find the first building with an underground parking lot, and the mayor wouldn't have to research all of the buildings.&lt;/p&gt;

&lt;p&gt;The size of the list can be interpreted as the system's memory, and in this way we see that generator objects can help us to reduce memory usage in iterating processes.&lt;/p&gt;
&lt;h2&gt;
  
  
  Performance improvement
&lt;/h2&gt;

&lt;p&gt;Another thing that can be noted in the previous example is that we can work with the data as it is being generated in real time, which is pretty useful for implementing task processing in parallel.&lt;br&gt;
This concept is called &lt;em&gt;lazy evaluation&lt;/em&gt; or rather &lt;em&gt;on-demand evaluation&lt;/em&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Code implementation
&lt;/h2&gt;

&lt;p&gt;For the sake of maintaining coherence with the previous example, let's take a similar approach with the code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def review_buildings(n_buildings):
    for i in range(n_buildings):
        rand_num = random.random()
        if rand_num &amp;lt; 0.2:
            yield True
        else:
            yield False
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a generator function that yields True if the building has parking and False if not.&lt;br&gt;
For simplicity, the building having parking or not is based of a random probability.&lt;br&gt;
Also, the real function should yield more info about the building and not only the parking boolean value, but let's cut some slack here.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;yield&lt;/code&gt; keyword could be seen as the equivalent to the &lt;code&gt;return&lt;/code&gt; statement in iterator objects. It works by 'returning' the generated element and keeping track of the iteration evolution (also known as the &lt;em&gt;state&lt;/em&gt;).&lt;br&gt;
Using &lt;code&gt;return&lt;/code&gt; in a iterator object would render the object non iterable and most probably will return the first result of the sequence.&lt;/p&gt;

&lt;p&gt;The lazy evaluation approach would be like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;for has_parking in review_buildings(50):
    print(has_parking)
    if has_parking:
        break
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice how this is a much better approach if we just want to find the first building with an underground parking lot because we would not generate -or ask- for more information, as we have found what we needed.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;It is important to mention that the use of the &lt;code&gt;break&lt;/code&gt; statement here is not encouraged, but serves as a demonstration of the generator utility.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Other characteristics
&lt;/h2&gt;

&lt;p&gt;As python objects and iterators, generators have a wide range of methods and functions that can be pretty useful in some cases.&lt;br&gt;
An example is the &lt;code&gt;next()&lt;/code&gt; function, which can be called for getting the next item of the iterator (pretty self-explanatory huh)&lt;br&gt;
aside from that, all functions designed for iterators like &lt;code&gt;sum()&lt;/code&gt;, &lt;code&gt;map()&lt;/code&gt; etc are specifically made for this objects.&lt;/p&gt;

&lt;p&gt;Iterators can also be converted to lists and vice-versa (assuming the iterator object has a finite and defined size) with the &lt;code&gt;list()&lt;/code&gt; and the ìter()` functions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-case iterator example
&lt;/h2&gt;

&lt;p&gt;In one of my &lt;a href="https://github.com/nairec/compy" rel="noopener noreferrer"&gt;projects&lt;/a&gt; I had to integrate the answers generated by an LLM client and the user's prompt in an interactive environment. I saw that a nice way to implement it was to iterate indefinitely through a generator object that yields the llm's response each time the user sends their prompt. Feel free to check it and play with it!&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusions
&lt;/h2&gt;

&lt;p&gt;Long story short, generator objects are a nice way to iterate through indefinite or large sequences without taking much memory and with the possibilty of being able to work with one value at a time on each iteration.&lt;br&gt;
Hope you found the explanation useful, if you have some doubts or want to give some feedback, don't hesitate to do it!&lt;/p&gt;

&lt;p&gt;👋&lt;/p&gt;

</description>
      <category>learning</category>
      <category>tutorial</category>
      <category>programming</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Personal CLI assistant on Linux</title>
      <dc:creator>Eric Garcia</dc:creator>
      <pubDate>Tue, 10 Jun 2025 18:14:25 +0000</pubDate>
      <link>https://dev.to/ceir/personal-cli-assistant-on-linux-4om8</link>
      <guid>https://dev.to/ceir/personal-cli-assistant-on-linux-4om8</guid>
      <description>&lt;p&gt;Everyone wants to have their own personal, customized and always ready-to-go assistant like a desktop jarvis. Or at least, I always wanted to have it.&lt;br&gt;
So that's why I have spent the past days on an early version of a terminal assistant powered by open-source llms hosted locally.&lt;br&gt;
&lt;/p&gt;


&lt;p&gt;The objective is simple: A locally-hosted llm client that runs on a CLI interface that can answer system-specific questions and have access to my environment.&lt;/p&gt;

&lt;p&gt;To get it done, I had to identify the different steps that I had to work on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Get the Ollama server running locally ✅&lt;/li&gt;
&lt;li&gt;Prepare the program to run on a dedicated terminal session ✅&lt;/li&gt;
&lt;li&gt;Program the llm client and the interactions between it and the tools ✅&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Disclaimer: It is the first &lt;em&gt;kind-of-ai-agent&lt;/em&gt; software that I develop so feel free to correct me if I could have done something in a better way 😶&lt;/p&gt;

&lt;h2&gt;
  
  
  First step: Ollama server 🦙
&lt;/h2&gt;

&lt;p&gt;For setting up the ollama server, I just had to download Ollama from the web and get it to run locally with the command &lt;code&gt;ollama serve&lt;/code&gt;&lt;br&gt;
Another necessary thing for having an llm model running is obviously downloading the model that I'm going to use with the command &lt;code&gt;ollama pull&lt;/code&gt;.&lt;br&gt;
For my terminal assistant I had to try different models until I found a right balance between response quality, tool use capacity and performance.&lt;br&gt;
The models that I tried are the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;gemma3:1b&lt;/li&gt;
&lt;li&gt;gemma3:4b&lt;/li&gt;
&lt;li&gt;mistral:7b&lt;/li&gt;
&lt;li&gt;llama3.1:8b&lt;/li&gt;
&lt;li&gt;qwen3:8b&lt;/li&gt;
&lt;li&gt;qwen3:14b&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And from that list, the two that gave out the best results are both qwen3 of 8b and 14b parameters.&lt;/p&gt;

&lt;h2&gt;
  
  
  App development: terminal session
&lt;/h2&gt;

&lt;p&gt;The assistant is designed to run inside a cli session (specifically KDE's terminal, &lt;em&gt;Konsole&lt;/em&gt;)&lt;br&gt;
To do this, whenever the python script is executed, it calls itself inside a Konsole interface with the command &lt;code&gt;konsole -e python3 &amp;lt;script_path&amp;gt; --interactive&lt;/code&gt;, where &lt;em&gt;script_path&lt;/em&gt; is the absolute path to the assistant's source code, and &lt;br&gt;
&lt;em&gt;--interactive&lt;/em&gt; is the flag that we use to run the instance.&lt;/p&gt;

&lt;h2&gt;
  
  
  App development: LLM client
&lt;/h2&gt;

&lt;p&gt;The LLM client is the core of the application. It is designed as a class instance wich has some parameters as the model name, the url to the local Ollama server and other configurations.&lt;br&gt;
Inside the class there are some useful tools that the LLM can use in case that the user wants the assistant to interact with the system or get useful info on the web.&lt;br&gt;
The output of each message is yielded as chunks from the generator function &lt;em&gt;get_response_stream&lt;/em&gt; wich makes it look faster and nicer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo snapshot 👀
&lt;/h2&gt;

&lt;p&gt;Here's a demonstration of the looks of the current version:&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%2Fsomkaa6fdbuxyxvdm33z.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%2Fsomkaa6fdbuxyxvdm33z.png" alt="Snapshot of the CLI assistant" width="800" height="603"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Learnings through the development 🧠
&lt;/h2&gt;

&lt;p&gt;While working on this project I had to investigate a lot of ways to implement tool use on a Ollama model and I have got more comfortable with the langchain framework, which I think is a really nice takeaway.&lt;/p&gt;

&lt;h2&gt;
  
  
  Features to improve or add 🛠️
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Memory between sessions&lt;/li&gt;
&lt;li&gt;More interaction with the system&lt;/li&gt;
&lt;li&gt;Compatibility with other OS&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;&amp;gt; Did my project catch your attention?&lt;/strong&gt;&lt;br&gt;
If so, you can check it at my github: &lt;a href="https://github.com/nairec/compy" rel="noopener noreferrer"&gt;https://github.com/nairec/compy&lt;/a&gt;&lt;br&gt;
(Your star could be the first one that I get ever ⭐)&lt;br&gt;
&lt;/p&gt;


&lt;p&gt;That was all! Have you ever considered having your own intelligent assistant at a command's distance? I would love to see your thoughts, or your opinion on the project!&lt;/p&gt;

</description>
      <category>programming</category>
      <category>python</category>
      <category>ai</category>
      <category>sideprojects</category>
    </item>
    <item>
      <title>Memorizing repetitive actions</title>
      <dc:creator>Eric Garcia</dc:creator>
      <pubDate>Mon, 09 Jun 2025 15:39:46 +0000</pubDate>
      <link>https://dev.to/ceir/memorizing-mechanic-actions-189a</link>
      <guid>https://dev.to/ceir/memorizing-mechanic-actions-189a</guid>
      <description>&lt;p&gt;We all have a set of actions in programming that we have to do each period of time -like every time that we start a project- and is not overly complex or difficult, but is just a boring step in the middle of the job.&lt;/p&gt;

&lt;p&gt;Often I find myself just searching in internet (or better I say 'refreshing' 😅) the way to do those actions because my brain just doesn't have the will to sit for 5 minutes and memorize the way to do it.&lt;/p&gt;

&lt;p&gt;Maybe one of these days I should end with this bad habit, but I'm curious if I'm the only one that has it.&lt;/p&gt;

&lt;p&gt;Do you find yorself often looking for the way to do something that you have done other times and most probably will repeat in the future? If true, what are some examples?&lt;/p&gt;

</description>
      <category>programming</category>
      <category>discuss</category>
      <category>coding</category>
    </item>
    <item>
      <title>FitVision - devlog #3</title>
      <dc:creator>Eric Garcia</dc:creator>
      <pubDate>Mon, 05 May 2025 22:36:52 +0000</pubDate>
      <link>https://dev.to/ceir/fitvision-devlog-3-3d69</link>
      <guid>https://dev.to/ceir/fitvision-devlog-3-3d69</guid>
      <description>&lt;p&gt;Hey everyone! 👋&lt;br&gt;
In this blog I will showcase the latest updates of my project.&lt;br&gt;
Some of this important changes include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Improvement of the User Interface&lt;/li&gt;
&lt;li&gt;Light/dark mode &lt;/li&gt;
&lt;li&gt;Settings window&lt;/li&gt;
&lt;li&gt;Added a toolbar to the main page&lt;/li&gt;
&lt;li&gt;Colored feedback&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Also, I've made public the repo of the project in case anyone wants to take a detailed look at it: &lt;a href="https://github.com/nairec/FitVision" rel="noopener noreferrer"&gt;https://github.com/nairec/FitVision&lt;/a&gt; 👀&lt;/p&gt;

&lt;p&gt;Let's jump right onto it!&lt;br&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Changes in the User Interface 🖥️
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Button display
&lt;/h3&gt;

&lt;p&gt;The start and end detection buttons have been replaced by a toggle start/stop button and an end detection button.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class Home(QMainWindow):
    def __init__(self):
        ...
        self.pause_session_button.clicked.connect(self.update_pause_state)
        self.end_session_button.clicked.connect(self.end_detection)
        ...

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class Home(QMainWindow):
    def initUI(self):
        self.paused = True
        self.detection_started = False
        ...
        self.end_session_button = QPushButton()
        self.end_session_button.setStyleSheet("width: 10px; height: 10px; margin-right: 200px")
        self.pause_session_button = QPushButton()
        self.pause_session_button.setStyleSheet("background: none; border: none; padding: 0px; margin-left: 200px")
        self.pause_session_button.setIcon(QIcon("icons/mediaplay.png"))
        self.pause_session_button.setIconSize(QSize(60, 60))
        ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The display of the info labels has also been changed, but the code is a &lt;em&gt;5-lines-for-each-label&lt;/em&gt; type of code, so I decided not to include it here.&lt;/p&gt;

&lt;p&gt;The class Inherited by &lt;em&gt;Home()&lt;/em&gt; has been changed from &lt;em&gt;QWidget()&lt;/em&gt; to &lt;em&gt;QMainWindow()&lt;/em&gt; to enable the new toolbar.&lt;/p&gt;

&lt;p&gt;Some methods of the &lt;em&gt;Home()&lt;/em&gt; class have been updated to work with the change in the display of the main buttons, and two new methods have been created: &lt;em&gt;update_pause_state()&lt;/em&gt; and &lt;em&gt;update_pause_icon()&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class Home(QMainWidget):
    ...
    def start_detection(self):
        self.paused = False
        self.update_pause_icon()
        self.app_signals.start_detection.emit()
    ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class Home(QMainWidget):
    ...
    def end_detection(self):
        self.paused = True
        self.detection_started = False
        self.update_pause_icon()
        self.app_signals.end_detection.emit()
    ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class Home(QMainWidget):
    ...
    def update_pause_state(self):
        if self.detection_started: # If detection has already started, pause
            self.paused = not self.paused
            self.update_pause_icon()
            self.app_signals.pause_detection.emit(self.paused)
        else: # If detection hasn't started, start it
            self.detection_started = True
            self.start_detection()
    ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class Home(QMainWidget):
    ...
    def update_pause_icon(self):
        if self.paused:
            self.pause_session_button.setIcon(QIcon("icons/mediaplay.png"))
            self.pause_session_button.setIconSize(QSize(60, 60))
        else:
            self.pause_session_button.setIcon(QIcon("icons/mediapause.png"))
            self.pause_session_button.setIconSize(QSize(60, 60))
    ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Colored feedback
&lt;/h3&gt;

&lt;p&gt;Now, the user has the option to enable or disable (it is enabled by default) a coloration in the labels that show the information like rep count, timer, etc.&lt;br&gt;
This coloration is based on the average values that would be considered low/medium/high or excessive/addequate.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class Home(QMainWindow):
    def __init__(self):
        ...
        self.feedback_on = True
        self.bad_feedback_color = "#94322E"
        self.medium_feedback_color = "#F0E68C"
        self.good_feedback_color = "#80FF80"
        ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The logic behind the actual coloration of the labels is formed of some if statements that check the intervals of the values.&lt;/p&gt;

&lt;h3&gt;
  
  
  Color themes
&lt;/h3&gt;

&lt;p&gt;Obviously, an application is not properly designed if it doesn't have an option to change between light and dark modes, so I added that posibility in the settings window.&lt;/p&gt;

&lt;p&gt;The changes on the UI style based on the color theme selected are defined in a new method called &lt;em&gt;toggle_theme()&lt;/em&gt;.&lt;br&gt;
I will skip the explanation of this method because it's mainly just boring CSS, and nobody likes CSS around here 🥱.&lt;/p&gt;
&lt;h2&gt;
  
  
  Settings window ⚙️
&lt;/h2&gt;

&lt;p&gt;For communicating changes in the settings I created a new signal class&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class SettingsSignals(QObject):
    toggle_feedback = pyqtSignal(bool)
    toggle_theme = pyqtSignal(str)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first signal sends a bool value that represents wether the colored feedback is on or off, and the second one sends the selected color theme.&lt;/p&gt;

&lt;p&gt;The class for the settings window is formed by inheriting the QWidget class and takes as parameters the &lt;em&gt;SettingsSignals&lt;/em&gt; object and the current value of the color theme for defining the pre-selected theme on the combobox when opening the window.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class SettingsWindow(QWidget):
    def __init__(self, settings_signals:SettingsSignals, currentTheme:str):
    ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I won't show fully the code of this class as it is not that interesting, but the two methods that control the change in feedback and theme options are the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class SettingsWindow(QWidget):
    ...
    def apply_toggle_feedback(self, state:bool):
        if state == Qt.Checked:
            self.settings_signals.toggle_feedback.emit(True)
        else:
            self.settings_signals.toggle_feedback.emit(False)

    def apply_toggle_theme(self, theme:str):
        self.settings_signals.toggle_theme.emit(theme)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first one emits the value of the checkbox and the second one emits the label of the selected color theme.&lt;/p&gt;

&lt;h2&gt;
  
  
  Results! ✅
&lt;/h2&gt;

&lt;p&gt;Light mode, colored feedback disabled:&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%2Funjfmfae79314axozk8o.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%2Funjfmfae79314axozk8o.png" alt="Image visualization of the two windows on light mode, colored feedback disabled" width="800" height="357"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Light mode, colored feedback enabled:&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%2Fa2jdgy6mil100a3xtl8q.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%2Fa2jdgy6mil100a3xtl8q.png" alt="Image visualization of the two windows on light mode, colored feedback enabled" width="800" height="352"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Dark mode, colored feedback disabled:&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%2Ft0z8406ffam76admtydg.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%2Ft0z8406ffam76admtydg.png" alt="Image visualization of the two windows on darkmode, colored feedback disabled" width="800" height="358"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Dark mode, colored feedback enabled:&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%2Fxmxtujzf7kqaxo8jusqe.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%2Fxmxtujzf7kqaxo8jusqe.png" alt="Image visualization of the two windows on dark mode, colored feedback enabled" width="800" height="351"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Next steps 📑
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Saving&lt;/strong&gt; session records&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;New&lt;/strong&gt; color themes ?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Personalized&lt;/strong&gt; color feedback&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thank you for reaching the end of the blog and see you in the next!&lt;/p&gt;

</description>
      <category>python</category>
      <category>ai</category>
      <category>sideprojects</category>
      <category>programming</category>
    </item>
    <item>
      <title>Cool python syntax techniques</title>
      <dc:creator>Eric Garcia</dc:creator>
      <pubDate>Sat, 03 May 2025 15:55:54 +0000</pubDate>
      <link>https://dev.to/ceir/cool-python-syntax-techniques-469d</link>
      <guid>https://dev.to/ceir/cool-python-syntax-techniques-469d</guid>
      <description>&lt;p&gt;&lt;strong&gt;Did you ever do something that your friends saw and said "Wow, how did you know that?"&lt;/strong&gt;&lt;br&gt;
Well, this blog aims to help you discovering (or getting a better understanding) of techniques that can level up your code notably if you did not know them before.&lt;br&gt;
Some of this techniques are python-specific, while the others are present in other languages but are considered pythonic (nice practices in python code).&lt;br&gt;
After reading this blog you may be able to flex a bit on your fellow beginner developers 😉.&lt;/p&gt;
&lt;h2&gt;
  
  
  What will you learn❓
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;String formatting and interpolation ✔️&lt;/li&gt;
&lt;li&gt;Inline conditionals ✔️&lt;/li&gt;
&lt;li&gt;List comprehensions ✔️&lt;/li&gt;
&lt;li&gt;Lambda functions ✔️&lt;/li&gt;
&lt;li&gt;Type hinting ✔️

&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  ⛓️ String formatting and interpolation
&lt;/h2&gt;

&lt;p&gt;The first way that people often learn to join values and strings is the concatenating method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;words = "5"
concat_str = "This string has " + words + " words"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, this method is not very readable and doesn't work on many languages.&lt;/p&gt;

&lt;p&gt;Interpolated strings are a way of combinating variables and fixed strings in a more visual way, which can help code insight and debugging.&lt;br&gt;
Also, it offers more configuration capabilities in the way we want to represent the values.&lt;br&gt;
The way that string interpolation works is by inserting other values as strings or objects into the original string.&lt;/p&gt;

&lt;p&gt;The next three ways are much better and have some useful particularities:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;f-strings (my favourite)&lt;/li&gt;
&lt;li&gt;.format()&lt;/li&gt;
&lt;li&gt;% operator&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  f-strings
&lt;/h3&gt;

&lt;p&gt;On python version 3.6 and higher, a new way of interpolating strings was added, which is the f-string.&lt;br&gt;
The way it is used is by adding the prefix "f" or "F" before the string.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;words = 5
concat_str = f"This string has {words} words"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Notice how the interpolated values don't have to be of type str&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This method offers some configuration capabilities, I.E: defining the number of decimals to show on a float.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;num1 = 3.141592
num2 = 6.6743
concat_str = f"2 decimals: {num1:.2f} and 4 decimals: {num2:.4f}
print(concat_str)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;-&amp;gt; Output: "2 decimals: 3.14 and 4 decimals: 6.6743"&lt;/p&gt;

&lt;h3&gt;
  
  
  .format()
&lt;/h3&gt;

&lt;p&gt;The &lt;em&gt;.format()&lt;/em&gt; method is another way of interpolating strings, but it comes with a big particularity: it uses &lt;strong&gt;lazy evaluation&lt;/strong&gt;&lt;br&gt;
The concepts of eager and lazy evaluations both deserve a dedicated blog, so if you want to see a blog about them, let me know 😉.&lt;/p&gt;

&lt;p&gt;The way to use it is as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;day = "saturday"
month = "may"
concat_str = "current day and month: {day},{month}".format(day=day,month=month)
print(concat_str)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;-&amp;gt; Output: "current day and month: saturday, may"&lt;/p&gt;

&lt;h3&gt;
  
  
  Modulo (%) operator
&lt;/h3&gt;

&lt;p&gt;A more "primitive" interpolation method that also appears when formatting strings in C. &lt;br&gt;
It is used by placing the % icon in the place we want the value printed, next to its type and other flags as the number of decimals to show.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;day = "saturday"
hour = 12.06
concat_str = "day: %s, hour: %.2f" % (day, hour)
print(concat_str)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;-&amp;gt; Output: "day: saturday, hour: 12.06"&lt;/p&gt;

&lt;h2&gt;
  
  
  ✅-❎ Inline conditionals
&lt;/h2&gt;

&lt;p&gt;Inline conditionals are a cool way of condensing expressions in less lines and eliminate a bit of the verbosity of traditional if statements.&lt;/p&gt;

&lt;p&gt;This is the general syntax for inline conditionals:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;expression_if_true if condition else expression_if_false
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is an example of inline conditionals in python:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;value = True
print("value is True") if value else print("value is False")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;-&amp;gt; Output: "value is True"&lt;/p&gt;

&lt;p&gt;Another example manipulating the value of a variable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;value = False
num = 1 if value else 0
print(num)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;-&amp;gt; Output: 0&lt;/p&gt;

&lt;h2&gt;
  
  
  [🧾] List comprehensions
&lt;/h2&gt;

&lt;p&gt;One of my favourite python techniques, widely used in data science where big sets of information are used and manipulated.&lt;br&gt;
List comprehensions are a way of writing a list (or other data collection types) out of another iterable variable in a single line.&lt;/p&gt;

&lt;p&gt;Imagine we have a list of fruits named fruits&lt;br&gt;
&lt;code&gt;fruits = ["apple", "banana", "pear", "peach"]&lt;/code&gt;&lt;br&gt;
and we want to create another list consisting of fruits that have a p in their name, the list comprehension used would be like this one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fruits_with_p = [fruit for fruit in fruits if "p" in fruit]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The meaning of the line of code from above is "Each element will be a &lt;em&gt;fruit&lt;/em&gt; element of the list &lt;em&gt;fruits&lt;/em&gt; if there's a "p" in said element &lt;em&gt;fruit&lt;/em&gt;"&lt;/p&gt;

&lt;p&gt;List comprehensions often demand some knowledge of how to create inline conditionals, as you may have seen.&lt;/p&gt;

&lt;p&gt;Also, an important thing to note is that list comprehensions are generally faster than their equivalent in a for loop expression. This is mainly because list comprehensions move better the computation to C rather that using the python computation. &lt;br&gt;
(kudos to u/toastedstapler - &lt;a href="https://www.reddit.com/r/learnpython/comments/i28kcn/why_are_list_comprehensions_so_much_faster_than/" rel="noopener noreferrer"&gt;https://www.reddit.com/r/learnpython/comments/i28kcn/why_are_list_comprehensions_so_much_faster_than/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;❗HOWEVER&lt;/strong&gt; list comprehensions are known for difficulting the readability of the code when used without conscience, so don't abuse of their power.&lt;/p&gt;
&lt;h2&gt;
  
  
  &lt;strong&gt;λ&lt;/strong&gt; Lambda functions
&lt;/h2&gt;

&lt;p&gt;Lambda functions (also called anonymous functions) are basically definitions of functions that are short, concise and usually not worth to declare as a traditional function.&lt;br&gt;
They could be imagined as some kind of inline function declarations.&lt;/p&gt;

&lt;p&gt;Their syntax is&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lambda arguments : expression
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even though lambda functions can be declared inline and called when needed as in the following example, this is not their main use.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;day_and_month = lambda day, month : print(f'day: {day}, month: {month}')
day_and_month("saturday", "may")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;-&amp;gt; Output: "day: saturday, month: may"&lt;/p&gt;

&lt;p&gt;Usually lambda functions are used as parameters to generator functions like &lt;em&gt;map()&lt;/em&gt; and are often used in Data Science.&lt;br&gt;
The following is an example of using a lambda function as a parameter&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;celsius_temps = [0, 20, 25, 30, 37]
fahrenheit_temps = list(map(lambda c: (c * 9/5) + 32, celsius_temps))
print(fahrenheit_temps)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;-&amp;gt; Output: [32.0, 68.0, 77.0, 86.0, 98.6]&lt;/p&gt;

&lt;p&gt;The above code takes an anonymous function that converts celsius degrees to fahrenheit and applies it to each element in the &lt;em&gt;celsius_temp&lt;/em&gt; list.&lt;br&gt;
Lambda functions can usually be replaced by list comprehensions, with the following code in this case:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;celsius_temps = [0, 20, 25, 30, 37]
fahrenheit_temps = [c * 9/5 + 32 for c in celsius_temps]
print(fahrenheit_temps)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;-&amp;gt; Output: [32.0, 68.0, 77.0, 86.0, 98.6]&lt;/p&gt;

&lt;p&gt;So the usage of each implementation depends on the programmer and the specific needs in the moment.&lt;/p&gt;

&lt;h2&gt;
  
  
  💡 Type hintings
&lt;/h2&gt;

&lt;p&gt;Type hintings are a useful way to make your code much more readable and easy to debug.&lt;br&gt;
Python is a dynamically typed language, which means that the variables don't have a specific data type in the moment of their creation.&lt;/p&gt;

&lt;p&gt;Type hintings can help the IDE or linting software to interpret the code and give better insights in case of errors or warnings.&lt;/p&gt;

&lt;p&gt;Variables can be type-hinted as they are created and also in the expected parameters of a function.&lt;/p&gt;

&lt;p&gt;Example of type hinting at creation of variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;a:int = 5
b:float = 3.5
s:str = "string"
l:list = [a, b, s]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example of type hinting at definition of a function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Function that searches a word in a list and returns its index
def search_in_list(original_list:list, word:str) -&amp;gt; int:
    for i,elem in enumerate(original_list):
        if elem == word:
            return i 
    return -1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using this technique, the readability and facility to debug your code will updgrade a lot.&lt;/p&gt;




&lt;p&gt;Hope you found this knowledge useful! If you have anything to contribute, feel free to comment it!&lt;br&gt;
Also, if you are interested in ML, computer vision or fitness in general, I suggest to check my devlog series about my latest side-project, FitVision 👀 &lt;a href="https://dev.to/ceir/fitvision-devlog-1-3a0i"&gt;https://dev.to/ceir/fitvision-devlog-1-3a0i&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>beginners</category>
      <category>programming</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>FitVision - devlog #2</title>
      <dc:creator>Eric Garcia</dc:creator>
      <pubDate>Thu, 01 May 2025 16:34:43 +0000</pubDate>
      <link>https://dev.to/ceir/fitvision-devlog-2-119d</link>
      <guid>https://dev.to/ceir/fitvision-devlog-2-119d</guid>
      <description>&lt;h2&gt;
  
  
  Communication with the video detection ⚙️
&lt;/h2&gt;

&lt;p&gt;For communicating the change in variables modified in the detection algorithm, a &lt;em&gt;AppSignals&lt;/em&gt; class is created, taking advantage of the pyqtSignal facility.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class AppSignals(QObject):
    stop_detection = pyqtSignal()
    start_detection = pyqtSignal()
    update_reps = pyqtSignal(int)
    update_avg_time = pyqtSignal(float)
    update_training_time = pyqtSignal(int)
    update_set_time = pyqtSignal(int)
    update_rest_time = pyqtSignal(int)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This class is used in the initializer method of the &lt;em&gt;Home&lt;/em&gt; class&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class Home(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()
        self.settings()

        self.app_signals = AppSignals()
        self.detection_worker = DetectionWorker(self.app_signals)

        self.start_session_button.clicked.connect(self.app_signals.start_detection.emit)
        self.end_session_button.clicked.connect(self.app_signals.stop_detection.emit)

        self.app_signals.update_reps.connect(self.update_reps_label)
        self.app_signals.update_avg_time.connect(self.update_avg_time_label)
        self.app_signals.update_training_time.connect(self.update_training_time_label)
        self.app_signals.update_set_time.connect(self.update_set_time_label)
        self.app_signals.update_rest_time.connect(self.update_rest_time_label)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This method initializes the UI and the settings of the app.&lt;br&gt;
Then the objects of &lt;em&gt;app_signals&lt;/em&gt; and &lt;em&gt;detection_worker&lt;/em&gt; are created, the first one for managing the change in variables in real time and the second one for calling the detection methods described in the last devlog.&lt;/p&gt;

&lt;p&gt;The rest of the lines connect the clicks in buttons to their respective actions and the change in variables to the update of their respective labels.&lt;/p&gt;
&lt;h2&gt;
  
  
  Updating methods and settings
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    def settings(self):
        self.setWindowTitle("FitVision")
        self.setGeometry(250, 250, 600, 400)

    def update_reps_label(self, reps):
        self.last_session_total_reps.setText(f"Total Reps: {reps}")

    def update_avg_time_label(self, avg_time):
        self.last_session_avg_time_per_rep.setText(f"Avg time per rep: {avg_time:.2f}s")

    def update_training_time_label(self, training_time):
        self.last_session_training_time.setText(f"Total training time: {training_time}s")

    def update_set_time_label(self, set_time):
        self.last_set_time.setText(f"Set time: {set_time}s")

    def update_rest_time_label(self, rest_time):
        self.last_rest_time.setText(f"Rest time: {rest_time}s")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The settings method, which sets the initial parameters of the app is called at the initialization of the &lt;em&gt;Home&lt;/em&gt; object, and the rest of the methods are called whenever a change in the value of any variable is communicated through the &lt;em&gt;app_signals&lt;/em&gt; object.&lt;/p&gt;
&lt;h2&gt;
  
  
  First version of the User Interface 🖥️
&lt;/h2&gt;
&lt;h3&gt;
  
  
  The code
&lt;/h3&gt;

&lt;p&gt;The first version of the app's UI is powered by PyQT5&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    def initUI(self):
        self.start_session_button = QPushButton("Start Session")
        self.end_session_button = QPushButton("End Session")
        self.last_session_total_reps = QLabel("Total Reps: 0")
        self.last_session_avg_time_per_rep = QLabel("Avg Time per Rep: 0s")
        self.last_session_training_time = QLabel("Training Time: 0s")
        self.last_set_time = QLabel("Set Time: 0s")
        self.last_rest_time = QLabel("Rest Time: 0s")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first lines of the UI initializer method are used to define the interface elements.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        self.master = QVBoxLayout()

        row1 = QHBoxLayout()
        row2 = QHBoxLayout()
        row3 = QHBoxLayout()

        row1.addWidget(self.start_session_button, alignment=Qt.AlignCenter)
        row1.addWidget(self.end_session_button, alignment=Qt.AlignCenter)
        row2.addWidget(self.last_session_training_time, alignment=Qt.AlignCenter)
        row2.addWidget(self.last_session_total_reps, alignment=Qt.AlignCenter)
        row2.addWidget(self.last_session_avg_time_per_rep, alignment=Qt.AlignCenter)
        row3.addWidget(self.last_set_time, alignment=Qt.AlignCenter)
        row3.addWidget(self.last_rest_time, alignment=Qt.AlignCenter)

        self.master.addLayout(row1, 50)
        self.master.addLayout(row2, 25)
        self.master.addLayout(row3, 25)
        self.setLayout(self.master)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, the widgets are created and placed in the master layout.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        self.setStyleSheet("""
            QWidget {
                background-color: #565656;
            }

            QPushButton {
                background-color: #1c549f;
                padding: 20px;
                border-width: 5px;
                border-style: solid;
                border-radius: 5px;
                border-color: #1a4b8e;
                color: white;
                margin-top: 40px;
            }

            QPushButton:hover {
                background-color: #1a4b8e;
            }
        """)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And finally, a simple CSS style is applied.&lt;/p&gt;

&lt;h3&gt;
  
  
  The results 👀
&lt;/h3&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%2Fp15dpbp4gtle32ll0pbj.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%2Fp15dpbp4gtle32ll0pbj.png" alt="Image description" width="598" height="452"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The result is a simple and minimalistic UI which tells the user some important metrics of his/her workout.&lt;br&gt;
&lt;strong&gt;Important:&lt;/strong&gt; The size of the window is currently set to be 600x400 as seen in the &lt;em&gt;settings()&lt;/em&gt; method, but this and many other things may be changed in the future.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next steps 📑
&lt;/h2&gt;

&lt;p&gt;The next planned steps are the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add a &lt;strong&gt;Pause session&lt;/strong&gt; feature&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improve&lt;/strong&gt; the UI desing&lt;/li&gt;
&lt;li&gt;Add a &lt;strong&gt;visual feedback&lt;/strong&gt; to some counts like the &lt;em&gt;avg_time&lt;/em&gt; per rep, set/rest times and repetition count.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Thanks for reading! 🤗
&lt;/h3&gt;

&lt;h2&gt;
  
  
  Credits
&lt;/h2&gt;

&lt;p&gt;I watched the following video to get to know PyQT5:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=RQOXVpaFYMU&amp;amp;t=987s" rel="noopener noreferrer"&gt;Build a Python Desktop Application in Minutes | Code with Josh&lt;/a&gt;&lt;/p&gt;

</description>
      <category>programming</category>
      <category>python</category>
      <category>ai</category>
      <category>development</category>
    </item>
    <item>
      <title>FitVision - devlog #1</title>
      <dc:creator>Eric Garcia</dc:creator>
      <pubDate>Tue, 29 Apr 2025 18:07:57 +0000</pubDate>
      <link>https://dev.to/ceir/fitvision-devlog-1-3a0i</link>
      <guid>https://dev.to/ceir/fitvision-devlog-1-3a0i</guid>
      <description>&lt;p&gt;Hi there! This will be the first devlog that I make public (And hope is the first one I finish ;)&lt;br&gt;
Also, this post is not a full explanation of the current version because I will divide the explanation in two parts, one for each file.&lt;br&gt;
In this series of blogs I'm going to share my journey through the development of an application that I wanted to build since when I started to workout at home.&lt;/p&gt;
&lt;h2&gt;
  
  
  What is this?
&lt;/h2&gt;

&lt;p&gt;-&amp;gt; The mission: building a desktop app that helps the user to track his/her training evolution in a automated way.&lt;br&gt;
-&amp;gt; The big brain idea: implementing a computer-vision system that detects user's repetitions, resting time, set time and total workout time (along with other possible ideas that may come to me while working on it).&lt;br&gt;
-&amp;gt; The tech stack: Initially only Python.&lt;/p&gt;

&lt;p&gt;Now that I have explained a bit the idea, it's time to start the journey.&lt;/p&gt;
&lt;h2&gt;
  
  
  First steps: vision model
&lt;/h2&gt;

&lt;p&gt;The libraries that the vision code uses currently are the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import cv2 
import mediapipe as mp
import numpy as np
import time
from PyQt5.QtCore import QThread, QTimer

mp_drawing = mp.solutions.drawing_utils
mp_pose = mp.solutions.pose
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;mp_drawing&lt;/em&gt; and &lt;em&gt;mp_pose&lt;/em&gt; are utilities for drawing the detected user's pose and detecting it, respectively (not libraries but I wanted to include them in this section either way).&lt;br&gt;
The imports are dedicated to the computer vision models, arithmetic operations and time calculations.&lt;/p&gt;

&lt;p&gt;The full implementation of the vision model is wrapped around a class called &lt;em&gt;DetectionWorker&lt;/em&gt; which is imported in the main file, which will be explained in the next blog.&lt;br&gt;
Originally, I designed the model to be called by a simple function called &lt;em&gt;capture_and_detect_video()&lt;/em&gt; which will be reviewed later, but for the detection to be started and stopped correctly using the interface buttons, I had to add the wrapper class.&lt;/p&gt;

&lt;p&gt;The next is the initializer method of the &lt;em&gt;DetectionWorker&lt;/em&gt; class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    def __init__(self, app_signals):
        super().__init__()
        self.app_signals = app_signals
        self.running = False
        self.last_rep_time = 0 # keeps track of the last rep time for detecting rest or mid-set time
        self.status = "resting" # resting by default until the first rep

        # Total training session timer
        self.total_training_time = 0
        self.training_timer = QTimer()
        self.training_timer.timeout.connect(self.update_total_training_time)

        # Set and rest times
        self.set_training_time = 0
        self.rest_training_time = 0

        self.app_signals.stop_detection.connect(self.stop_detection)
        self.app_signals.start_detection.connect(self.start_detection)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The initializer method is pretty self-explanatory, the object is initialized with a &lt;em&gt;app_signals&lt;/em&gt; object which communicate to the app the state of some variables, and the timer is initialized with a &lt;em&gt;QTime()&lt;/em&gt; object.&lt;/p&gt;

&lt;p&gt;Now I will showcase the start and stop detection methods and other utilities of the class.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    def start_detection(self):
        self.total_training_time = 0
        self.set_training_time = 0
        self.rest_training_time = 0
        self.training_timer.start(1000)
        self.running = True
        self.capture_and_detect_video()

    def stop_detection(self):
        self.training_timer.stop()
        self.running = False
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The start and the stop of the video detection actualize the value of the time counts, starts/stops the timers and updates the &lt;em&gt;self.running&lt;/em&gt; property, which is the responsible for maintaining the flow of the detections.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    def update_total_training_time(self):
        self.total_training_time += 1
        self.app_signals.update_training_time.emit(self.total_training_time)

        # Detect if user is resting or mid-set
        if np.abs(self.last_rep_time - time.time()) &amp;gt; 5:
            self.status = "resting"
            self.set_training_time = 0
            self.rest_training_time += 1
            self.app_signals.update_rest_time.emit(self.rest_training_time)
        else:
            self.status = "mid-set"
            self.rest_training_time = 0
            self.set_training_time += 1
            self.app_signals.update_set_time.emit(self.set_training_time)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The function dedicated to update the timers first adds 1 to the count of total seconds of the workout and detects wether the user is resting or in the middle of a set. Depending on the result, the respective timers are updated.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note that the time period used for determining the status of the workout is not definitive, it is currently low for debug purposes.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    def calculate_angle(self, a: list, b: list, c: list) -&amp;gt; float:
        a = np.array(a)
        b = np.array(b)
        c = np.array(c)
        radians = np.arctan2(c[1] - b[1], c[0] - b[0]) - np.arctan2(a[1] - b[1], a[0] - b[0])
        angle = np.abs(radians * 180 / np.pi)

        if angle &amp;gt; 180.0:
            angle = 360 - angle

        return angle
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;em&gt;calculate_angle&lt;/em&gt; function simply takes three lists of coordinates (x,y) as arguments for determining the angle formed by such points and returns it as a float.&lt;/p&gt;

&lt;p&gt;From now on, all of the next pieces of code will be the ones that form the _capture_and_detect_video() method, which is the method in charge of the pose detection in real time.&lt;br&gt;
&lt;code&gt;cap = cv2.VideoCapture(0)  # Camera code may be changed depending on the device&lt;/code&gt;&lt;br&gt;
This line initializes the video capturing element.&lt;/p&gt;

&lt;p&gt;The remainding code of the method is inside a with statement:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        with mp_pose.Pose(min_detection_confidence=0.5, min_tracking_confidence=0.5) as pose:
            counter = 0
            stage = "down"
            times_hist = []
            rep_start_time = None
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using the pose detection utility of mediapipe, the default initial values of counter and stage are set.&lt;br&gt;
A list named &lt;em&gt;times_hist&lt;/em&gt; is created with the purpose of storing the time that the user took for completing each rep (for calculating the average time per rep metric)&lt;br&gt;
&lt;em&gt;rep_start_time&lt;/em&gt; is also set to its default value of None.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;            while cap.isOpened() and self.running:
                ret, frame = cap.read()

                # Recolor image to RGB
                image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
                image.flags.writeable = False

                # Make detection
                results = pose.process(image)

                # Recolor back
                image.flags.writeable = True
                image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inside the loop of the video detection, the frame is first processed for detecting the user's pose and reprocessed back.&lt;br&gt;
This loop starts and stop running when the &lt;em&gt;self.running&lt;/em&gt; property is changed as was shown earlier.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                # Extract landmarks
                try:
                    landmarks = results.pose_landmarks.landmark

                    # Get limbs
                    l_shoulder = [landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value].x, landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value].y]
                    l_elbow = [landmarks[mp_pose.PoseLandmark.LEFT_ELBOW.value].x, landmarks[mp_pose.PoseLandmark.LEFT_ELBOW.value].y]
                    l_wrist = [landmarks[mp_pose.PoseLandmark.LEFT_WRIST.value].x, landmarks[mp_pose.PoseLandmark.LEFT_WRIST.value].y]

                    # Calculate angles
                    angle = self.calculate_angle(l_shoulder, l_elbow, l_wrist)

                    # Visualize angles
                    cv2.putText(image, str(angle), tuple(np.multiply(l_elbow, [640, 480]).astype(int)), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 2, cv2.LINE_AA)

                    # Curl count
                    if angle &amp;gt; 160:
                        stage = "down"
                        rep_start_time = time.time() if rep_start_time is None else rep_start_time
                    elif angle &amp;lt; 30 and stage == "down":
                        stage = "up"
                        counter += 1
                        end_time = time.time()
                        self.last_rep_time = end_time
                        rep_time = end_time - rep_start_time
                        rep_start_time = None
                        times_hist.append(rep_time)



                    average_time = sum(times_hist) / len(times_hist)
                    self.app_signals.update_reps.emit(counter)
                    self.app_signals.update_avg_time.emit(average_time)


                except:
                    pass
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this fragment of code, the system tries to extract information about the position of each key node of the user's body (currently just the ones needed for counting curl reps) and calculate the angle formed by them.&lt;br&gt;
Then the angle is visualized for debugging purposes and the curl count is executed.&lt;/p&gt;

&lt;p&gt;If the formed angle is more than 160º, it means the user has the arm extended (resting position), and the system updates the stage and the start time of the repetition, which is only set one time immediately when the arm reaches the resting position.&lt;br&gt;
If the formed angle is less than 30º and the last tracked position of the curl was "down", the system updates the stage counts one rep, calculates the time took to execute the rep and it is stored to the &lt;em&gt;times_hist&lt;/em&gt; list.&lt;/p&gt;

&lt;p&gt;Then the average time per repetition is calculated and the information is sent through the &lt;em&gt;app_signals&lt;/em&gt; object that was shown previously.&lt;/p&gt;

&lt;p&gt;If any errors occur while executing the above instructions (which may be caused by an erroneous detection of the user's pose), the system ignores that frame for the sake of simplicity.&lt;/p&gt;

&lt;p&gt;Now, the next fragment is the last one to review!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                #Render detection
                mp_drawing.draw_landmarks(image, results.pose_landmarks, mp_pose.POSE_CONNECTIONS,
                                        mp_drawing.DrawingSpec(color=(255,255,255), thickness=2, circle_radius=2),
                                        mp_drawing.DrawingSpec(color=(255,255,255), thickness=2, circle_radius=2))
                cv2.imshow('frame', image)


                if cv2.waitKey(10) &amp;amp; 0xFF == ord('q') or not self.running:
                    self.stop_detection()

            cap.release()
            cv2.destroyAllWindows()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The last lines of code of the &lt;em&gt;capture_and_detect_video()&lt;/em&gt; method render the detected nodes and the connections between them in the returned frames.&lt;br&gt;
They also handle the stop of the detection by the user and, ultimately, releases the video capture element and destroys all instances of the video detection when the loop is stopped.&lt;/p&gt;

&lt;h2&gt;
  
  
  In a few lines
&lt;/h2&gt;

&lt;p&gt;Summarizing all of this, the algorithm made is able to determine the current state of the user's workout, detecting if he/she is in a rest or mid-set time interval and if he/she is doing a rep or just ended one.&lt;br&gt;
This functionalities are connected with a UI that will be explained in the next devlog, and implemented in a way that will help the user evaluating his/her workouts automatically.&lt;/p&gt;

&lt;p&gt;If you have followed through until here, thank you very much for taking the time to read it! Any question, kind suggestion or idea is extremely appreciated. 😉&lt;/p&gt;

&lt;p&gt;(and yes, the cover image is ai generated)&lt;/p&gt;

&lt;h2&gt;
  
  
  Credits
&lt;/h2&gt;

&lt;p&gt;For starting this project, I watched the following videos:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.toAI%20Pose%20Estimation%20with%20Python%20and%20MediaPipe%20-%20Nicholas%20Renotte"&gt;https://youtu.be/06TE_U21FK4?si=oDEl9eeGzVKnkv-g&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>machinelearning</category>
      <category>development</category>
    </item>
  </channel>
</rss>
