Compare commits
3 commits
9c0b8bef3c
...
2393faf9d0
| Author | SHA1 | Date | |
|---|---|---|---|
| 2393faf9d0 | |||
| c7bca62df4 | |||
| aeefd4a157 |
12 changed files with 97 additions and 12 deletions
|
|
@ -1,7 +1,7 @@
|
||||||
meta {
|
meta {
|
||||||
name: delete_snippet
|
name: delete_snippet
|
||||||
type: http
|
type: http
|
||||||
seq: 5
|
seq: 6
|
||||||
}
|
}
|
||||||
|
|
||||||
delete {
|
delete {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
meta {
|
meta {
|
||||||
name: get_snippet_by_slug
|
name: get
|
||||||
type: http
|
type: http
|
||||||
seq: 10
|
seq: 5
|
||||||
}
|
}
|
||||||
|
|
||||||
get {
|
get {
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
meta {
|
meta {
|
||||||
name: get_snippets
|
name: list
|
||||||
type: http
|
type: http
|
||||||
seq: 4
|
seq: 4
|
||||||
}
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
meta {
|
meta {
|
||||||
name: ping
|
name: ping
|
||||||
type: http
|
type: http
|
||||||
seq: 1
|
seq: 2
|
||||||
}
|
}
|
||||||
|
|
||||||
get {
|
get {
|
||||||
|
|
|
||||||
23
bruno/CodeSnippets/similar.bru
Normal file
23
bruno/CodeSnippets/similar.bru
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
meta {
|
||||||
|
name: similar
|
||||||
|
type: http
|
||||||
|
seq: 10
|
||||||
|
}
|
||||||
|
|
||||||
|
get {
|
||||||
|
url: {{host}}/api/similar?slug=rg-output
|
||||||
|
body: none
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
params:query {
|
||||||
|
slug: rg-output
|
||||||
|
}
|
||||||
|
|
||||||
|
body:json {
|
||||||
|
{
|
||||||
|
"title": "Test Snippet",
|
||||||
|
"markdown": "## Cool Snippet\ndoes a cool thing",
|
||||||
|
"tags": ["git", "jj"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
[snippets.use-cases.view]
|
[snippets.use-cases.view]
|
||||||
[snippets.use-cases.delete]
|
[snippets.use-cases.delete]
|
||||||
[snippets.use-cases.create]
|
[snippets.use-cases.create]
|
||||||
|
[snippets.use-cases.similar-search]
|
||||||
[snippets.use-cases.edit]
|
[snippets.use-cases.edit]
|
||||||
[snippets.infra.config :as config]
|
[snippets.infra.config :as config]
|
||||||
[muuntaja.middleware :as mm]
|
[muuntaja.middleware :as mm]
|
||||||
|
|
@ -60,6 +61,14 @@
|
||||||
{:status 200
|
{:status 200
|
||||||
:body (snippets.use-cases.view/view-snippets-by-tag tag)}))
|
:body (snippets.use-cases.view/view-snippets-by-tag tag)}))
|
||||||
|
|
||||||
|
(defn handle-view-similar-snippets [{params :query-params}]
|
||||||
|
(let [slug (get params "slug")]
|
||||||
|
(cond
|
||||||
|
(nil? slug) {:status 400
|
||||||
|
:body "Slug parameter is required"}
|
||||||
|
:else {:status 200
|
||||||
|
:body (snippets.use-cases.similar-search/search-similar slug)})))
|
||||||
|
|
||||||
(defn handle-view-snippet-by-slug [{params :query-params}]
|
(defn handle-view-snippet-by-slug [{params :query-params}]
|
||||||
(let [slug (get params "slug")
|
(let [slug (get params "slug")
|
||||||
snippet (snippets.use-cases.view/view-snippet-by-slug slug)]
|
snippet (snippets.use-cases.view/view-snippet-by-slug slug)]
|
||||||
|
|
@ -83,6 +92,7 @@
|
||||||
["/tags" {:get handle-view-tags}]
|
["/tags" {:get handle-view-tags}]
|
||||||
["/tag" {:get handle-view-snippets-by-tag}]
|
["/tag" {:get handle-view-snippets-by-tag}]
|
||||||
["/snippet-by-slug" {:get handle-view-snippet-by-slug}]
|
["/snippet-by-slug" {:get handle-view-snippet-by-slug}]
|
||||||
|
["/similar" {:get handle-view-similar-snippets}]
|
||||||
["/snippets" {:get handle-view-snippets}]
|
["/snippets" {:get handle-view-snippets}]
|
||||||
["/snippet" {:post handle-create-snippet
|
["/snippet" {:post handle-create-snippet
|
||||||
:patch handle-edit-snippet
|
:patch handle-edit-snippet
|
||||||
|
|
|
||||||
|
|
@ -122,6 +122,17 @@
|
||||||
:where [?e :snippet/slug ?slug]]]
|
:where [?e :snippet/slug ?slug]]]
|
||||||
(ffirst (d/q query db slug))))
|
(ffirst (d/q query db slug))))
|
||||||
|
|
||||||
|
(defn- get-by-db-id-from-db
|
||||||
|
"Get a snippet by db id"
|
||||||
|
[id]
|
||||||
|
(let [conn (get-conn)
|
||||||
|
db (d/db conn)
|
||||||
|
entity (d/pull db '[* :snippet] id)]
|
||||||
|
(if (= (keys entity) [:db/id]) nil entity)))
|
||||||
|
|
||||||
|
(def get-by-db-id
|
||||||
|
(wrap-snippet-return get-by-db-id-from-db))
|
||||||
|
|
||||||
(defn- get-snippet-by-slug-from-db
|
(defn- get-snippet-by-slug-from-db
|
||||||
"Get a single snippet by its slug."
|
"Get a single snippet by its slug."
|
||||||
[slug]
|
[slug]
|
||||||
|
|
@ -163,7 +174,9 @@
|
||||||
new-tags (get raw-patch :tags '[])
|
new-tags (get raw-patch :tags '[])
|
||||||
existing-tags (get snippet :snippet/tags '[])
|
existing-tags (get snippet :snippet/tags '[])
|
||||||
tags-to-remove (vec (set/difference (set existing-tags) (set new-tags)))
|
tags-to-remove (vec (set/difference (set existing-tags) (set new-tags)))
|
||||||
retracts (map #(vector :db/retract eid :snippet/tags %) tags-to-remove)
|
retracts (if (nil? (:tags raw-patch))
|
||||||
|
nil
|
||||||
|
(map #(vector :db/retract eid :snippet/tags %) tags-to-remove))
|
||||||
patch (merge (to-update raw-patch) {:db/id eid})]
|
patch (merge (to-update raw-patch) {:db/id eid})]
|
||||||
(t/log! {:level :info, :data {:patch patch :retracts retracts :slug slug :eid eid}} "Patching snippet")
|
(t/log! {:level :info, :data {:patch patch :retracts retracts :slug slug :eid eid}} "Patching snippet")
|
||||||
(when (nil? eid)
|
(when (nil? eid)
|
||||||
|
|
|
||||||
|
|
@ -54,11 +54,36 @@
|
||||||
"Save an embedding to Qdrant"
|
"Save an embedding to Qdrant"
|
||||||
[snippet embed]
|
[snippet embed]
|
||||||
(let [api-key (:qdrant-api-key (config))
|
(let [api-key (:qdrant-api-key (config))
|
||||||
id (db/slug-to-db-id (:slug snippet))]
|
id (db/slug-to-db-id (:slug snippet))
|
||||||
(t/log! {:level :info :data {:slug (:slug snippet) :api-key api-key :id id}} "Saving embedding for snippet")
|
host (str (:qdrant-host (config)) "/collections/snippets-dev/points")]
|
||||||
(http/put (str (:qdrant-host (config)) "/collections/snippets-dev/points")
|
(t/log! {:level :info :data {:slug (:slug snippet) :id id :host host}} "Saving embedding for snippet")
|
||||||
|
(http/put host
|
||||||
{:headers {"api-key" api-key}
|
{:headers {"api-key" api-key}
|
||||||
:content-type :json
|
:content-type :json
|
||||||
:form-params {:points [{:id id :vector embed :payload {:slug (:slug snippet)}}]}
|
:form-params {:points [{:id id :vector embed :payload {:slug (:slug snippet)}}]}
|
||||||
;; :cookie-store false
|
;; :cookie-store false
|
||||||
:as :json})))
|
:as :json})))
|
||||||
|
|
||||||
|
(defn get-and-save-embed
|
||||||
|
"Get an embedding for a snippet and save it to Qdrant"
|
||||||
|
[snippet]
|
||||||
|
(let [embed (get-embed snippet)]
|
||||||
|
(save-embed snippet embed)))
|
||||||
|
|
||||||
|
(defn search
|
||||||
|
"Search for similar snippet in Qdrant
|
||||||
|
returns example: [{:id 101155069755600, :version 204, :score 0.85372585}] or []"
|
||||||
|
[slug]
|
||||||
|
(let [db-id (db/slug-to-db-id slug)
|
||||||
|
api-key (:qdrant-api-key (config))
|
||||||
|
host (str (:qdrant-host (config)) "/collections/snippets-dev/points/query")]
|
||||||
|
(t/log! {:level :info :data {:slug slug :db-id db-id}} "Searching Qdrant for similar snippets")
|
||||||
|
(when (nil? db-id)
|
||||||
|
(throw (ex-info "Invalid slug" {:slug slug})))
|
||||||
|
(->
|
||||||
|
(http/post host
|
||||||
|
{:headers {"api-key" api-key}
|
||||||
|
:content-type :json
|
||||||
|
:form-params {:query db-id :limit 3 :score_threshold 0.7}
|
||||||
|
:as :json})
|
||||||
|
(get-in [:body :result :points] '[]))))
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
(ns snippets.use-cases.create
|
(ns snippets.use-cases.create
|
||||||
(:require
|
(:require
|
||||||
[taoensso.telemere :as t]
|
[taoensso.telemere :as t]
|
||||||
|
[snippets.infra.text-embed :as embed]
|
||||||
[snippets.infra.db :as db]))
|
[snippets.infra.db :as db]))
|
||||||
|
|
||||||
(defn create-snippet [{:keys [title slug markdown tags]}]
|
(defn create-snippet [{:keys [title slug markdown tags]}]
|
||||||
(let [pub-date (java.util.Date.)]
|
(let [pub-date (java.util.Date.)]
|
||||||
(t/log! {:level :info, :data {:title title :slug slug}} "Creating snippet")
|
(t/log! {:level :info, :data {:title title :slug slug}} "Creating snippet")
|
||||||
(db/create-snippets [{:title title :slug slug :markdown markdown :tags tags :pub-date pub-date}])
|
(db/create-snippets [{:title title :slug slug :markdown markdown :tags tags :pub-date pub-date}])
|
||||||
;; TODO: caculate text embed vector
|
(embed/get-and-save-embed (db/get-snippet-by-slug slug))))
|
||||||
))
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
(ns snippets.use-cases.edit
|
(ns snippets.use-cases.edit
|
||||||
(:require
|
(:require
|
||||||
[taoensso.telemere :as t]
|
[taoensso.telemere :as t]
|
||||||
|
[snippets.infra.text-embed :as embed]
|
||||||
[malli.core :as m]
|
[malli.core :as m]
|
||||||
[snippets.infra.db :as db]))
|
[snippets.infra.db :as db]))
|
||||||
|
|
||||||
|
|
@ -18,5 +19,6 @@
|
||||||
(do
|
(do
|
||||||
(t/log! {:level :info, :data {:patch patch :slug slug}} "Valid changes editing snippet")
|
(t/log! {:level :info, :data {:patch patch :slug slug}} "Valid changes editing snippet")
|
||||||
(db/update-snippet slug patch)
|
(db/update-snippet slug patch)
|
||||||
|
(embed/get-and-save-embed (db/get-snippet-by-slug slug))
|
||||||
{:success true})
|
{:success true})
|
||||||
{:success false :reason :invalid-patch}))
|
{:success false :reason :invalid-patch}))
|
||||||
|
|
|
||||||
10
src/snippets/use_cases/similar_search.clj
Normal file
10
src/snippets/use_cases/similar_search.clj
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
(ns snippets.use-cases.similar-search
|
||||||
|
(:require [snippets.infra.text-embed :as embed]
|
||||||
|
[taoensso.telemere :as t]
|
||||||
|
[snippets.infra.db :as db]))
|
||||||
|
|
||||||
|
(defn search-similar
|
||||||
|
[slug]
|
||||||
|
(t/log! {:level :info :data {:slug slug}} "Making a similar search by slug")
|
||||||
|
(->> (embed/search slug)
|
||||||
|
(map #(hash-map :snippet (db/get-by-db-id (:id %)), :score (:score %)))))
|
||||||
|
|
@ -7,7 +7,9 @@
|
||||||
"Converts snippet pub-date to ISO-8601 string for EDN serialization"
|
"Converts snippet pub-date to ISO-8601 string for EDN serialization"
|
||||||
[snippet]
|
[snippet]
|
||||||
(when snippet
|
(when snippet
|
||||||
(assoc snippet :pub-date (.toString (:pub-date snippet)))))
|
(-> snippet
|
||||||
|
(assoc :tags (if (nil? (:tags snippet)) '[] (:tags snippet)))
|
||||||
|
(assoc :pub-date (.toString (:pub-date snippet))))))
|
||||||
|
|
||||||
(defn view-snippets [options]
|
(defn view-snippets [options]
|
||||||
(let [limit (:limit options)
|
(let [limit (:limit options)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue