DEV Community

Ishita Sharmaa
Ishita Sharmaa

Posted on

I’m Too Lazy to Google Search an Image and Paste it Back in Word, So I Built EZSearch

Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady Submission 🦥

This is a submission for the Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady

Repo: https://github.com/zish-s/EZsearch

A sloth does not rush. It conserves its energy and lets the world come to it.
EZsearch is built the same way: two open models run slowly and locally on your laptop, so that you never have to spend time searching for the perfect image to paste into your docs again.


What I Built

Imagine you're finished with your report, and then you remember you need to paste photos!
It can be such a hectic task - google search and then try to find a good quality, not blurred, not watermarked, perfect picture describing what you want, then having to copy, go back to your document and paste.

I'm an avid lazy person, someone who chose to study comp sci because apparently all you need is a laptop and you can work while sitting anywhere.

Hence, to simplify this tedious process I built EZSearch.

The EZsearch popup

This is how the popup looks like in action

  1. Select a sentence anywhere - Word, Google Docs, PowerPoint, OneNote, a browser.
  2. Press Ctrl+Shift+X. A small panel appears next to the cursor with up to eight matching pictures.
  3. Click one and it is pasted right in the document you were already typing in.

Ctrl+click opens the source of the picture. Right-click copies a finished attribution line.

The idea is simple, it was how I implemented the approach to come up with as accurate pics as possible!


Demo

A demonstration video showing exactly how you can use this build on your desktop:


Code

https://github.com/zish-s/EZsearch

  • ezsearch.py - the hotkey, selection capture, popup-close-before-capture, paste dispatch
  • ezsearch_core.py - query ladder, Commons/Openverse clients, licence and artwork filters, de-duplication, CLIP scoring, on-disk cache
  • ezsearch_ui.py - the popup: up to eight tiles, + show more, match bars, credit copy
  • ezsearch_focus.py - the Win32 focus capture and restore that makes Word obey
  • tests/ and benchmark.py - 64 offline tests, and the 20-sentence x 2-model benchmark

MIT licensed. python -m pytest runs the suite; python ezsearch.py --search "..." runs headless.


How I Built It

This project was built using OpenCode

Two open models do the slow jobs in EZsearch, and both run on the laptop:

Piece Job Licence
Llama 3.2 3B via Ollama Turns the sentence into a short, photographable query Llama 3.2 Community
CLIP ViT-B/32 via fastembed (ONNX) Scores each candidate against the whole sentence MIT
Wikimedia Commons + Openverse Supply the pictures CC / public domain, per file
  • Gemma 3 4B is a one-env-var swap and was benchmarked against it.

Around the models it is deliberately plain Python: Tkinter for the popup, pynput for the global hotkey, pywin32 (pythoncom) for the Word automation fallbacks, requests + Pillow for search and previews. Wikimedia Commons and Openverse answer with no API keys. The build itself was pair-programmed in OpenCode, an open-source agent harness, with DevRelay's DEV tools.

The architecture follows the privacy claim:
the sentence is read from the clipboard and handed to the local Ollama server, so it never leaves the machine. Only the short query and the image
downloads cross the network.

The pipeline:

 sentence --> local LLM --> "lighthouse in stormy sea"
                              |
                 +------------+------------+
                 v                         v
          Wikimedia Commons          Openverse     (queried in parallel, merged)
                 +------------+------------+
                              v
                 candidate pictures (24 of them)
                              |
                 local CLIP scores each one against the whole sentence
                               v
                 8 tiles in a panel next to your cursor
                 (a show-more page reveals the rest of the pool)
Enter fullscreen mode Exit fullscreen mode

The accuracy work lives in ezsearch_core.py:

a precision-first query ladder, Commons category mining when filename search is thin, artwork filtering by Commons categories, three layers of
de-duplication, and a licence gate that keeps NC/ND/fair-use images out before you ever see them.
64 offline tests pin the behaviour; the 20-sentence x 2-model benchmark pins the numbers above.


The lazy payoff

I recently had to search up pictures to paste in a word document and get a print of it.
Like normal, I googled them. In the process of trying to find an image that wasn't blurry, didn't have a watermark, or did not copy/paste into a document took me a whole hour. Just to paste 7-8 images.

So I tried my build, and even though I tend to be skeptical with my choices, for a work that just needs to get done and doesn't perfectionism. This build was a major success! I was able to reduce my time to just 10 minutes, taking roughly 30-45 seconds to show suggested images.
It was unbelievably useful and I was very happy to not have to decide which image to pick among hundreds. The model picked out the best search terms and the search engine ranked the best matches!!

