<?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: Billy</title>
    <description>The latest articles on DEV Community by Billy (@tomlin7).</description>
    <link>https://dev.to/tomlin7</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%2F755738%2F26e69cdc-c142-49e7-ac58-08c7b0a329d6.gif</url>
      <title>DEV Community: Billy</title>
      <link>https://dev.to/tomlin7</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tomlin7"/>
    <language>en</language>
    <item>
      <title>I Built an Excalidraw Clone in Under 3 Hours — Here's How and Why</title>
      <dc:creator>Billy</dc:creator>
      <pubDate>Fri, 06 Jun 2025 12:18:26 +0000</pubDate>
      <link>https://dev.to/tomlin7/i-built-an-excalidraw-clone-in-under-3-hours-heres-how-and-why-47kd</link>
      <guid>https://dev.to/tomlin7/i-built-an-excalidraw-clone-in-under-3-hours-heres-how-and-why-47kd</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Sometimes the best projects are born from creative chaos and time limits.&lt;/p&gt;
&lt;/blockquote&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%2F61lyvu09l611dahv2wle.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%2F61lyvu09l611dahv2wle.png" alt="art" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I recently challenged myself to build an Excalidraw-style drawing tool — from scratch — in under 3 hours.&lt;br&gt;
The result? Freehand — a fast, lightweight, and expressive tool for creating hand-drawn diagrams in the browser.&lt;/p&gt;

&lt;p&gt;In this post, I’ll walk through how I built it, what tech powers it, and what I learned along the way.&lt;/p&gt;
&lt;h2&gt;
  
  
  What is Excalidraw?
&lt;/h2&gt;

&lt;p&gt;Excalidraw is a popular open-source whiteboard tool that lets you draw diagrams with a hand-drawn look. It’s used by developers, designers, and educators to visualize ideas quickly, collaboratively, and with charm.&lt;/p&gt;

&lt;p&gt;It features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🖊️ Hand-drawn-style elements&lt;/li&gt;
&lt;li&gt;🧠 Collaborative editing&lt;/li&gt;
&lt;li&gt;🧰 Shapes, arrows, freehand, and text&lt;/li&gt;
&lt;li&gt;📦 Export to PNG/SVG&lt;/li&gt;
&lt;li&gt;⚡ Offline-first, local storage support&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Excalidraw is simple, yet powerful — and the sketchy aesthetic makes it more human and fun.&lt;/p&gt;
&lt;h2&gt;
  
  
  What is Freehand?
&lt;/h2&gt;

&lt;p&gt;Freehand is my attempt to reimagine the core ideas of Excalidraw — built in under 3 hours.&lt;/p&gt;

&lt;p&gt;It’s not meant to replace it, but rather to explore how much you can build with the right tools, the right mindset, and a self-imposed time limit.&lt;/p&gt;

&lt;p&gt;It lets you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Draw freehand lines ✏️ &lt;/li&gt;
&lt;li&gt;Create shapes (rectangle, circle, etc.)&lt;/li&gt;
&lt;li&gt;Select, move, and delete elements&lt;/li&gt;
&lt;li&gt;Undo / Redo with keyboard shortcuts 🔄&lt;/li&gt;
&lt;li&gt;Save/Open drawings &lt;/li&gt;
&lt;li&gt;Export drawings as images&lt;/li&gt;
&lt;li&gt;Library that lets you save drawn items, and add them as you need&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Libraries Used
&lt;/h2&gt;

&lt;p&gt;Libraries that make the magic happen:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Zustand&lt;/strong&gt;&lt;br&gt;
Handles all the app's state—elements, selections, undo/redo, and more—without breaking a sweat.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Rough.js&lt;/strong&gt;&lt;br&gt;
Draws shapes with that hand-drawn, wobbly look that makes everything feel playful and alive.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;perfect-freehand&lt;/strong&gt;&lt;br&gt;
Turns your mouse or stylus scribbles into beautiful, natural-looking freehand lines.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Architecture in a Nutshell
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User Action --&amp;gt; Zustand State Update --&amp;gt; Canvas Re-render --&amp;gt; Rough.js + Path Render
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The app maintains a elements[] array representing lines, rectangles, or freehand strokes. Drawing, editing, and deleting elements trigger a redraw of the entire canvas with new strokes.&lt;/p&gt;
&lt;h2&gt;
  
  
  Timeline Breakdown
&lt;/h2&gt;

&lt;p&gt;5 mins  -   Setup: Vite + Canvas + Rough.js&lt;br&gt;
30 mins -   Shapes drawing engine&lt;br&gt;
30 mins -   Freehand pen stroke logic&lt;br&gt;
30 mins -   Element interaction (select/move/delete)&lt;br&gt;
30 mins -   Zustand refactor + Undo/Redo&lt;br&gt;
30 mins -   Polish UI &amp;amp; fix edge cases&lt;/p&gt;

&lt;p&gt;Source code: &lt;a href="https://github.com/tomlin7/Freehand" rel="noopener noreferrer"&gt;https://github.com/tomlin7/Freehand&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  So HOW TO... BUILD IT?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The heart of a this drawing app is the HTML canvas. We’ll use the HTML &lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt; element and handle mouse/touch events to draw.

&lt;ul&gt;
&lt;li&gt;Supports multiple shapes: rectangles, ellipses, diamonds, arrows, lines, pencil (freehand), text, and images.&lt;/li&gt;
&lt;li&gt;Integrates with roughjs for hand-drawn style rendering.&lt;/li&gt;
&lt;li&gt;Handles zoom, pan, and multi-layer drawing.&lt;/li&gt;
&lt;li&gt;Implements a local "library" for saving and reusing element groups.
Exposes methods for exporting the canvas as an image and saving/loading as JSON.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is a simple minimal example:&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;React&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useRef&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;useState&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;Canvas&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&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;=&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;canvasRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;HTMLCanvasElement&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;null&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;drawing&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setDrawing&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="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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;canvasRef&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerWidth&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;canvas&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="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHeight&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;startDrawing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MouseEvent&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;setDrawing&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;ctx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;canvasRef&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="nf"&gt;getContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2d&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;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;beginPath&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;moveTo&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="nx"&gt;nativeEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;offsetX&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="nx"&gt;nativeEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;offsetY&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;draw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MouseEvent&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;drawing&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;canvasRef&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="nf"&gt;getContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2d&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;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lineTo&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="nx"&gt;nativeEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;offsetX&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="nx"&gt;nativeEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;offsetY&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stroke&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;stopDrawing&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="nf"&gt;setDrawing&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="k"&gt;return &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;canvas&lt;/span&gt;
      &lt;span class="nx"&gt;ref&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;canvasRef&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;border w-full h-full&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="nx"&gt;onMouseDown&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;startDrawing&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nx"&gt;onMouseMove&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nx"&gt;onMouseUp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;stopDrawing&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nx"&gt;onMouseLeave&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;stopDrawing&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="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;Canvas&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Full version here: &lt;a href="https://github.com/tomlin7/Freehand/blob/main/src/components/Canvas.tsx" rel="noopener noreferrer"&gt;src/components/Canvas.tsx&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;All drawing state is managed with Zustand (&lt;a href="https://github.com/tomlin7/Freehand/blob/main/src/store/index.ts" rel="noopener noreferrer"&gt;src/store/index.ts&lt;/a&gt;). The store tracks:

&lt;ul&gt;
&lt;li&gt;elements: All drawn shapes and objects&lt;/li&gt;
&lt;li&gt;selectedElements: Currently selected items&lt;/li&gt;
&lt;li&gt;mode: Current tool (e.g., selection, rectangle, pencil)&lt;/li&gt;
&lt;li&gt;strokeColor, fillColor, strokeWidth, fillStyle, opacity: Style settings&lt;/li&gt;
&lt;li&gt;canvasBg: Canvas background color&lt;/li&gt;
&lt;li&gt;history: Undo/redo stack&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;The store exposes actions for adding, updating, selecting, deleting, duplicating, and reordering elements, as well as for undo/redo and style changes.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A toolbar for tool selection + a Library &lt;a href="https://github.com/tomlin7/Freehand/blob/main/src/components/Toolbar.tsx" rel="noopener noreferrer"&gt;src/components/Toolbar.tsx&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;Tool Buttons: Switch between selection, shapes, pencil, text, and image tools.&lt;/li&gt;
&lt;li&gt;Tool Lock: Option to stay in the current tool after use.&lt;/li&gt;
&lt;li&gt;Library: Save the element to library, to reuse later.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;A sidebar that contains styling and layer controls &lt;a href="https://github.com/tomlin7/Freehand/blob/main/src/components/Sidebar.tsx" rel="noopener noreferrer"&gt;src/components/Sidebar.tsx&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;Stroke and Fill Color Pickers: Choose colors for outlines and fills.&lt;/li&gt;
&lt;li&gt;Fill Style: Solid, hachure (sketchy), or none.&lt;/li&gt;
&lt;li&gt;Stroke Width: Select line thickness.&lt;/li&gt;
&lt;li&gt;Opacity Slider: Adjust transparency.&lt;/li&gt;
&lt;li&gt;Layer Controls: Move elements up/down in the stack.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Add a cool command palette for quick commands like open, save, export, reset, and theme switching. It’s accessible via keyboard shortcuts.&lt;/li&gt;

&lt;li&gt;Add keyboard shortcuts for picking tools easily, and managing elements - &lt;a href="https://github.com/tomlin7/Freehand/blob/main/src/utils/keyboard.ts" rel="noopener noreferrer"&gt;src/utils/keyboard.ts&lt;/a&gt;
&lt;/li&gt;

&lt;li&gt;Enhance the canvas

&lt;ul&gt;
&lt;li&gt;Undo/Redo: Managed via the store’s history stack.&lt;/li&gt;
&lt;li&gt;Layering: Bring-to-front/send-to-back actions.&lt;/li&gt;
&lt;li&gt;Export/Import: Download as PNG or JSON, load from file.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  That's about it
&lt;/h2&gt;

&lt;p&gt;There are still many features missing out in Freehand, and Excalidraw is still the GOAT. This challenge was only to see how hard it can be to build something like that.&lt;/p&gt;

&lt;p&gt;There are multiple ways we can go about enhancing this tiny thing and creating something really cool though. Brainstorm and find out, and let me know!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>javascript</category>
      <category>ai</category>
    </item>
    <item>
      <title>Future Writing Challenge</title>
      <dc:creator>Billy</dc:creator>
      <pubDate>Wed, 19 Mar 2025 19:09:17 +0000</pubDate>
      <link>https://dev.to/tomlin7/future-writing-challenge-l3j</link>
      <guid>https://dev.to/tomlin7/future-writing-challenge-l3j</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://future.forem.com/challenges/writing-2025-02-26"&gt;Future Writing Challenge&lt;/a&gt;: How Technology Is Changing Things.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dear Friends,&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I hope this letter finds you well! I wanted to take a moment to talk about something that's changing the world around us every day—technology. You may not notice it as much since it's sneaky, creeping into our lives bit by bit, but I thought I’d share some thoughts on how it’s reshaping everything from the way we live to the way we connect. And don’t worry—I’ll keep it simple and fun!&lt;/p&gt;

&lt;p&gt;Remember when we had just a regular TV? Now, we’ve got “smart” TVs, “smart” fridges, and even “smart” lights! Everything’s getting smarter. Imagine your refrigerator telling you when the milk is about to expire or your TV suggesting what you might want to watch next. Technology is making everyday things better at helping us out.&lt;/p&gt;

