Skip to content

Commit de332b6

Browse files
committed
api: Improve experimental image search
1 parent e134fcb commit de332b6

File tree

19 files changed

+294
-378
lines changed

19 files changed

+294
-378
lines changed

.dockerignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,4 @@
88
**/node_modules
99
**/Pods
1010
docker-compose*
11-
!**/napi-v3/**
11+
!**/napi-v6/**

.gitattributes

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
packages/api/services/story-search/model/comic_embedding_model.onnx filter=lfs diff=lfs merge=lfs -text

.github/workflows/deploy.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,26 @@ jobs:
2323
steps:
2424
- uses: actions/checkout@v4
2525
with:
26+
lfs: false # Don't fetch LFS during checkout
2627
fetch-depth: 0
28+
id: checkout
29+
30+
- name: Fetch required LFS objects
31+
run: |
32+
echo "Fetching required LFS objects..."
33+
git lfs install
34+
35+
# Try to fetch the LFS objects
36+
echo "Attempting to fetch LFS objects..."
37+
git lfs pull origin ${{ github.sha }} --include="packages/api/services/story-search/model/*.onnx" || {
38+
echo "LFS pull failed, trying alternative approach..."
39+
# If LFS pull fails, try to get the file from git directly
40+
git checkout ${{ github.sha }} -- packages/api/services/story-search/model/comic_embedding_model.onnx || true
41+
}
42+
43+
# Verify the model file is available and has content
44+
ls -lh packages/api/services/story-search/model/comic_embedding_model.onnx || echo "File still missing"
45+
file packages/api/services/story-search/model/comic_embedding_model.onnx
2746
2847
- name: Log in to the Container registry
2948
uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1

.vscode/launch.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,15 @@
114114
"stopOnEntry": false,
115115
"watchMode": true
116116
},
117+
{
118+
"type": "bun",
119+
"request": "launch",
120+
"name": "Debug import-entryurl-vectors",
121+
"program": "scripts/import-entryurl-vectors.ts",
122+
"cwd": "${workspaceFolder}/packages/api",
123+
"stopOnEntry": false,
124+
"watchMode": true
125+
},
117126
],
118127
"compounds": [
119128
{

apps/web/src/pages/image-search-test.vue

Lines changed: 57 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,30 @@ meta:
3636
small
3737
caption-top
3838
responsive
39-
/>
39+
>
40+
<template #cell(results)="row">
41+
<template v-if="row.item.results">
42+
<div v-if="'error' in row.item.results">
43+
{{ row.item.results.error }}
44+
</div>
45+
<div v-else>
46+
<b-table
47+
class="results"
48+
:items="row.item.results"
49+
:fields="['score', 'fullUrl']"
50+
small
51+
responsive
52+
>
53+
<template #cell(fullUrl)="result">
54+
<img
55+
:src="CLOUDINARY_URL + result.item.fullUrl"
56+
class="img-fluid"
57+
/>
58+
</template>
59+
</b-table></div
60+
></template>
61+
</template>
62+
</b-table>
4063
</div>
4164
</template>
4265

@@ -54,25 +77,37 @@ type Example = {
5477
isCover: boolean;
5578
};
5679
80+
type Results =
81+
| {
82+
error: string;
83+
}
84+
| {
85+
score: number;
86+
fullUrl: string;
87+
}[];
88+
89+
const CLOUDINARY_URL =
90+
"https://res.cloudinary.com/dl7hskxab/image/upload/inducks-covers/";
91+
5792
const examples = [
5893
{
59-
url: "https://res.cloudinary.com/dl7hskxab/image/upload/inducks-covers/webusers/webusers/2018/07/fr_aljm_011a_001.jpg",
94+
url: `${CLOUDINARY_URL}webusers/webusers/2018/07/fr_aljm_011a_001.jpg`,
6095
isCover: true,
6196
},
6297
{
63-
url: "https://res.cloudinary.com/dl7hskxab/image/upload/inducks-covers/webusers/webusers/2014/02/fr_tp_0024a_001.jpg",
98+
url: `${CLOUDINARY_URL}webusers/webusers/2014/02/fr_tp_0024a_001.jpg`,
6499
isCover: true,
65100
},
66101
{
67-
url: "https://res.cloudinary.com/dl7hskxab/image/upload/inducks-covers/webusers/webusers/2011/01/fr_tp_0013a_001.jpg",
102+
url: `${CLOUDINARY_URL}webusers/webusers/2011/01/fr_tp_0013a_001.jpg`,
68103
isCover: true,
69104
},
70105
{
71-
url: "https://res.cloudinary.com/dl7hskxab/image/upload/inducks-covers/webusers/webusers/2024/05/eg_mg_0149p053_001.jpg",
106+
url: `${CLOUDINARY_URL}webusers/webusers/2024/05/eg_mg_0149p053_001.jpg`,
72107
isCover: false,
73108
},
74109
{
75-
url: "https://res.cloudinary.com/dl7hskxab/image/upload/inducks-covers/webusers/webusers/2021/06/yu_mzc_983c_001.jpg",
110+
url: `${CLOUDINARY_URL}webusers/webusers/2021/06/yu_mzc_983c_001.jpg`,
76111
isCover: false,
77112
},
78113
] as const;
@@ -82,9 +117,9 @@ const models = ref<
82117
modelData: "covers" | "story first pages";
83118
indexSize?: number | string;
84119
getIndexSize: () => Promise<number | string>;
85-
run: (base64: string) => Promise<string>;
120+
run: (base64: string) => Promise<Results>;
86121
time?: string;
87-
results?: string;
122+
results?: Results;
88123
}[]
89124
>([
90125
{
@@ -101,17 +136,12 @@ const models = ref<
101136
const searchResults = await coverIdEvents.searchFromCover(base64);
102137
103138
return "error" in searchResults
104-
? searchResults.errorDetails || "Error"
105-
: JSON.stringify(
106-
searchResults.covers.map(({ issuecode, score }) => ({
107-
issuecode,
108-
score,
109-
})),
110-
);
139+
? { error: searchResults.errorDetails || "Error" }
140+
: searchResults.covers;
111141
} catch (error) {
112142
return typeof error === "object" && "errorDetails" in error!
113-
? (error.errorDetails as string) || "Error"
114-
: "Error";
143+
? { error: (error.errorDetails as string) || "Error" }
144+
: { error: "Error" };
115145
}
116146
},
117147
},
@@ -125,14 +155,8 @@ const models = ref<
125155
true,
126156
);
127157
if ("error" in searchResults) {
128-
return searchResults.error!;
129-
} else
130-
return JSON.stringify(
131-
searchResults.results.map(({ entrycode, score }) => ({
132-
entrycode,
133-
score,
134-
})),
135-
);
158+
return { error: searchResults.error! };
159+
} else return searchResults.results;
136160
},
137161
},
138162
{
@@ -145,14 +169,8 @@ const models = ref<
145169
false,
146170
);
147171
if ("error" in searchResults) {
148-
return searchResults.error!;
149-
} else
150-
return JSON.stringify(
151-
searchResults.results.map(({ entrycode, score }) => ({
152-
entrycode,
153-
score,
154-
})),
155-
);
172+
return { error: searchResults.error! };
173+
} else return searchResults.results;
156174
},
157175
},
158176
]);
@@ -214,6 +232,12 @@ onMounted(async () => {
214232
</script>
215233

216234
<style scoped lang="scss">
235+
:deep(.results) {
236+
td {
237+
width: 50%;
238+
}
239+
}
240+
217241
img {
218242
cursor: pointer;
219243
height: 100px;

apps/whattheduck/src/views/IssueList.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ const getSortedItemsWithCovers = async () => {
220220
key,
221221
item: {
222222
...item,
223-
cover: coverUrls[item.issuecode]!.fullUrl,
223+
cover: coverUrls[item.issuecode]?.fullUrl || null,
224224
},
225225
}));
226226
};

packages/api/Dockerfile

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ RUN apt-get update && apt-get install -y openssl \
99

1010
RUN npm install -g pm2
1111

12-
COPY ./node_modules/.pnpm/onnxruntime-node@*/node_modules/onnxruntime-node/bin/napi-v3/linux/x64 /bin/napi-v3/linux/x64
12+
COPY ./node_modules/.pnpm/onnxruntime-node@*/node_modules/onnxruntime-node/bin/napi-v6/linux/x64 /bin/napi-v6/linux/x64
13+
14+
COPY ./packages/api/services/story-search/model/comic_embedding_model.onnx /app/services/story-search/model/comic_embedding_model.onnx
1315