I have shown exactly how it works in practicality in the demo video linked above, do check it out :))

Here's how the machine functions:

  • ~13 s for the first search of a new subject on this laptop, with no GPU and no API.
  • ~1 s for the same subject again: the pictures and the CLIP vectors are cached on disk.
  • Zero network round trips for the sentence itself. Only the 2-5 word query leaves the machine.
Stage (median, cold cache) llama3.2:3b gemma3:4b
LLM query build 3.5 s 4.5 s
Image search 1.9 s 1.9 s
Download + CLIP scoring 8.1 s 5.3 s
Sentence to picture 13.4 s 13.3 s

So you trade about thirteen seconds of local compute for a thirty-second context switch. The seconds are similar; what you get back is the paragraph you did not lose. Even A sloth could do it.


Why Does Open Innovation Matter?

Privacy. The sentence we are writing - the draft, the research note, the thing you not published yet - is read from the clipboard and handed to a local Ollama server.
It never leaves the machine. The only thing that crosses the network is the 2-5 word query, and the requests for the pictures themselves.

Cost. $0 per use. A closed vision API bills every single selection, which is exactly the kind of cost that makes people stop doing the thing at all.

Swappable. EZSEARCH_MODEL points at any model you have pulled. I benchmarked two:

Model Query build (median) Top 1 Top 3
Llama 3.2 3B (default) 3.5 s 14/20 16/20
Gemma 3 4B 4.5 s 15/20 15/20

Twenty sentences, ranked in the search engine's order. The two are within noise on quality, and Gemma takes about a second longer per query, so Llama stays the default. EZSEARCH_MODEL=gemma3:4b is a one-line change if you want the other trade.

Offline-capable. Query building and CLIP scoring run with no internet at all. Only the image fetch needs a connection. If I lose my network halfway through a paragraph, EZsearch still finds me the right picture from the cache; any new pictures need the connection back.


Choosing the most relevant model for most accuracy

CLIP helps find images by understanding what a sentence means, rather than just matching keywords.

To test how well it works, I used 20 sentences:

12 that directly name the subject (like a dog) and 8 that describe it without using its name (like “the four-legged animal barked at the man in the blue coat”).

For each sentence, we fetch images once and compare their original search-engine ranking with their ranking after CLIP reorders them based on the full sentence.

An image counts as a correct result only if it shows the right subject and is an actual photograph, not a painting or engraving.

The goal was to see whether CLIP can bring the most relevant photographs closer to the top, especially when the sentence doesn't contain the exact keywords the search engine would normally look for.

Ranking (20 sentences, two models) Top 1 Top 3
Search engine order (keywords only) 29/40 (72%) 31/40 (78%)
CLIP re-ranked 24/40 (60%) 31/40 (78%)

The results were not what I expected. The search engine's original ranking performed better than CLIP at finding the correct image in the first position, with 72% accuracy compared to CLIP's 60%. Both achieved 78% accuracy in the top three results. This meant CLIP did not improve the overall ranking, even when tested on sentences that described subjects without using their exact names.

One limitation is that the test judged images partly by their titles, which naturally favours keyword-based search. A photograph might perfectly match a sentence even if its filename doesn't describe it accurately. However, CLIP also failed to outperform the search engine on the paraphrased sentences designed to test this advantage, so there was no clear evidence that re-ranking helped.

Based on these results, I changed the app to keep the search engine's original image order instead of reordering images using CLIP.
CLIP still scores each image to display match scores and warn users when no strong match is found. Re-ranking can also be enabled again for further testing.

The key learning for me was that a feature shouldn't be kept just because it sounds clever. Testing showed that CLIP's re-ranking wasn't improving results, so I changed the app rather than making users rely on a feature that performed worse.


Run it yourself

git clone https://github.com/zish-s/EZsearch.git
cd EZsearch
python -m venv .venv
.venv\Scripts\activate
pip install -r requirements.txt
ollama pull llama3.2:3b
python ezsearch.py
Enter fullscreen mode Exit fullscreen mode

First run downloads the CLIP weights (~600 MB, cached afterwards). After that it starts instantly
and ranks entirely offline.

python -m pytest        # 64 offline tests
python ezsearch.py --doctor
python ezsearch.py --search "a sentence"   # headless
Enter fullscreen mode Exit fullscreen mode

Windows is the tested platform.


My Agent Session