&lt;p&gt;But here's the cool part—things will keep getting smarter. One day, your house might adjust its temperature on its own, knowing exactly how warm or cool you like it at different times of the day. We’re already halfway there with devices like Alexa and Siri! These little changes will soon be so normal, we won’t even think about them.&lt;/p&gt;

&lt;p&gt;You’ve probably heard of robots, right? While we might not all have robots running around like in the movies, they’re already helping in a lot of ways. There are robots in factories making cars, robots in hospitals assisting doctors, and even tiny robots cleaning up after us (looking at you, Roomba!). &lt;/p&gt;

&lt;p&gt;Soon, we’ll see more robots doing things we don’t want to do—like cleaning windows or mowing the lawn. It might sound a bit scary, but the idea is to give us more time to do what &lt;em&gt;we&lt;/em&gt; enjoy, whether it’s spending time with loved ones or following hobbies.&lt;/p&gt;

&lt;p&gt;This is where things are getting really interesting. Not so long ago, if you wanted to talk to someone far away, you wrote a letter. Then came phones, and now we have video calls and social media. We can talk to anyone, anytime, anywhere. But this is just the beginning!&lt;/p&gt;

&lt;p&gt;Technology is bringing us closer in ways we couldn’t have imagined. With virtual reality (VR), soon we’ll be able to “visit” each other without ever leaving the house. Imagine putting on special glasses and suddenly being in the same room as someone across the world! And it won’t stop there—new ways of connecting will keep popping up, blurring the lines between “real” and “virtual.”&lt;/p&gt;

&lt;p&gt;Now, let’s think about what all this means for how we interact with each other. Remember when people would sit together and talk over dinner? Well, now we often see folks glued to their phones. That’s one way technology has changed things, for better or worse. &lt;/p&gt;

&lt;p&gt;Technology might help us connect in new ways, but it can also make us feel a little more distant from the people right next to us. It's important to find a balance—to use tech to stay close to those far away, but also to stay present with the people who are in front of us.&lt;/p&gt;

&lt;p&gt;For those of us still working or entering the workforce, things are changing quickly. Automation and artificial intelligence (AI) are taking over some jobs, while creating new ones we haven’t even thought of yet. Maybe one day, your job will involve working alongside an AI or using technology that doesn’t exist today. It’s like being part of a big experiment where everything is always evolving.&lt;/p&gt;

&lt;p&gt;For kids growing up, this might seem normal. They’ll be learning how to code, how to use new gadgets, and how to navigate a world where things move faster and faster. It’s an exciting time, but it’s also one where we’ll need to stay curious and always be learning!&lt;/p&gt;

&lt;p&gt;At the end of the day, even with all this cool technology, what matters most is &lt;em&gt;us&lt;/em&gt;. Technology should help make our lives better—whether it’s helping us stay healthy, keeping us connected, or giving us more time for the things we love. But we need to be careful not to let it take over the things that make us human, like our relationships, our creativity, and our ability to feel empathy.&lt;/p&gt;

&lt;p&gt;So, as technology keeps growing, let’s grow with it. Let’s use it to improve our lives, but let’s also remember to unplug once in a while, spend time with loved ones, and keep our human connections strong.&lt;/p&gt;

&lt;p&gt;Thanks for reading, and I hope you’re as excited as I am to see where technology takes us next!&lt;/p&gt;

&lt;p&gt;Warmly,&lt;br&gt;&lt;br&gt;
Billy&lt;/p&gt;

&lt;h3&gt;
  
  
  Additional Prize Categories
&lt;/h3&gt;

&lt;p&gt;Explain Like I'm Five and Ripple Effects&lt;/p&gt;

</description>
      <category>futurechallenge</category>
    </item>
    <item>
      <title>KeePassDiff: A diff/merge tool for KeePassXC databases</title>
      <dc:creator>Billy</dc:creator>
      <pubDate>Sun, 08 Dec 2024 10:01:40 +0000</pubDate>
      <link>https://dev.to/tomlin7/keepassdiff-a-diffmerge-tool-for-keepassxc-databases-2j6l</link>
      <guid>https://dev.to/tomlin7/keepassdiff-a-diffmerge-tool-for-keepassxc-databases-2j6l</guid>
      <description>&lt;p&gt;I started using KeePass back in 2021 and I have been using it since then to store my passwords. But I didn't set up a proper way to sync the database between my devices. So I ended up with multiple databases with different passwords and entries. I wanted to sort of &lt;strong&gt;diff the databases and merge them&lt;/strong&gt; into one, like git diff -- resolving conflicts, reverting, etc. Well, KeePass doesn't provide a way to diff two databases. Hence this project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Usage
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Open two KeePass databases in diff.&lt;/li&gt;
&lt;li&gt;See the differences and conflicting entries between the two databases.&lt;/li&gt;
&lt;li&gt;Then using merge left, merge right options decide which entries or groups to keep and which to discard.&lt;/li&gt;
&lt;li&gt;Finally, export the merged database.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;keepassdiff
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run &lt;code&gt;kpd&lt;/code&gt; or &lt;code&gt;kpdiff&lt;/code&gt; to run the tool.&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%2Fv2zc7mi6iksttnwarwf1.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%2Fv2zc7mi6iksttnwarwf1.png" alt="image" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Features
&lt;/h2&gt;

&lt;p&gt;Following is a tracker for all the features I'm aiming for the project.&lt;br&gt;
If you are interested in giving me a hand, &lt;a href="https://github.com/tomlin7/KeePassDiff" rel="noopener noreferrer"&gt;check the github repo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;✅ Support for KeePassXC databases&lt;br&gt;
✅ Uploading and unlocking two KeePass databases&lt;br&gt;
✅ Supports both password and keyfile authentication&lt;br&gt;
✅ Visual diff of entries and groups&lt;br&gt;
✅ Support for entry groups&lt;br&gt;
✅ Hierarchical view of database contents&lt;br&gt;
✅ Merging individual entries and groups between databases&lt;br&gt;
✅ Exporting the final merged database&lt;br&gt;
⏺️ Resolving conflicting entries with preferred ones&lt;br&gt;
⏺️ Command-line interface for batch processing&lt;br&gt;
⏺️ Copying passwords to clipboard, clearing clipboard after timeout&lt;/p&gt;

&lt;h2&gt;
  
  
  Security
&lt;/h2&gt;

&lt;p&gt;All database handling is done locally and no data is stored or transmitted. Temporary files are securely deleted after use, passwords are not stored.&lt;/p&gt;

&lt;h2&gt;
  
  
  Development
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/tomlin7/KeePassDiff.git
&lt;span class="nb"&gt;cd &lt;/span&gt;KeePassDiff
pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
kpd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>webdev</category>
      <category>programming</category>
      <category>python</category>
      <category>security</category>
    </item>
    <item>
      <title>AI Research Assistant with Semantic Document Search System</title>
      <dc:creator>Billy</dc:creator>
      <pubDate>Sat, 09 Nov 2024 10:49:42 +0000</pubDate>
      <link>https://dev.to/tomlin7/semantic-document-search-system-with-pgvector-and-pgai-2aca</link>
      <guid>https://dev.to/tomlin7/semantic-document-search-system-with-pgvector-and-pgai-2aca</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/pgai"&gt;Open Source AI Challenge with pgai and Ollama&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;This is an AI based research assistant with a semantic document search system for smart document storage and retrieval using natural language queries. &lt;a href="https://ollama.com/" rel="noopener noreferrer"&gt;&lt;strong&gt;Ollama&lt;/strong&gt;&lt;/a&gt; is integrated into the assistant to summarise, and generate sentiment analysis, key points, related topics for provided content. &lt;a href="https://streamlit.io/" rel="noopener noreferrer"&gt;Streamlit&lt;/a&gt; is used to provide a minimalistic user interface.&lt;/p&gt;

&lt;p&gt;You can use natural language to search data stored in the PostgreSQL database. Uses &lt;a href="https://github.com/pgvector/pgvector" rel="noopener noreferrer"&gt;pgvector&lt;/a&gt; for vector similarity search, &lt;a href="https://github.com/timescale/pgai" rel="noopener noreferrer"&gt;&lt;strong&gt;pgai&lt;/strong&gt;&lt;/a&gt; through &lt;strong&gt;TimescaleDB&lt;/strong&gt; for search AI features. It is very helpful in cases where you have to manage and search through large collections of documents based on meaning rather than just keywords.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Summarize docs, and generate sentiment analysis, key points, and related topics&lt;/li&gt;
&lt;li&gt;Semantic search &amp;amp; insights using document embeddings&lt;/li&gt;
&lt;li&gt;Batch document processing (directly upload CSV files)&lt;/li&gt;
&lt;li&gt;User-friendly interface&lt;/li&gt;
&lt;li&gt;Rich metadata and insights for categorization&lt;/li&gt;
&lt;li&gt;Scalable vector search using both IVFFlat and &lt;a href="https://github.com/timescale/pgvectorscale" rel="noopener noreferrer"&gt;&lt;strong&gt;pgvectorscale&lt;/strong&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Although initially the idea was to develop a semantic document search tool, later on I decided to extend this to an AI research assistant featuring the same document search system along with Ollama integration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;Because of problems with hosting Ollama along with the assistant app, only the semantic document search tool demo is hosted. 😅&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Thanks to Streamlit community cloud, &lt;a href="https://semantic-doc-search.streamlit.app" rel="noopener noreferrer"&gt;&lt;strong&gt;visit the demo&lt;/strong&gt;&lt;/a&gt; ⭐&lt;/li&gt;
&lt;/ul&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%2Fpox95r5uyfqux8xsrqx1.jpeg" 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%2Fpox95r5uyfqux8xsrqx1.jpeg" alt="assistant" width="800" height="399"&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%2Fsth0wz9j2y8vxg0pjt0e.jpeg" 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%2Fsth0wz9j2y8vxg0pjt0e.jpeg" alt="document search tool" width="800" height="399"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img 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" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/tomlin7" rel="noopener noreferrer"&gt;
        tomlin7
      &lt;/a&gt; / &lt;a href="https://github.com/tomlin7/AI-research-assistant" rel="noopener noreferrer"&gt;
        AI-research-assistant
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Semantic document search system with pgvector and PGAI
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;AI Research Assistant with Semantic Document Search System&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/pgai" rel="nofollow"&gt;Open Source AI Challenge with pgai and Ollama&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;What I Built&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;This is an AI based research assistant with a semantic document search system for smart document storage and retrieval using natural language queries. &lt;a href="https://ollama.com/" rel="nofollow noopener noreferrer"&gt;&lt;strong&gt;Ollama&lt;/strong&gt;&lt;/a&gt; is integrated into the assistant to summarise, and generate sentiment analysis, key points, related topics for provided content. &lt;a href="https://streamlit.io/" rel="nofollow noopener noreferrer"&gt;Streamlit&lt;/a&gt; is used to provide a minimalistic user interface.&lt;/p&gt;
&lt;p&gt;You can use natural language to search data stored in the PostgreSQL database. Uses &lt;strong&gt;pgvector&lt;/strong&gt; for vector similarity search, &lt;a href="https://github.com/timescale/pgai" rel="noopener noreferrer"&gt;&lt;strong&gt;pgai&lt;/strong&gt;&lt;/a&gt; through &lt;strong&gt;TimescaleDB&lt;/strong&gt; for search AI features. It is very helpful in cases where you have to manage and search through large collections of documents based on meaning rather than just keywords.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Key Features:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Uses Ollama to summarise docs, and generate sentiment analysis, key points, and related topics&lt;/li&gt;
&lt;li&gt;Semantic search capability using document embeddings, powered…&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/tomlin7/AI-research-assistant" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  Tools Used
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Ollama + pgvector + pgai + Streamlit
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://ollama.com/" rel="noopener noreferrer"&gt;&lt;strong&gt;Ollama&lt;/strong&gt;&lt;/a&gt; is integrated into the assistant to summarise, and generate sentiment analysis, key points, related topics for provided content. &lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.timescale.com/" rel="noopener noreferrer"&gt;&lt;strong&gt;TimescaleDB&lt;/strong&gt;&lt;/a&gt; (PostgreSQL) for primary database (can be configured for self hosted psql as well)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/pgvector/pgvector" rel="noopener noreferrer"&gt;&lt;strong&gt;pgvector&lt;/strong&gt;&lt;/a&gt; for efficient vector similarity search&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/timescale/pgai" rel="noopener noreferrer"&gt;&lt;strong&gt;pgai&lt;/strong&gt;&lt;/a&gt; through TimescaleDB for AI&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://streamlit.io/" rel="noopener noreferrer"&gt;&lt;strong&gt;Streamlit&lt;/strong&gt;&lt;/a&gt; for the web interface&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Key Technologies
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Database Layer&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;pgvector extension for vector operations&lt;/li&gt;
&lt;li&gt;pgai extension for AI features&lt;/li&gt;
&lt;li&gt;IVFFlat indexing for efficient similarity search&lt;/li&gt;
&lt;li&gt;JSONB data type for flexible metadata storage&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Machine Learning&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/UKPLab/sentence-transformers" rel="noopener noreferrer"&gt;Sentence-Transformers&lt;/a&gt; (&lt;code&gt;all-MiniLM-L6-v2 model&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;384-dimensional embeddings for semantic representation&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Backend&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Python 3.12+&lt;/li&gt;
&lt;li&gt;psycopg2 for PostgreSQL interaction&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Frontend&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Streamlit for the web interface&lt;/li&gt;
&lt;li&gt;Pandas for data display&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;This project is about integrating AI vector search features with traditional databases (which are hard to get used to). The same tool is used to create an AI research assistant with Ollama integration. This is a very helpful tool for content management systems where you need to manage and search through large collections of documents. Integration of pgvector and pgai provides a strong solution.&lt;/p&gt;