1416
RUN npm install --os=linux --cpu=x64 sharp
1517

packages/api/index-story-search.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { SocketIoInstrumentation } from "@opentelemetry/instrumentation-socket.i
22
import * as Sentry from "@sentry/node";
33
import dotenv from "dotenv";
44

5-
import { loadModel, server as storySearch } from "./services/story-search";
5+
import { getSession, server as storySearch } from "./services/story-search";
66
import createSocketServer from "./socket";
77

88
dotenv.config({
@@ -20,4 +20,4 @@ if (process.env.SENTRY_DSN) {
2020
const storySearchIo = createSocketServer(3011);
2121
storySearch(storySearchIo);
2222

23-
loadModel();
23+
getSession();

packages/api/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
"sentry:sourcemaps": "sentry-cli sourcemaps inject --org bruno-perel --project dm-api ./dist && sentry-cli sourcemaps upload --org bruno-perel --project dm-api ./dist"
2020
},
2121
"dependencies": {
22-
"@huggingface/transformers": "~3.5.2",
2322
"@opentelemetry/instrumentation-socket.io": "^0.46.1",
2423
"@pusher/push-notifications-server": "^1.2.7",
2524
"@sentry/node": "^9.46.0",
@@ -34,6 +33,7 @@
3433
"ejs": "^3.1.10",
3534
"i18n": "^0.15.1",
3635
"jsonwebtoken": "^9.0.2",
36+
"onnxruntime-node": "^1.22.0-rev",
3737
"nodemailer": "^7.0.5",
3838
"sharp": "0.33.5",
3939
"sharp-0-34": "npm:sharp@^0.34.3",

0 commit comments

Comments
 (0)