mirror of
https://github.com/Damillora/phoebe.git
synced 2025-03-10 05:57:22 +00:00
feat: add dedicated similarity search endpoint
This commit is contained in:
parent
fa81548e8a
commit
4c2fb159a9
@ -29,6 +29,10 @@ func InitializeBlobRoutes(g *gin.Engine) {
|
||||
{
|
||||
protected.POST("/upload", uploadBlob)
|
||||
}
|
||||
unprotected := g.Group("/api/blob")
|
||||
{
|
||||
unprotected.POST("/search", searchBlob)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -45,14 +49,6 @@ func uploadBlob(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.ErrorResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
Message: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
id := uuid.NewString()
|
||||
folder1 := id[0:2]
|
||||
folder2 := id[2:4]
|
||||
@ -205,3 +201,48 @@ func uploadBlob(c *gin.Context) {
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func searchBlob(c *gin.Context) {
|
||||
// Source
|
||||
file, err := c.FormFile("file")
|
||||
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.ErrorResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
Message: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
fileObj, _ := file.Open()
|
||||
originalImage, _, err := image.Decode(fileObj)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.ErrorResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
Message: err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
hash, err := goimagehash.PerceptionHash(originalImage)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.ErrorResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
Message: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
hashInt := hash.GetHash()
|
||||
|
||||
similarPosts, err := services.SimilaritySearch(hashInt)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.ErrorResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
Message: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK,
|
||||
models.SimilarResponse{
|
||||
Similar: similarPosts,
|
||||
})
|
||||
}
|
||||
|
@ -24,5 +24,7 @@ type PostListItem struct {
|
||||
|
||||
type PostSimilarityListItem struct {
|
||||
ID string `json:"id"`
|
||||
ImagePath string `json:"image_path"`
|
||||
ImageThumbnailPath string `json:"thumbnail_path"`
|
||||
Distance int `json:"distance"`
|
||||
}
|
||||
|
@ -28,6 +28,11 @@ type BlobSimilarResponse struct {
|
||||
PreviewUrl string `json:"previewUrl"`
|
||||
Similar []PostSimilarityListItem `json:"similar"`
|
||||
}
|
||||
|
||||
type SimilarResponse struct {
|
||||
Similar []PostSimilarityListItem `json:"similar"`
|
||||
}
|
||||
|
||||
type PostPaginationResponse struct {
|
||||
CurrentPage int `json:"currentPage"`
|
||||
TotalPage int `json:"totalPage"`
|
||||
|
@ -39,6 +39,8 @@ func SimilaritySearch(originalHashInt uint64) ([]models.PostSimilarityListItem,
|
||||
database.DB.Where("blob_id = ?", blob.ID).Find(&post)
|
||||
posts = append(posts, models.PostSimilarityListItem{
|
||||
ID: post.ID,
|
||||
ImageThumbnailPath: "/data/" + blob.ThumbnailFilePath,
|
||||
ImagePath: "/data/" + blob.FilePath,
|
||||
Distance: distance,
|
||||
})
|
||||
}
|
||||
|
@ -123,6 +123,28 @@ export async function uploadBlob({ file, onProgress }) {
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function searchBlob({ file, onProgress }) {
|
||||
var formData = new FormData();
|
||||
formData.append("file", file);
|
||||
const endpoint = url + "/api/blob/search";
|
||||
const response = await axios({
|
||||
url: endpoint,
|
||||
method: "POST",
|
||||
headers: {
|
||||
'Authorization': 'Bearer ' + current_token,
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
withCredentials: true,
|
||||
data: formData,
|
||||
onUploadProgress: e => {
|
||||
if (onProgress) {
|
||||
onProgress(e)
|
||||
}
|
||||
}
|
||||
})
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function postCreate({ blob_id, source_url, tags }) {
|
||||
const endpoint = url + "/api/post/create";
|
||||
const response = await axios({
|
||||
|
@ -41,6 +41,7 @@
|
||||
<div class="navbar-start">
|
||||
<a class="navbar-item" href="/posts">Posts</a>
|
||||
<a class="navbar-item" href="/tags">Tags</a>
|
||||
<a class="navbar-item" href="/imagesearch">Image Search</a>
|
||||
{#if loggedIn}
|
||||
<a class="navbar-item" href="/upload">Upload</a>
|
||||
{/if}
|
||||
|
144
pkg/web/src/routes/imagesearch/+page.svelte
Normal file
144
pkg/web/src/routes/imagesearch/+page.svelte
Normal file
@ -0,0 +1,144 @@
|
||||
<script>
|
||||
import { getTagAutocomplete, searchBlob } from "$lib/api";
|
||||
import PostGallery from "$lib/components/ui/PostGallery.svelte";
|
||||
import ShiorikoImage from "$lib/components/ui/ShiorikoImage.svelte";
|
||||
import { paginate } from "$lib/simple-pagination";
|
||||
|
||||
let currentProgress = $state(0);
|
||||
|
||||
let file = $state();
|
||||
let fileName = $state("");
|
||||
let similar = $state([]);
|
||||
let similarCount = $state(0);
|
||||
let loading = $state(false);
|
||||
let loaded = $state(false);
|
||||
|
||||
let form = $state({
|
||||
blob_id: "",
|
||||
source_url: "",
|
||||
tags: [],
|
||||
});
|
||||
|
||||
const onProgress = (e) => {
|
||||
var percentCompleted = Math.round((e.loaded * 100) / e.total);
|
||||
currentProgress = percentCompleted;
|
||||
};
|
||||
|
||||
const onFileChange = async (e) => {
|
||||
file = e.target.files[0];
|
||||
fileName = file.name;
|
||||
};
|
||||
const onSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
loading = true;
|
||||
loaded = false;
|
||||
similar = [];
|
||||
if (file) {
|
||||
var response = await searchBlob({ file, onProgress });
|
||||
similar = response.similar;
|
||||
similarCount = similar.length;
|
||||
}
|
||||
loading = false;
|
||||
loaded = true;
|
||||
};
|
||||
</script>
|
||||
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<div class="columns">
|
||||
<div class="column is-one-third">
|
||||
<div class="panel is-primary">
|
||||
<form onsubmit={onSubmit}>
|
||||
<p class="panel-heading">Image Search</p>
|
||||
<div class="panel-block column">
|
||||
<div class="row">
|
||||
<label for="file" class="label">Image:</label>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<div class="file">
|
||||
<label class="file-label">
|
||||
<input
|
||||
id="file"
|
||||
class="file-input"
|
||||
type="file"
|
||||
name="resume"
|
||||
onchange={onFileChange}
|
||||
/>
|
||||
<span class="file-cta">
|
||||
<span class="file-icon"
|
||||
></span>
|
||||
<span class="file-label">
|
||||
Choose a file…
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
{#if fileName}
|
||||
<p class="help">{fileName}</p>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{#if currentProgress > 0 && currentProgress < 100}
|
||||
<div class="panel-block">
|
||||
<progress
|
||||
class="progress is-primary is-small"
|
||||
value={currentProgress}
|
||||
max="100"
|
||||
>
|
||||
{currentProgress}%
|
||||
</progress>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="panel-block column">
|
||||
<button
|
||||
type="submit"
|
||||
class="button is-primary is-fullwidth is-outlined"
|
||||
>Search</button
|
||||
>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-two-thirds">
|
||||
{#if !loading}
|
||||
{#if loaded}
|
||||
{#if similarCount > 0}
|
||||
<div class="block">
|
||||
<div class="notification is-success">
|
||||
Found {similarCount} similar posts.
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="block">
|
||||
<div class="notification is-warning">
|
||||
Found no similar posts.
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
<div class="notification is-primary">
|
||||
Similar posts will appear here.
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
<div class="columns is-multiline">
|
||||
{#if !loading}
|
||||
{#if similarCount > 0}
|
||||
<div class="column is-full">
|
||||
<PostGallery posts={similar} />
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
<div class="column">
|
||||
<div class="skeleton-block"></div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
Loading…
x
Reference in New Issue
Block a user