&lt;h3&gt;
  
  
  TODO
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;⏺️ Better visualization of results using charts and stuff&lt;/li&gt;
&lt;li&gt;✅ Batch document processing (import CSV)&lt;/li&gt;
&lt;li&gt;⏺️ Delete, update documents functionality&lt;/li&gt;
&lt;li&gt;⏺️ Filtering based on metadata as well&lt;/li&gt;
&lt;li&gt;⏺️ More use cases of pgai&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devchallenge</category>
      <category>pgaichallenge</category>
      <category>database</category>
      <category>ai</category>
    </item>
    <item>
      <title>Hello world</title>
      <dc:creator>Billy</dc:creator>
      <pubDate>Wed, 17 Nov 2021 17:10:03 +0000</pubDate>
      <link>https://dev.to/tomlin7/hello-world-5fjf</link>
      <guid>https://dev.to/tomlin7/hello-world-5fjf</guid>
      <description>&lt;p&gt;&lt;strong&gt;This is my first DEV post :)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I got to know about this community from &lt;strong&gt;hacktoberfest 2021&lt;/strong&gt;.&lt;br&gt;
I like talking about python, c#, cpp, unity, tkinter, machine learning, data science, etc.&lt;/p&gt;

&lt;p&gt;That's all!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;print("Hello, DEV!")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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