<?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: Nora Aguirre</title>
    <description>The latest articles on DEV Community by Nora Aguirre (@noraprogramadora).</description>
    <link>https://dev.to/noraprogramadora</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4187500%2Fb00aa8c3-4a7c-44c3-8444-fcac2d75578d.png</url>
      <title>DEV Community: Nora Aguirre</title>
      <link>https://dev.to/noraprogramadora</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/noraprogramadora"/>
    <language>en</language>
    <item>
      <title>Snap a photo, get a listing: a local Gemma 3 listing generator for secondhand clothes</title>
      <dc:creator>Nora Aguirre</dc:creator>
      <pubDate>Mon, 19 Oct 2026 01:50:31 +0000</pubDate>
      <link>https://dev.to/noraprogramadora/snap-a-photo-get-a-listing-a-local-gemma-3-listing-generator-for-secondhand-clothes-c17</link>
      <guid>https://dev.to/noraprogramadora/snap-a-photo-get-a-listing-a-local-gemma-3-listing-generator-for-secondhand-clothes-c17</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week2-2026-10-12"&gt;Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;I'm building &lt;a href="https://manoenmano.mx" rel="noopener noreferrer"&gt;Mano en Mano&lt;/a&gt;, a circular-economy marketplace for secondhand clothing in Mexico. The part sellers hate most is not taking the photos. It's filling in the form afterwards: title, description, category, subcategory, style, gender, brand, materials, condition, color, size, defects. Twelve fields, for every single garment. For someone clearing out a closet, that is the difference between listing thirty pieces and listing three.&lt;/p&gt;

&lt;p&gt;So I built a listing generator. The seller uploads one to four photos of a garment and the app pre-fills the whole listing. The seller then reviews and edits everything before publishing. The model suggests, the seller decides.&lt;/p&gt;

&lt;p&gt;Who would pay for it? sellers who list a lot of pieces and want to stop retyping the same things&lt;/p&gt;

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

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/u4IuFxRxIdQ" width="710" height="399"&gt;
  &lt;/iframe&gt;
. &lt;/p&gt;
&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/nora-programadora" rel="noopener noreferrer"&gt;
        nora-programadora
      &lt;/a&gt; / &lt;a href="https://github.com/nora-programadora/mano-en-mano-fichas-ia" rel="noopener noreferrer"&gt;
        mano-en-mano-fichas-ia
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &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;Mano en Mano · AI Listing Generator&lt;/h1&gt;
&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;Upload 1 to 4 photos of a secondhand garment and get an editable product listing with 12 fields, generated locally by an open-weight vision model.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href="https://github.com/nora-programadora/mano-en-mano-fichas-ia/LICENSE" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/fdf2982b9f5d7489dcf44570e714e3a15fce6253e0cc6b5aa61a075aac2ff71b/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d4d49542d79656c6c6f772e737667" alt="License: MIT"&gt;&lt;/a&gt;
&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/158ff9b1ff2873d298ba39b9946c7bd4e5a38951da2094074d74d3352f231dc5/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4e6f64652d32302532422d3333393933333f6c6f676f3d6e6f64652e6a73266c6f676f436f6c6f723d7768697465"&gt;&lt;img src="https://camo.githubusercontent.com/158ff9b1ff2873d298ba39b9946c7bd4e5a38951da2094074d74d3352f231dc5/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4e6f64652d32302532422d3333393933333f6c6f676f3d6e6f64652e6a73266c6f676f436f6c6f723d7768697465" alt="Node"&gt;&lt;/a&gt;
&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/3c0af6c551e289eab066ba690024e35842785f434713d91c8ec128cf4afdec73/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4d6f64656c2d47656d6d6125323033253230283442292d343238354634"&gt;&lt;img src="https://camo.githubusercontent.com/3c0af6c551e289eab066ba690024e35842785f434713d91c8ec128cf4afdec73/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4d6f64656c2d47656d6d6125323033253230283442292d343238354634" alt="Gemma 3"&gt;&lt;/a&gt;
&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/16e9eed0b787714e04c0325cd33f3bf16504a67810e6e7ed6db931c8b1379f29/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f52756e732532306f6e2d4f6c6c616d612d303030303030"&gt;&lt;img src="https://camo.githubusercontent.com/16e9eed0b787714e04c0325cd33f3bf16504a67810e6e7ed6db931c8b1379f29/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f52756e732532306f6e2d4f6c6c616d612d303030303030" alt="Ollama"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Built for the &lt;strong&gt;Hacktoberfest Open-Source AI Challenge, Week 2: Side Hustle&lt;/strong&gt; · Read the post on DEV: &lt;a href="https://dev.to/noraprogramadora/snap-a-photo-get-a-listing-a-local-gemma-3-listing-generator-for-secondhand-clothes-c17" rel="nofollow"&gt;POST_URL&lt;/a&gt; · Demo video: &lt;a href="https://www.youtube.com/watch?v=u4IuFxRxIdQ" rel="nofollow noopener noreferrer"&gt;VIDEO_URL&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/nora-programadora/mano-en-mano-fichas-ia/docs/ss.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnora-programadora%2Fmano-en-mano-fichas-ia%2FHEAD%2Fdocs%2Fss.png" alt="Pre-filled listing form"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;The problem&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a href="https://manoenmano.mx" rel="nofollow noopener noreferrer"&gt;Mano en Mano&lt;/a&gt; is a circular-economy marketplace for secondhand clothing. Every seller has to write a listing for each garment: title, description, category, condition, size... For people selling dozens of items, that is slow, repetitive work, and it is one of the main reasons secondhand clothes never get listed.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;What it does&lt;/h2&gt;
&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;The seller uploads 1 to 4 photos of one garment.&lt;/li&gt;
&lt;li&gt;Gemma 3 (&lt;code&gt;gemma3:4b&lt;/code&gt;), running locally in Ollama, analyzes the images.&lt;/li&gt;
&lt;li&gt;The app returns a structured listing that &lt;strong&gt;pre-fills an editable form&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;The seller reviews it, fixes what is wrong…&lt;/li&gt;
&lt;/ol&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/nora-programadora/mano-en-mano-fichas-ia" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;The README has the steps to run it locally: install Ollama, pull &lt;code&gt;gemma3:4b&lt;/code&gt;, start the Express server and the Vite app.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The open pieces&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Model:&lt;/strong&gt; Gemma 3 (4B, vision-capable), open weights.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inference:&lt;/strong&gt; &lt;a href="https://ollama.com" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt;, running locally on my laptop (an AMD Ryzen AI 9 HX 370 with 64 GB of RAM). No cloud calls anywhere.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backend:&lt;/strong&gt; Node.js, TypeScript and Express. One endpoint, &lt;code&gt;POST /generate-listing&lt;/code&gt;, receives the photos and talks to Ollama.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frontend:&lt;/strong&gt; React and Vite. An uploader, an editable pre-filled form, and a confirmation step.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Making the model answer in my marketplace's language&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The most useful thing I did was stop treating the model as a chatbot and treat it as a function that has to return a specific shape. Ollama accepts a JSON schema in its &lt;code&gt;format&lt;/code&gt; parameter and constrains the output to it:&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="nx"&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;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="c1"&gt;// gemma3:4b&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;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;               &lt;span class="c1"&gt;// schema.json&lt;/span&gt;
  &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&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;0&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="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;system&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="nx"&gt;systemPrompt&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;user&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;Genera la ficha de esta prenda.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;images&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;The schema's closed lists are copied from Mano en Mano's own catalog: the same categories, subcategories, styles, genders, conditions, materials, colors and sizes the real form uses. That means the output can fill my form directly, with no translation layer. A &lt;code&gt;taxonomy.json&lt;/code&gt; next to it does two jobs the schema can't: it checks that the subcategory actually belongs to the category, and it maps the color name to the hex value the app stores.&lt;/p&gt;

&lt;p&gt;Every list also has an &lt;code&gt;unknown&lt;/code&gt; option. If the photo doesn't show a brand label or a size tag, the model is told to say &lt;code&gt;unknown&lt;/code&gt; instead of guessing. The server turns that into &lt;code&gt;null&lt;/code&gt;, and the form highlights those fields as "Completar" so the seller knows exactly what is left to fill.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What went wrong with my first prompt&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I ran my first prompt on 10 garment photos from my own closet and read the results field by field. It was fast to get wrong in specific, fixable ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Condition came back as "Con detalles/Usado" for &lt;strong&gt;all 10&lt;/strong&gt; garments, even when it reported no defects. My rule "when in doubt, pick the lowest" made the model always doubt.&lt;/li&gt;
&lt;li&gt;The defects field returned the literal string &lt;code&gt;unknown&lt;/code&gt; in 8 of 10 rows, because two of my rules contradicted each other.&lt;/li&gt;
&lt;li&gt;Two sporty Nike tops were labelled "Boho".&lt;/li&gt;
&lt;li&gt;A khaki pair of pants became "Jeans" and "Marrón" (brown instead of beige).&lt;/li&gt;
&lt;li&gt;A description of a pair of pants mentioned long sleeves.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I rewrote the rules instead of changing the model: condition is now tied to visible defects, defects are empty when none are visible, "Jeans" is only for denim, beige and khaki map to the same color, and a description may only mention parts that exist on that kind of garment. I re-ran the same photos: condition now varied across X of 10 garments, and 4 of the 5 errors above were gone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Measuring the human in the loop&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The confirmation screen tells the seller how many of the 12 fields she corrected compared to what the model proposed. It's a small feature, but it gives me the number I care about: not how often the model is right on a test set, but how much work it actually saves a real person.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Speed and limits&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;On my laptop, each garment takes roughly 15 to 30 seconds (the first request is slower because it loads the model). That is fine for a seller doing a batch, and not fine for anything real-time. The 4B model also makes mistakes, which is why every field stays editable. Condition is the hardest field to judge from a single photo, so I treat it as a suggestion, never as a fact.&lt;/p&gt;

&lt;p&gt;I built this with help from Claude, in chat and in Claude Code, as a pair programmer. The product decisions, the catalog, the prompt rules and the testing are mine. &lt;/p&gt;

&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Privacy.&lt;/strong&gt; Sellers' photos go from the browser to my own server to a model running on the same machine. They never reach a third party. For a marketplace that handles people's belongings and homes in the background of their photos, that matters.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost.&lt;/strong&gt; There are no per-request fees. Secondhand garments are low-value items, and a few cents of API cost per listing would eat into a business that works on small margins.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Control.&lt;/strong&gt; Because I run the model, I can enforce my own schema, rewrite the prompt as many times as I need, and swap Gemma for another open-weight model by changing one environment variable. Fine-tuning on real Mano en Mano listings is the obvious next step, and it is only possible because the weights are open.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No lock-in.&lt;/strong&gt; My pricing doesn't depend on someone else's pricing page.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I haven't benchmarked this against a closed API, so I won't claim it is more accurate. What it gave me is control, privacy and a cost structure that fits this specific product.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Gemma:&lt;/strong&gt; the whole project runs on Gemma 3 locally through Ollama.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;Measurements in centimeters from a flat-lay photo with a reference marker, fine-tuning on real marketplace listings, and integrating the service into the Mano en Mano app.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>ai</category>
      <category>gemma</category>
    </item>
  </channel>
</rss>