EZsearch was built across one long OpenCode session with a DeepSeek V4.1 Flash agent: diagnosing
why Word refused the paste, adding the automation fallbacks, then chasing "accuracy" down the query
ladder into Commons' category space. The transcript below is that session curated to the milestones

  • the rest was pytest runs and PowerShell.
EZsearch: making a Windows paste-back hotkey find accurate pictures (curated milestones)
You

EZsearch (my Windows hotkey that pastes openly licensed pictures from Commons/Openverse into the document): make it actually shippable. The picture sometimes never lands in Word, the pasted image eats my selected sentence, and the popup only ever shows one result. Also give me more variety in the grid.

Agent

Two independent Word problems, both caused by synthetic input being ignorable. (1) Focus: Office keeps the caret in a child control, so SetForegroundWindow is not enough. Capture the focused child with GetGUIThreadInfo before the popup appears and restore it with AttachThreadInput + SetFocus on the child, detaching in finally. (2) COM: a synthetic Ctrl+C is as ignorable as a synthetic Ctrl+V, so both halves get an automation path through Word itself - read ActiveWindow.Selection, collapse it to its end (which is why the picture appends after the sentence instead of eating it), and paste with Selection.Paste(). COM must be initialised per thread with pythoncom.CoInitialize. Also moved the default hotkey off Ctrl+Alt+F, which is Word's Insert Footnote accelerator, to Ctrl+Shift+X.

You

This is what it's supposed to show - Chandigarh roundabouts. The grid should feel topical for "The roundabouts in chandigarh", not generic roundabouts from anywhere.

Agent

Commons has zero file hits for "roundabout chandigarh" and no matching categories by name; the topical reservoir is category space ("Road bridges in Chandigarh", "National highways in Chandigarh"). Rewrote resolve_query to be precision-first: the query as given and the de-filler variant lead the grid; Commons category mining fills a thin pool; the closest broader phrasing tops the pool up behind the topical head so Show more still has pages. Openverse got a longer timeout, one retry and a one-minute cooldown instead of ten. Same sentence: 1 candidate before, 24 after.

You

The current working level is good. Refine the README and the DevPost draft, and leave space under a Demo heading for screenshots.

Agent

Synced both documents with the working app: Ctrl+Shift+X and the Insert Footnote story, up to eight tiles plus show more, sentence-survives-paste, the precision-first ladder and category mining, the Word automation fallbacks, 64 offline tests, the Openverse timeout knob. Added a Demo section with three placeholders in README, DevPost.md and the staged DEV draft 4898724.

You

Write another new dev post draft, from your understanding of the project, for the Week 3 submission.

Agent

Created a second, differently-shaped draft: "EZsearch: slow on purpose, steady on your sentence". Argument-first, with three decisions I would defend in review (reranking cut on measurement, the licence gate, the Win32 edges where the demo never looks) and a compressed field-notes bug log. Saved locally as DevPost-alt.md and staged on DEV as draft 4911997.

You

The idea is simple, it was how I implemented the approach to come up with as accurate pics as possible. Justify this sentence with a few pointers. Very few.

Agent

Four pointers: precision-first query ladder; filter before scoring (artwork categories, work-key and 64-bit hash de-duplication); licence gate before display; measured instead of assumed (search order beat CLIP reranking 29/40 vs 24/40 on top-1, so reranking was cut). Accuracy came from the pool, not from a bigger model.

You

Give me content for the DEV post headings: Prize Categories, My Agent Session, Code, How I Built It.

Agent

Prepared the four submission sections and saved this curated session so it can be embedded with the agent_session Liquid tag.


Prize Categories

Best Use of Gemma (featured category). Gemma 3 4B is a first-class local model in EZsearch,
not a demo stub: EZSEARCH_MODEL=gemma3:4b swaps the query model behind the same Ollama call, and
the benchmark in this post was run head-to-head against the default Llama 3.2 3B on 20 sentences.
Gemma built queries in 4.5 s vs 3.5 s, won one more top-1 hit (15/20 vs 14/20) and one fewer top-3
(15/20 vs 16/20). Llama stays the default because it is a full second faster; every Gemma number in
this post came out of the app.

No other partner technology is used in this build - it is a local desktop app with no hosted
database, deployment platform, voice or browser tooling - so the other categories are not claimed.

Solo entry, published by @zishs.

Top comments (1)

Collapse
 
dabhi_rohit_5ebdeb38bb301 profile image
Dabhi Rohit •

Hy how many Hackathon participate u?
I don't know about dev. I earliest joined it and builded first project using ai can u give more info about dev? Help me🙂