mirror of
https://github.com/osmarks/meme-search-engine.git
synced 2025-04-26 04:33:10 +00:00
restore stashed code
This commit is contained in:
parent
fcd28a5ede
commit
3e568ff613
62
Cargo.lock
generated
62
Cargo.lock
generated
@ -416,6 +416,18 @@ version = "2.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b81e1519b0d82120d2fd469d5bfb2919a9361c48b02d82d04befc1cdd2002452"
|
||||
|
||||
[[package]]
|
||||
name = "bitvec"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c"
|
||||
dependencies = [
|
||||
"funty",
|
||||
"radium",
|
||||
"tap",
|
||||
"wyz",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.10.4"
|
||||
@ -1117,6 +1129,12 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "funty"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
|
||||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.31"
|
||||
@ -2850,6 +2868,12 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "radium"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.5"
|
||||
@ -3274,6 +3298,12 @@ version = "4.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
|
||||
|
||||
[[package]]
|
||||
name = "seahash"
|
||||
version = "4.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
|
||||
|
||||
[[package]]
|
||||
name = "security-framework"
|
||||
version = "2.11.1"
|
||||
@ -3873,6 +3903,12 @@ dependencies = [
|
||||
"version-compare",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tap"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
|
||||
|
||||
[[package]]
|
||||
name = "target-lexicon"
|
||||
version = "0.12.16"
|
||||
@ -3958,6 +3994,7 @@ dependencies = [
|
||||
"bytes",
|
||||
"libc",
|
||||
"mio 1.0.2",
|
||||
"mio 1.0.2",
|
||||
"parking_lot",
|
||||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
@ -4162,6 +4199,17 @@ dependencies = [
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tqdm"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa2d2932240205a99b65f15d9861992c95fbb8c9fb280b3a1f17a92db6dc611f"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"crossterm",
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.40"
|
||||
@ -4206,6 +4254,17 @@ dependencies = [
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-log"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
|
||||
dependencies = [
|
||||
"log",
|
||||
"once_cell",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-subscriber"
|
||||
version = "0.3.18"
|
||||
@ -4214,14 +4273,17 @@ checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b"
|
||||
dependencies = [
|
||||
"matchers",
|
||||
"nu-ansi-term",
|
||||
"nu-ansi-term",
|
||||
"once_cell",
|
||||
"regex",
|
||||
"sharded-slab",
|
||||
"smallvec",
|
||||
"smallvec",
|
||||
"thread_local",
|
||||
"tracing",
|
||||
"tracing-core",
|
||||
"tracing-log",
|
||||
"tracing-log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
453
clipfront2/package-lock.json
generated
453
clipfront2/package-lock.json
generated
@ -5,8 +5,8 @@
|
||||
"packages": {
|
||||
"": {
|
||||
"devDependencies": {
|
||||
"esbuild": "^0.12.15",
|
||||
"esbuild-svelte": "^0.5.3",
|
||||
"esbuild": "^0.13.5",
|
||||
"esbuild-svelte": "^0.6.3",
|
||||
"sass": "^1.68.0",
|
||||
"svelte-preprocess-sass": "^2.0.1"
|
||||
}
|
||||
@ -67,27 +67,289 @@
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.12.15",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.12.15.tgz",
|
||||
"integrity": "sha512-72V4JNd2+48eOVCXx49xoSWHgC3/cCy96e7mbXKY+WOWghN00cCmlGnwVLRhRHorvv0dgCyuMYBZlM2xDM5OQw==",
|
||||
"version": "0.13.15",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.13.15.tgz",
|
||||
"integrity": "sha512-raCxt02HBKv8RJxE8vkTSCXGIyKHdEdGfUmiYb8wnabnaEmHzyW7DCHb5tEN0xU8ryqg5xw54mcwnYkC4x3AIw==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"esbuild": "bin/esbuild"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"esbuild-android-arm64": "0.13.15",
|
||||
"esbuild-darwin-64": "0.13.15",
|
||||
"esbuild-darwin-arm64": "0.13.15",
|
||||
"esbuild-freebsd-64": "0.13.15",
|
||||
"esbuild-freebsd-arm64": "0.13.15",
|
||||
"esbuild-linux-32": "0.13.15",
|
||||
"esbuild-linux-64": "0.13.15",
|
||||
"esbuild-linux-arm": "0.13.15",
|
||||
"esbuild-linux-arm64": "0.13.15",
|
||||
"esbuild-linux-mips64le": "0.13.15",
|
||||
"esbuild-linux-ppc64le": "0.13.15",
|
||||
"esbuild-netbsd-64": "0.13.15",
|
||||
"esbuild-openbsd-64": "0.13.15",
|
||||
"esbuild-sunos-64": "0.13.15",
|
||||
"esbuild-windows-32": "0.13.15",
|
||||
"esbuild-windows-64": "0.13.15",
|
||||
"esbuild-windows-arm64": "0.13.15"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-svelte": {
|
||||
"version": "0.5.3",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-svelte/-/esbuild-svelte-0.5.3.tgz",
|
||||
"integrity": "sha512-KByKD/yt8QaqKjLu32MG3MXBExJYlDM0QwzW3pzKLJR4eev0923DrUKRHPBBjB+OVirUtZnEJE/qitjdW/WyAw==",
|
||||
"node_modules/esbuild-android-arm64": {
|
||||
"version": "0.13.15",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.13.15.tgz",
|
||||
"integrity": "sha512-m602nft/XXeO8YQPUDVoHfjyRVPdPgjyyXOxZ44MK/agewFFkPa8tUo6lAzSWh5Ui5PB4KR9UIFTSBKh/RrCmg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
]
|
||||
},
|
||||
"node_modules/esbuild-darwin-64": {
|
||||
"version": "0.13.15",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.13.15.tgz",
|
||||
"integrity": "sha512-ihOQRGs2yyp7t5bArCwnvn2Atr6X4axqPpEdCFPVp7iUj4cVSdisgvEKdNR7yH3JDjW6aQDw40iQFoTqejqxvQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
]
|
||||
},
|
||||
"node_modules/esbuild-darwin-arm64": {
|
||||
"version": "0.13.15",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.13.15.tgz",
|
||||
"integrity": "sha512-i1FZssTVxUqNlJ6cBTj5YQj4imWy3m49RZRnHhLpefFIh0To05ow9DTrXROTE1urGTQCloFUXTX8QfGJy1P8dQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
]
|
||||
},
|
||||
"node_modules/esbuild-freebsd-64": {
|
||||
"version": "0.13.15",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.13.15.tgz",
|
||||
"integrity": "sha512-G3dLBXUI6lC6Z09/x+WtXBXbOYQZ0E8TDBqvn7aMaOCzryJs8LyVXKY4CPnHFXZAbSwkCbqiPuSQ1+HhrNk7EA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
]
|
||||
},
|
||||
"node_modules/esbuild-freebsd-arm64": {
|
||||
"version": "0.13.15",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.13.15.tgz",
|
||||
"integrity": "sha512-KJx0fzEDf1uhNOZQStV4ujg30WlnwqUASaGSFPhznLM/bbheu9HhqZ6mJJZM32lkyfGJikw0jg7v3S0oAvtvQQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
]
|
||||
},
|
||||
"node_modules/esbuild-linux-32": {
|
||||
"version": "0.13.15",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.13.15.tgz",
|
||||
"integrity": "sha512-ZvTBPk0YWCLMCXiFmD5EUtB30zIPvC5Itxz0mdTu/xZBbbHJftQgLWY49wEPSn2T/TxahYCRDWun5smRa0Tu+g==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/esbuild-linux-64": {
|
||||
"version": "0.13.15",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.13.15.tgz",
|
||||
"integrity": "sha512-eCKzkNSLywNeQTRBxJRQ0jxRCl2YWdMB3+PkWFo2BBQYC5mISLIVIjThNtn6HUNqua1pnvgP5xX0nHbZbPj5oA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/esbuild-linux-arm": {
|
||||
"version": "0.13.15",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.13.15.tgz",
|
||||
"integrity": "sha512-wUHttDi/ol0tD8ZgUMDH8Ef7IbDX+/UsWJOXaAyTdkT7Yy9ZBqPg8bgB/Dn3CZ9SBpNieozrPRHm0BGww7W/jA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/esbuild-linux-arm64": {
|
||||
"version": "0.13.15",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.13.15.tgz",
|
||||
"integrity": "sha512-bYpuUlN6qYU9slzr/ltyLTR9YTBS7qUDymO8SV7kjeNext61OdmqFAzuVZom+OLW1HPHseBfJ/JfdSlx8oTUoA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/esbuild-linux-mips64le": {
|
||||
"version": "0.13.15",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.13.15.tgz",
|
||||
"integrity": "sha512-KlVjIG828uFPyJkO/8gKwy9RbXhCEUeFsCGOJBepUlpa7G8/SeZgncUEz/tOOUJTcWMTmFMtdd3GElGyAtbSWg==",
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/esbuild-linux-ppc64le": {
|
||||
"version": "0.13.15",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.13.15.tgz",
|
||||
"integrity": "sha512-h6gYF+OsaqEuBjeesTBtUPw0bmiDu7eAeuc2OEH9S6mV9/jPhPdhOWzdeshb0BskRZxPhxPOjqZ+/OqLcxQwEQ==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/esbuild-netbsd-64": {
|
||||
"version": "0.13.15",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.13.15.tgz",
|
||||
"integrity": "sha512-3+yE9emwoevLMyvu+iR3rsa+Xwhie7ZEHMGDQ6dkqP/ndFzRHkobHUKTe+NCApSqG5ce2z4rFu+NX/UHnxlh3w==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
]
|
||||
},
|
||||
"node_modules/esbuild-openbsd-64": {
|
||||
"version": "0.13.15",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.13.15.tgz",
|
||||
"integrity": "sha512-wTfvtwYJYAFL1fSs8yHIdf5GEE4NkbtbXtjLWjM3Cw8mmQKqsg8kTiqJ9NJQe5NX/5Qlo7Xd9r1yKMMkHllp5g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
]
|
||||
},
|
||||
"node_modules/esbuild-sunos-64": {
|
||||
"version": "0.13.15",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.13.15.tgz",
|
||||
"integrity": "sha512-lbivT9Bx3t1iWWrSnGyBP9ODriEvWDRiweAs69vI+miJoeKwHWOComSRukttbuzjZ8r1q0mQJ8Z7yUsDJ3hKdw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"sunos"
|
||||
]
|
||||
},
|
||||
"node_modules/esbuild-svelte": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-svelte/-/esbuild-svelte-0.6.3.tgz",
|
||||
"integrity": "sha512-WzDnkVeTwoyMPHHAqEkfy8aRkgK4YmpFcOOq9Cs6gdufPvH39K051mgmLSoqORqbjep7br4KXpDd0NUSSYFtKg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"svelte": "^3.38.3"
|
||||
"svelte": "^3.46.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"esbuild": ">=0.9.6"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-windows-32": {
|
||||
"version": "0.13.15",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.13.15.tgz",
|
||||
"integrity": "sha512-fDMEf2g3SsJ599MBr50cY5ve5lP1wyVwTe6aLJsM01KtxyKkB4UT+fc5MXQFn3RLrAIAZOG+tHC+yXObpSn7Nw==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/esbuild-windows-64": {
|
||||
"version": "0.13.15",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.13.15.tgz",
|
||||
"integrity": "sha512-9aMsPRGDWCd3bGjUIKG/ZOJPKsiztlxl/Q3C1XDswO6eNX/Jtwu4M+jb6YDH9hRSUflQWX0XKAfWzgy5Wk54JQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/esbuild-windows-arm64": {
|
||||
"version": "0.13.15",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.13.15.tgz",
|
||||
"integrity": "sha512-zzvyCVVpbwQQATaf3IG8mu1IwGEiDxKkYUdA4FpoCHi1KtPa13jeScYDjlW0Qh+ebWzpKfR2ZwvqAQkSWNcKjA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/fill-range": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
|
||||
@ -234,10 +496,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/svelte": {
|
||||
"version": "3.38.3",
|
||||
"resolved": "https://registry.npmjs.org/svelte/-/svelte-3.38.3.tgz",
|
||||
"integrity": "sha512-N7bBZJH0iF24wsalFZF+fVYMUOigaAUQMIcEKHO3jstK/iL8VmP9xE+P0/a76+FkNcWt+TDv2Gx1taUoUscrvw==",
|
||||
"version": "3.59.2",
|
||||
"resolved": "https://registry.npmjs.org/svelte/-/svelte-3.59.2.tgz",
|
||||
"integrity": "sha512-vzSyuGr3eEoAtT/A6bmajosJZIUWySzY2CzB3w2pgPvnkUjGqlDnsNnA0PMO+mMAhuyMul6C2uuZzY6ELSkzyA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
@ -316,20 +579,158 @@
|
||||
}
|
||||
},
|
||||
"esbuild": {
|
||||
"version": "0.12.15",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.12.15.tgz",
|
||||
"integrity": "sha512-72V4JNd2+48eOVCXx49xoSWHgC3/cCy96e7mbXKY+WOWghN00cCmlGnwVLRhRHorvv0dgCyuMYBZlM2xDM5OQw==",
|
||||
"dev": true
|
||||
},
|
||||
"esbuild-svelte": {
|
||||
"version": "0.5.3",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-svelte/-/esbuild-svelte-0.5.3.tgz",
|
||||
"integrity": "sha512-KByKD/yt8QaqKjLu32MG3MXBExJYlDM0QwzW3pzKLJR4eev0923DrUKRHPBBjB+OVirUtZnEJE/qitjdW/WyAw==",
|
||||
"version": "0.13.15",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.13.15.tgz",
|
||||
"integrity": "sha512-raCxt02HBKv8RJxE8vkTSCXGIyKHdEdGfUmiYb8wnabnaEmHzyW7DCHb5tEN0xU8ryqg5xw54mcwnYkC4x3AIw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"svelte": "^3.38.3"
|
||||
"esbuild-android-arm64": "0.13.15",
|
||||
"esbuild-darwin-64": "0.13.15",
|
||||
"esbuild-darwin-arm64": "0.13.15",
|
||||
"esbuild-freebsd-64": "0.13.15",
|
||||
"esbuild-freebsd-arm64": "0.13.15",
|
||||
"esbuild-linux-32": "0.13.15",
|
||||
"esbuild-linux-64": "0.13.15",
|
||||
"esbuild-linux-arm": "0.13.15",
|
||||
"esbuild-linux-arm64": "0.13.15",
|
||||
"esbuild-linux-mips64le": "0.13.15",
|
||||
"esbuild-linux-ppc64le": "0.13.15",
|
||||
"esbuild-netbsd-64": "0.13.15",
|
||||
"esbuild-openbsd-64": "0.13.15",
|
||||
"esbuild-sunos-64": "0.13.15",
|
||||
"esbuild-windows-32": "0.13.15",
|
||||
"esbuild-windows-64": "0.13.15",
|
||||
"esbuild-windows-arm64": "0.13.15"
|
||||
}
|
||||
},
|
||||
"esbuild-android-arm64": {
|
||||
"version": "0.13.15",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.13.15.tgz",
|
||||
"integrity": "sha512-m602nft/XXeO8YQPUDVoHfjyRVPdPgjyyXOxZ44MK/agewFFkPa8tUo6lAzSWh5Ui5PB4KR9UIFTSBKh/RrCmg==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-darwin-64": {
|
||||
"version": "0.13.15",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.13.15.tgz",
|
||||
"integrity": "sha512-ihOQRGs2yyp7t5bArCwnvn2Atr6X4axqPpEdCFPVp7iUj4cVSdisgvEKdNR7yH3JDjW6aQDw40iQFoTqejqxvQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-darwin-arm64": {
|
||||
"version": "0.13.15",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.13.15.tgz",
|
||||
"integrity": "sha512-i1FZssTVxUqNlJ6cBTj5YQj4imWy3m49RZRnHhLpefFIh0To05ow9DTrXROTE1urGTQCloFUXTX8QfGJy1P8dQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-freebsd-64": {
|
||||
"version": "0.13.15",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.13.15.tgz",
|
||||
"integrity": "sha512-G3dLBXUI6lC6Z09/x+WtXBXbOYQZ0E8TDBqvn7aMaOCzryJs8LyVXKY4CPnHFXZAbSwkCbqiPuSQ1+HhrNk7EA==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-freebsd-arm64": {
|
||||
"version": "0.13.15",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.13.15.tgz",
|
||||
"integrity": "sha512-KJx0fzEDf1uhNOZQStV4ujg30WlnwqUASaGSFPhznLM/bbheu9HhqZ6mJJZM32lkyfGJikw0jg7v3S0oAvtvQQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-linux-32": {
|
||||
"version": "0.13.15",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.13.15.tgz",
|
||||
"integrity": "sha512-ZvTBPk0YWCLMCXiFmD5EUtB30zIPvC5Itxz0mdTu/xZBbbHJftQgLWY49wEPSn2T/TxahYCRDWun5smRa0Tu+g==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-linux-64": {
|
||||
"version": "0.13.15",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.13.15.tgz",
|
||||
"integrity": "sha512-eCKzkNSLywNeQTRBxJRQ0jxRCl2YWdMB3+PkWFo2BBQYC5mISLIVIjThNtn6HUNqua1pnvgP5xX0nHbZbPj5oA==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-linux-arm": {
|
||||
"version": "0.13.15",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.13.15.tgz",
|
||||
"integrity": "sha512-wUHttDi/ol0tD8ZgUMDH8Ef7IbDX+/UsWJOXaAyTdkT7Yy9ZBqPg8bgB/Dn3CZ9SBpNieozrPRHm0BGww7W/jA==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-linux-arm64": {
|
||||
"version": "0.13.15",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.13.15.tgz",
|
||||
"integrity": "sha512-bYpuUlN6qYU9slzr/ltyLTR9YTBS7qUDymO8SV7kjeNext61OdmqFAzuVZom+OLW1HPHseBfJ/JfdSlx8oTUoA==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-linux-mips64le": {
|
||||
"version": "0.13.15",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.13.15.tgz",
|
||||
"integrity": "sha512-KlVjIG828uFPyJkO/8gKwy9RbXhCEUeFsCGOJBepUlpa7G8/SeZgncUEz/tOOUJTcWMTmFMtdd3GElGyAtbSWg==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-linux-ppc64le": {
|
||||
"version": "0.13.15",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.13.15.tgz",
|
||||
"integrity": "sha512-h6gYF+OsaqEuBjeesTBtUPw0bmiDu7eAeuc2OEH9S6mV9/jPhPdhOWzdeshb0BskRZxPhxPOjqZ+/OqLcxQwEQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-netbsd-64": {
|
||||
"version": "0.13.15",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.13.15.tgz",
|
||||
"integrity": "sha512-3+yE9emwoevLMyvu+iR3rsa+Xwhie7ZEHMGDQ6dkqP/ndFzRHkobHUKTe+NCApSqG5ce2z4rFu+NX/UHnxlh3w==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-openbsd-64": {
|
||||
"version": "0.13.15",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.13.15.tgz",
|
||||
"integrity": "sha512-wTfvtwYJYAFL1fSs8yHIdf5GEE4NkbtbXtjLWjM3Cw8mmQKqsg8kTiqJ9NJQe5NX/5Qlo7Xd9r1yKMMkHllp5g==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-sunos-64": {
|
||||
"version": "0.13.15",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.13.15.tgz",
|
||||
"integrity": "sha512-lbivT9Bx3t1iWWrSnGyBP9ODriEvWDRiweAs69vI+miJoeKwHWOComSRukttbuzjZ8r1q0mQJ8Z7yUsDJ3hKdw==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-svelte": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-svelte/-/esbuild-svelte-0.6.3.tgz",
|
||||
"integrity": "sha512-WzDnkVeTwoyMPHHAqEkfy8aRkgK4YmpFcOOq9Cs6gdufPvH39K051mgmLSoqORqbjep7br4KXpDd0NUSSYFtKg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"svelte": "^3.46.4"
|
||||
}
|
||||
},
|
||||
"esbuild-windows-32": {
|
||||
"version": "0.13.15",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.13.15.tgz",
|
||||
"integrity": "sha512-fDMEf2g3SsJ599MBr50cY5ve5lP1wyVwTe6aLJsM01KtxyKkB4UT+fc5MXQFn3RLrAIAZOG+tHC+yXObpSn7Nw==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-windows-64": {
|
||||
"version": "0.13.15",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.13.15.tgz",
|
||||
"integrity": "sha512-9aMsPRGDWCd3bGjUIKG/ZOJPKsiztlxl/Q3C1XDswO6eNX/Jtwu4M+jb6YDH9hRSUflQWX0XKAfWzgy5Wk54JQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-windows-arm64": {
|
||||
"version": "0.13.15",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.13.15.tgz",
|
||||
"integrity": "sha512-zzvyCVVpbwQQATaf3IG8mu1IwGEiDxKkYUdA4FpoCHi1KtPa13jeScYDjlW0Qh+ebWzpKfR2ZwvqAQkSWNcKjA==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"fill-range": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
|
||||
@ -430,9 +831,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"svelte": {
|
||||
"version": "3.38.3",
|
||||
"resolved": "https://registry.npmjs.org/svelte/-/svelte-3.38.3.tgz",
|
||||
"integrity": "sha512-N7bBZJH0iF24wsalFZF+fVYMUOigaAUQMIcEKHO3jstK/iL8VmP9xE+P0/a76+FkNcWt+TDv2Gx1taUoUscrvw==",
|
||||
"version": "3.59.2",
|
||||
"resolved": "https://registry.npmjs.org/svelte/-/svelte-3.59.2.tgz",
|
||||
"integrity": "sha512-vzSyuGr3eEoAtT/A6bmajosJZIUWySzY2CzB3w2pgPvnkUjGqlDnsNnA0PMO+mMAhuyMul6C2uuZzY6ELSkzyA==",
|
||||
"dev": true
|
||||
},
|
||||
"svelte-preprocess-filter": {
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"devDependencies": {
|
||||
"esbuild": "^0.12.15",
|
||||
"esbuild-svelte": "^0.5.3",
|
||||
"esbuild": "^0.13.5",
|
||||
"esbuild-svelte": "^0.6.3",
|
||||
"sass": "^1.68.0",
|
||||
"svelte-preprocess-sass": "^2.0.1"
|
||||
}
|
||||
|
40
clipfront2/src/About.svelte
Normal file
40
clipfront2/src/About.svelte
Normal file
@ -0,0 +1,40 @@
|
||||
<style lang="sass">
|
||||
@use 'common' as *
|
||||
|
||||
.about
|
||||
max-width: 40em
|
||||
padding-left: 1em
|
||||
padding-right: 1em
|
||||
</style>
|
||||
|
||||
<div class="about">
|
||||
<p>
|
||||
Welcome to {util.hardConfig.name} by <a href="https://osmarks.net/">osmarks.net Computational Memetics</a>. {util.hardConfig.name} searches images using semantic image/text embedding models. In general, search by thinking of what caption your desired image might have been given by random people on the internet. The model currently in use can read text fairly well and understands moderately abstract properties of images, but is limited to English and case-insensitive.
|
||||
</p>
|
||||
<p>
|
||||
Advanced Mode sliders are generated from PCA on the index. The human-readable labels are generated manually by <a href="https://datasets.osmarks.net/components.html">looking at things</a>.
|
||||
</p>
|
||||
<p>
|
||||
The code is open-source and available on <a href="https://github.com/osmarks/meme-search-engine/">GitHub.</a>
|
||||
</p>
|
||||
{#if util.hardConfig.telemetryEndpoint}
|
||||
<h2>Privacy</h2>
|
||||
<p>
|
||||
We do not collect personal information. We do collect usage information (associated with a random ID) to improve the ranking algorithms. You can disable this:
|
||||
</p>
|
||||
<div>
|
||||
<input type="checkbox" checked={$telemetryStore} on:change={saveTelemetryEnabled} id="telemetry" />
|
||||
<label for="telemetry">Allow usage statistics</label>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<script>
|
||||
import * as util from "./util"
|
||||
|
||||
const telemetryStore = util.telemetryEnabled
|
||||
|
||||
const saveTelemetryEnabled = ev => {
|
||||
telemetryStore.set(ev.target.checked)
|
||||
}
|
||||
</script>
|
@ -1,27 +1,30 @@
|
||||
<style lang="sass">
|
||||
@use 'common' as *
|
||||
@use 'sass:color'
|
||||
|
||||
\:global(*)
|
||||
box-sizing: border-box
|
||||
|
||||
\:global(html)
|
||||
scrollbar-color: black lightgray
|
||||
|
||||
|
||||
\:global(body)
|
||||
font-family: "Fira Sans", "Noto Sans", "Segoe UI", Verdana, sans-serif
|
||||
font-family: "Iosevka", "Shure Tech Mono", "IBM Plex Mono", monospace // TODO import iosevka
|
||||
font-weight: 300
|
||||
overflow-anchor: none
|
||||
//margin: 0
|
||||
//min-height: 100vh
|
||||
margin: 0
|
||||
|
||||
\:global(strong)
|
||||
font-weight: bold
|
||||
|
||||
@mixin header
|
||||
border-bottom: 1px solid gray
|
||||
border-bottom: 1px solid white
|
||||
margin: 0
|
||||
margin-bottom: 0.5em
|
||||
font-weight: 500
|
||||
//a
|
||||
//color: inherit
|
||||
|
||||
h1
|
||||
text-transform: uppercase
|
||||
|
||||
\:global(h1)
|
||||
@include header
|
||||
@ -40,14 +43,15 @@
|
||||
padding: 0
|
||||
padding-left: 1em
|
||||
|
||||
input, button, select
|
||||
\:global(input), :\global(button), :\global(select)
|
||||
border-radius: 0
|
||||
border: 1px solid gray
|
||||
padding: 0.5em
|
||||
font-family: inherit
|
||||
|
||||
.controls
|
||||
input[type=search]
|
||||
width: 80%
|
||||
width: 70%
|
||||
.ctrlbar
|
||||
> *
|
||||
margin: 0 -1px
|
||||
@ -63,117 +67,242 @@
|
||||
.sliders-ctrl
|
||||
width: 5em
|
||||
|
||||
.result
|
||||
border: 1px solid gray
|
||||
*
|
||||
display: block
|
||||
.result img, .result video
|
||||
width: 100%
|
||||
.enable-advanced-mode
|
||||
position: fixed
|
||||
top: 0.2em
|
||||
right: 0.2em
|
||||
font-size: 1.3em
|
||||
|
||||
nav
|
||||
background: $palette-secondary
|
||||
display: flex
|
||||
justify-content: space-between
|
||||
padding: 1em
|
||||
|
||||
.friendly
|
||||
h1
|
||||
border: none
|
||||
padding-top: 1em
|
||||
|
||||
input[type=search]
|
||||
width: 100%
|
||||
margin: 0
|
||||
padding: 0.5em
|
||||
font-size: 1.5em
|
||||
border-radius: 6px
|
||||
|
||||
.center
|
||||
margin-left: auto
|
||||
margin-right: auto
|
||||
max-width: 40em
|
||||
|
||||
.description
|
||||
opacity: 0.8
|
||||
margin-bottom: 1em
|
||||
font-weight: bold
|
||||
|
||||
button
|
||||
margin-left: 0.5em
|
||||
margin-right: 0.5em
|
||||
padding: 0.5em
|
||||
background: #9a0eea
|
||||
border-radius: 10px
|
||||
font-size: 1.5em
|
||||
color: white
|
||||
|
||||
.header
|
||||
padding-bottom: 2em
|
||||
|
||||
.header
|
||||
padding-left: 1em
|
||||
padding-right: 1em
|
||||
padding-top: 0.5em
|
||||
padding-bottom: 0.5em
|
||||
background: $palette-primary
|
||||
color: white
|
||||
|
||||
p
|
||||
font-weight: bold
|
||||
|
||||
.results
|
||||
padding-left: 1em
|
||||
padding-right: 1em
|
||||
|
||||
@media (prefers-color-scheme: dark)
|
||||
\:global(body)
|
||||
background-color: black
|
||||
color: white
|
||||
|
||||
\:global(input), :\global(button), :\global(select)
|
||||
border-radius: 0
|
||||
border: 1px solid gray
|
||||
padding: 0.5em
|
||||
font-family: inherit
|
||||
background: #222
|
||||
color: white
|
||||
|
||||
.logo
|
||||
height: 1em
|
||||
vertical-align: middle
|
||||
margin-bottom: 6px
|
||||
</style>
|
||||
|
||||
<h1>Meme Search Engine</h1>
|
||||
{#if config.n_total}
|
||||
<p>{config.n_total} items indexed.</p>
|
||||
{/if}
|
||||
<details>
|
||||
<summary>Usage tips</summary>
|
||||
<ul>
|
||||
<li>This uses CLIP-like image/text embedding models. In general, search by thinking of what caption your desired image might be given by random people on the internet.</li>
|
||||
<li>The model can read text, but not all of it.</li>
|
||||
<li>In certain circumstances, it may be useful to postfix your query with "meme".</li>
|
||||
<li>Capitalization is ignored.</li>
|
||||
<li>Only English is supported. Other languages might work slightly.</li>
|
||||
<li>Sliders are generated from PCA on the index. The human-readable labels are approximate.</li>
|
||||
<li>Want your own deployment? Use the open-source code on <a href="https://github.com/osmarks/meme-search-engine/">GitHub.</a>.</li>
|
||||
</ul>
|
||||
</details>
|
||||
<div class="controls">
|
||||
<ul>
|
||||
{#each queryTerms as term}
|
||||
<li>
|
||||
<button on:click={removeTerm(term)}>Remove</button>
|
||||
<select bind:value={term.sign}>
|
||||
<option>+</option>
|
||||
<option>-</option>
|
||||
</select>
|
||||
<input type="range" min="0" max="2" bind:value={term.weight} step="0.01">
|
||||
{#if term.type === "image"}
|
||||
<span>{term.file.name}</span>
|
||||
{:else if term.type === "text"}
|
||||
<input type="search" use:focusEl on:keydown={handleKey} bind:value={term.text} />
|
||||
{/if}
|
||||
{#if term.type === "embedding"}
|
||||
<span>[embedding loaded from URL]</span>
|
||||
{/if}
|
||||
{#if term.type === "predefined_embedding"}
|
||||
<span>{term.sign === "-" ? invertEmbeddingDesc(term.predefinedEmbedding) : term.predefinedEmbedding}</span>
|
||||
{/if}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
<div class="ctrlbar">
|
||||
<input type="search" placeholder="Text Query" on:keydown={handleKey} on:focus={newTextQuery}>
|
||||
<button on:click={pickFile}>Image Query</button>
|
||||
<select bind:value={predefinedEmbeddingName} on:change={setPredefinedEmbedding} class="sliders-ctrl">
|
||||
<option>Sliders</option>
|
||||
{#each config.predefined_embedding_names ?? [] as name}
|
||||
<option>{name}</option>
|
||||
{/each}
|
||||
</select>
|
||||
<button on:click={runSearch} style="margin-left: auto">Search</button>
|
||||
<nav>
|
||||
<div class="left">
|
||||
<NavItem page="search">Search</NavItem>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right">
|
||||
<NavItem page="advanced">Advanced</NavItem>
|
||||
<NavItem page="about">About</NavItem>
|
||||
<NavItem page="refine">Refine</NavItem>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
{#if error}
|
||||
<div>{error}</div>
|
||||
{/if}
|
||||
{#if resultPromise}
|
||||
<Loading />
|
||||
{/if}
|
||||
{#if results}
|
||||
{#if displayedResults.length === 0}
|
||||
No results. Wait for index rebuild.
|
||||
{/if}
|
||||
<Masonry bind:refreshLayout={refreshLayout} colWidth="minmax(Min(20em, 100%), 1fr)" items={displayedResults}>
|
||||
{#each displayedResults as result}
|
||||
{#key `${queryCounter}${result.file}`}
|
||||
<div class="result" style={aspectRatio(result)}>
|
||||
<a href={util.getURL(result)}>
|
||||
{#if util.hasFormat(results, result, "VIDEO")}
|
||||
<video controls poster={util.hasFormat(results, result, "jpegh") ? util.thumbnailURL(results, result, "jpegh") : null} preload="metadata" on:loadstart={updateCounter} on:loadedmetadata={redrawGrid} on:loadeddata={redrawGrid}>
|
||||
<source src={util.getURL(result)} />
|
||||
</video>
|
||||
{:else}
|
||||
<picture>
|
||||
{#if util.hasFormat(results, result, "avifl")}
|
||||
<source srcset={util.thumbnailURL(results, result, "avifl") + (util.hasFormat(results, result, "avifh") ? ", " + util.thumbnailURL(results, result, "avifh") + " 2x" : "")} type="image/avif" />
|
||||
{/if}
|
||||
{#if util.hasFormat(results, result, "jpegl")}
|
||||
<source srcset={util.thumbnailURL(results, result, "jpegl") + (util.hasFormat(results, result, "jpegh") ? ", " + util.thumbnailURL(results, result, "jpegh") + " 2x" : "")} type="image/jpeg" />
|
||||
{/if}
|
||||
<img src={util.getURL(result)} on:load={updateCounter} on:error={updateCounter} alt={result[1]}>
|
||||
</picture>
|
||||
{#if page === "search" || page === "advanced"}
|
||||
<div class={"container" + (friendlyMode ? " friendly" : " advanced")}>
|
||||
<div class="header">
|
||||
{#if friendlyMode}
|
||||
<div>
|
||||
<div class="center">
|
||||
<h1 class="logo-container"><img class="logo" src="./logo.png"> {util.hardConfig.name}</h1>
|
||||
<div class="description">{util.hardConfig.description}</div>
|
||||
<input type="search" placeholder="🔎 Search Memes" on:keydown={handleKey} autofocus bind:value={friendlyModeQuery} />
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<h1>{util.hardConfig.name}</h1>
|
||||
<p>
|
||||
{#if config.n_total}
|
||||
{config.n_total} items indexed.
|
||||
{/if}
|
||||
</p>
|
||||
<div class="controls">
|
||||
<ul>
|
||||
{#each queryTerms as term}
|
||||
<li>
|
||||
<button on:click={removeTerm(term)}>Remove</button>
|
||||
<select bind:value={term.sign}>
|
||||
<option>+</option>
|
||||
<option>-</option>
|
||||
</select>
|
||||
<input type="range" min="0" max="2" bind:value={term.weight} step="0.01">
|
||||
{#if term.type === "image"}
|
||||
<span>{term.file.name}</span>
|
||||
{:else if term.type === "text"}
|
||||
<input type="search" use:focusEl on:keydown={handleKey} bind:value={term.text} />
|
||||
{/if}
|
||||
</a>
|
||||
</div>
|
||||
{/key}
|
||||
{/each}
|
||||
</Masonry>
|
||||
{#if term.type === "embedding"}
|
||||
<span>[embedding loaded from URL]</span>
|
||||
{/if}
|
||||
{#if term.type === "predefined_embedding"}
|
||||
<span>{term.sign === "-" ? invertEmbeddingDesc(term.predefinedEmbedding) : term.predefinedEmbedding}</span>
|
||||
{/if}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
<div class="ctrlbar">
|
||||
<input type="search" placeholder="Text Query" on:keydown={handleKey} on:focus={newTextQuery}>
|
||||
<button on:click={pickFile}>Image Query</button>
|
||||
<select bind:value={predefinedEmbeddingName} on:change={setPredefinedEmbedding} class="sliders-ctrl">
|
||||
<option>Sliders</option>
|
||||
{#each config.predefined_embedding_names ?? [] as name}
|
||||
<option>{name}</option>
|
||||
{/each}
|
||||
</select>
|
||||
<button on:click={runSearch} style="margin-left: auto">Search</button>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="results"><SearchResults {resultPromise} {results} {error} {friendlyMode} {queryCounter} /></div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<svelte:window on:resize={redrawGrid} on:scroll={handleScroll}></svelte:window>
|
||||
{#if page === "about"}
|
||||
<About />
|
||||
{/if}
|
||||
|
||||
{#if page === "refine"}
|
||||
<QueryRefiner {config} />
|
||||
{/if}
|
||||
|
||||
<script>
|
||||
import * as util from "./util"
|
||||
import Loading from "./Loading.svelte"
|
||||
import Masonry from "./Masonry.svelte"
|
||||
import SearchResults from "./SearchResults.svelte"
|
||||
import QueryRefiner from "./QueryRefiner.svelte"
|
||||
import NavItem from "./NavItem.svelte"
|
||||
import About from "./About.svelte"
|
||||
|
||||
const chunkSize = 40
|
||||
document.title = util.hardConfig.name
|
||||
|
||||
let page = "search"
|
||||
let friendlyModeQuery = ""
|
||||
let queryTerms = []
|
||||
let queryCounter = 0
|
||||
|
||||
let config = {}
|
||||
|
||||
const newTextQuery = (content=null) => {
|
||||
queryTerms.push({ type: "text", weight: 1, sign: "+", text: typeof content === "string" ? content : "" })
|
||||
queryTerms = queryTerms
|
||||
}
|
||||
|
||||
let resultPromise
|
||||
let results
|
||||
let error
|
||||
|
||||
const runSearch = async () => {
|
||||
if (!resultPromise) {
|
||||
let args = {
|
||||
"terms": friendlyMode ? [{ text: friendlyModeQuery, weight: 1, sign: "+" }] : queryTerms.filter(x => x.text !== "").map(x => ({ image: x.imageData, text: x.text, embedding: x.embedding, predefined_embedding: x.predefinedEmbedding, weight: x.weight * { "+": 1, "-": -1 }[x.sign] })),
|
||||
"include_video": true
|
||||
}
|
||||
|
||||
util.sendTelemetry("search", {
|
||||
terms: args.terms.map(x => {
|
||||
if (x.image) {
|
||||
return { type: "image" }
|
||||
} else if (x.text) {
|
||||
return { type: "text", text: x.text }
|
||||
} else if (x.embedding) {
|
||||
return { type: "embedding" }
|
||||
} else if (x.predefined_embedding) {
|
||||
return { type: "predefined_embedding", embedding: x.predefined_embedding }
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
queryCounter += 1
|
||||
resultPromise = util.doQuery(args).then(res => {
|
||||
error = null
|
||||
results = res
|
||||
resultPromise = null
|
||||
}).catch(e => { error = e; resultPromise = null; console.log("error", e) })
|
||||
}
|
||||
}
|
||||
|
||||
const parseQueryString = queryStringParams => {
|
||||
if (queryStringParams.get("q") && queryTerms.length === 0) {
|
||||
newTextQuery(queryStringParams.get("q"))
|
||||
friendlyModeQuery = queryStringParams.get("q")
|
||||
runSearch()
|
||||
}
|
||||
if (queryStringParams.get("e") && queryTerms.length === 0) {
|
||||
const binaryData = atob(queryStringParams.get("e").replace(/\-/g, "+").replace(/_/g, "/"))
|
||||
const uint16s = new Uint16Array(new Uint8Array(binaryData.split('').map(c => c.charCodeAt(0))).buffer)
|
||||
queryTerms.push({ type: "embedding", weight: 1, sign: "+", embedding: Array.from(uint16s).map(decodeFloat16) })
|
||||
friendlyMode = false
|
||||
runSearch()
|
||||
}
|
||||
if (queryStringParams.get("page")) {
|
||||
page = queryStringParams.get("page")
|
||||
}
|
||||
}
|
||||
|
||||
util.router.subscribe(parseQueryString)
|
||||
|
||||
$: friendlyMode = page === "search"
|
||||
|
||||
util.serverConfig.subscribe(x => {
|
||||
config = x
|
||||
})
|
||||
@ -198,8 +327,6 @@
|
||||
return `${snd}/${fst}`
|
||||
}
|
||||
|
||||
const aspectRatio = result => result[4] ? `aspect-ratio: ${result[4][0]}/${result[4][1]}` : null
|
||||
|
||||
const decodeFloat16 = uint16 => {
|
||||
const sign = (uint16 & 0x8000) ? -1 : 1
|
||||
const exponent = (uint16 & 0x7C00) >> 10
|
||||
@ -215,75 +342,10 @@
|
||||
}
|
||||
|
||||
const focusEl = el => el.focus()
|
||||
const newTextQuery = (content=null) => {
|
||||
queryTerms.push({ type: "text", weight: 1, sign: "+", text: typeof content === "string" ? content : "" })
|
||||
queryTerms = queryTerms
|
||||
}
|
||||
const removeTerm = term => {
|
||||
queryTerms = queryTerms.filter(x => x !== term)
|
||||
}
|
||||
|
||||
let refreshLayout
|
||||
let heightThreshold
|
||||
let error
|
||||
let pendingImageLoads
|
||||
const recomputeScroll = () => {
|
||||
const maxOffsets = new Map()
|
||||
for (const el of document.querySelectorAll(".result")) {
|
||||
if (el.getAttribute("data-h")) { // layouted
|
||||
const rect = el.getBoundingClientRect()
|
||||
maxOffsets.set(rect.left, Math.max(maxOffsets.get(rect.left) || 0, rect.top))
|
||||
}
|
||||
}
|
||||
heightThreshold = Math.min(...maxOffsets.values())
|
||||
console.log(heightThreshold, pendingImageLoads)
|
||||
}
|
||||
const redrawGrid = async () => {
|
||||
if (refreshLayout) {
|
||||
refreshLayout()
|
||||
await recomputeScroll()
|
||||
}
|
||||
}
|
||||
let resultPromise
|
||||
let results
|
||||
let displayedResults = []
|
||||
const runSearch = async () => {
|
||||
if (!resultPromise) {
|
||||
let args = {
|
||||
"terms": queryTerms.filter(x => x.text !== "").map(x => ({ image: x.imageData, text: x.text, embedding: x.embedding, predefined_embedding: x.predefinedEmbedding, weight: x.weight * { "+": 1, "-": -1 }[x.sign] })),
|
||||
"include_video": true
|
||||
}
|
||||
queryCounter += 1
|
||||
resultPromise = util.doQuery(args).then(res => {
|
||||
error = null
|
||||
results = res
|
||||
resultPromise = null
|
||||
displayedResults = []
|
||||
pendingImageLoads = 0
|
||||
for (let i = 0; i < chunkSize; i++) {
|
||||
if (i >= results.matches.length) break
|
||||
displayedResults.push(results.matches[i])
|
||||
pendingImageLoads += 1
|
||||
}
|
||||
redrawGrid()
|
||||
}).catch(e => { error = e; resultPromise = null })
|
||||
}
|
||||
}
|
||||
|
||||
const handleScroll = () => {
|
||||
if (window.scrollY + window.innerHeight >= heightThreshold && pendingImageLoads === 0) {
|
||||
recomputeScroll()
|
||||
if (window.scrollY + window.innerHeight < heightThreshold) return;
|
||||
let init = displayedResults.length
|
||||
for (let i = 0; i < chunkSize; i++) {
|
||||
if (init + i >= results.matches.length) break
|
||||
displayedResults.push(results.matches[init + i])
|
||||
pendingImageLoads += 1
|
||||
}
|
||||
displayedResults = displayedResults
|
||||
}
|
||||
}
|
||||
|
||||
const handleKey = ev => {
|
||||
if (ev.key === "Enter") {
|
||||
runSearch()
|
||||
@ -295,7 +357,6 @@
|
||||
const pickFile = () => {
|
||||
input.oninput = ev => {
|
||||
currentFile = ev.target.files[0]
|
||||
console.log(currentFile)
|
||||
if (currentFile) {
|
||||
let reader = new FileReader()
|
||||
reader.readAsDataURL(currentFile)
|
||||
@ -309,22 +370,4 @@
|
||||
}
|
||||
input.click()
|
||||
}
|
||||
|
||||
const updateCounter = () => {
|
||||
console.log("redraw")
|
||||
pendingImageLoads -= 1
|
||||
redrawGrid()
|
||||
}
|
||||
|
||||
const queryStringParams = new URLSearchParams(window.location.search)
|
||||
if (queryStringParams.get("q")) {
|
||||
newTextQuery(queryStringParams.get("q"))
|
||||
runSearch()
|
||||
}
|
||||
if (queryStringParams.get("e")) {
|
||||
const binaryData = atob(queryStringParams.get("e").replace(/\-/g, "+").replace(/_/g, "/"))
|
||||
const uint16s = new Uint16Array(new Uint8Array(binaryData.split('').map(c => c.charCodeAt(0))).buffer)
|
||||
queryTerms.push({ type: "embedding", weight: 1, sign: "+", embedding: Array.from(uint16s).map(decodeFloat16) })
|
||||
runSearch()
|
||||
}
|
||||
</script>
|
||||
|
@ -1,31 +1,33 @@
|
||||
<style lang="sass">
|
||||
.spinner
|
||||
color: black
|
||||
|
||||
color: inherit
|
||||
text-align: center
|
||||
padding-top: 0.5em
|
||||
|
||||
.spinner:before
|
||||
animation: textSpinner 0.8s linear infinite
|
||||
content: "⠋"
|
||||
margin-right: 0.5em
|
||||
padding-top: 0.5em
|
||||
|
||||
|
||||
@keyframes textSpinner
|
||||
10%
|
||||
10%
|
||||
content: "⠙"
|
||||
20%
|
||||
20%
|
||||
content: "⠹"
|
||||
30%
|
||||
30%
|
||||
content: "⠸"
|
||||
40%
|
||||
40%
|
||||
content: "⠼"
|
||||
50%
|
||||
50%
|
||||
content: "⠴"
|
||||
60%
|
||||
60%
|
||||
content: "⠦"
|
||||
70%
|
||||
70%
|
||||
content: "⠧"
|
||||
80%
|
||||
80%
|
||||
content: "⠇"
|
||||
90%
|
||||
90%
|
||||
content: "⠏"
|
||||
</style>
|
||||
|
||||
@ -33,4 +35,4 @@
|
||||
export let operation = "Loading"
|
||||
</script>
|
||||
|
||||
<span class="spinner">{operation}</span>
|
||||
<div class="spinner">{operation}</div>
|
||||
|
@ -86,7 +86,7 @@ const calcGrid = async (_masonryArr) => {
|
||||
}
|
||||
|
||||
|
||||
$: if(masonryElement) {
|
||||
$: if(masonryElement && gridGap) {
|
||||
calcGrid([masonryElement])
|
||||
}
|
||||
|
||||
@ -106,9 +106,7 @@ $: if(items) { // update if items are changed
|
||||
grid-template-columns: repeat(auto-fit, var(--col-width));
|
||||
grid-template-rows: masonry;
|
||||
justify-content: center;
|
||||
grid-gap: var(--grid-gap);
|
||||
padding: var(--grid-gap);
|
||||
|
||||
grid-gap: var(--grid-gap);
|
||||
}
|
||||
:global(.__grid--masonry > *) {
|
||||
align-self: start
|
||||
|
25
clipfront2/src/NavItem.svelte
Normal file
25
clipfront2/src/NavItem.svelte
Normal file
@ -0,0 +1,25 @@
|
||||
<style lang="sass">
|
||||
@use 'common' as *
|
||||
|
||||
a
|
||||
text-decoration: none
|
||||
font-size: 1.5em
|
||||
display: inline-block
|
||||
padding-right: 0.25em
|
||||
padding-left: 0.25em
|
||||
color: white
|
||||
|
||||
.active
|
||||
font-style: italic
|
||||
</style>
|
||||
|
||||
<a href={util.router.urlForPage(page)} class={$currentPage === page ? "active" : ""} on:click={util.router.handleClick}><slot /></a>
|
||||
|
||||
<script>
|
||||
import * as util from "./util"
|
||||
import { derived } from "svelte/store"
|
||||
|
||||
let currentPage = derived(util.router, x => x.get("page"))
|
||||
|
||||
export let page
|
||||
</script>
|
78
clipfront2/src/QueryRefiner.svelte
Normal file
78
clipfront2/src/QueryRefiner.svelte
Normal file
@ -0,0 +1,78 @@
|
||||
<style lang="sass">
|
||||
.candidate-images
|
||||
height: 15vh
|
||||
display: flex
|
||||
|
||||
.candidate
|
||||
margin-top: 1em
|
||||
.candidate-images
|
||||
margin-top: 1em
|
||||
</style>
|
||||
|
||||
<div>
|
||||
<div>
|
||||
{#each candidates as candidate}
|
||||
<div class="candidate">
|
||||
<button on:click={select(candidate)}>Select {candidate.i + 1}</button>
|
||||
<div class="candidate-images">
|
||||
{#if candidate.results}
|
||||
{#each candidate.results.matches as result}
|
||||
<ResultImage {result} results={candidate.results} updateCounter={null} redrawGrid={null} constrainBy="height" />
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<svelte:window on:keydown={handleKey} />
|
||||
|
||||
<script>
|
||||
import * as util from "./util"
|
||||
import ResultImage from "./ResultImage.svelte"
|
||||
|
||||
export let config
|
||||
|
||||
const d_emb = 1152
|
||||
|
||||
const vecSum = (xs, ys) => xs.map((x, i) => x + ys[i])
|
||||
const vecZero = d => new Array(d).fill(0)
|
||||
const vecScale = (xs, s) => xs.map(x => x * s)
|
||||
|
||||
const boxMuller = () => {
|
||||
let x = Math.random()
|
||||
let y = Math.random()
|
||||
return Math.sqrt(-2.0 * Math.log(x)) * Math.cos(2.0 * Math.PI * y)
|
||||
}
|
||||
|
||||
const randn = (d, sigma) => Array.from({ length: d }, () => boxMuller() * sigma)
|
||||
|
||||
const K = 2
|
||||
let candidates = []
|
||||
|
||||
const select = async candidate => {
|
||||
candidates = []
|
||||
const direction = randn(d_emb, 1 / d_emb)
|
||||
for (let i = -K; i <= K; i++) {
|
||||
const newV = vecSum(vecScale(direction, i / K), candidate.vector)
|
||||
candidates.push({ vector: newV, results: null, i: i + K })
|
||||
}
|
||||
await Promise.all(candidates.map(async x => {
|
||||
const queryResult = await util.doQuery({ terms: [{ embedding: x.vector, weight: 1, sign: "+" }], include_video: false, k: 100 })
|
||||
x.results = queryResult
|
||||
x.results.matches = x.results.matches.slice(0, 10)
|
||||
}))
|
||||
candidates = candidates
|
||||
console.log(candidates)
|
||||
}
|
||||
|
||||
select({ vector: randn(d_emb, 1 / d_emb) })
|
||||
|
||||
const handleKey = ev => {
|
||||
const num = parseInt(ev.key)
|
||||
if (num && num >= 1 && num <= (2 * K + 1)) {
|
||||
select(candidates[num - 1])
|
||||
}
|
||||
}
|
||||
</script>
|
53
clipfront2/src/ResultImage.svelte
Normal file
53
clipfront2/src/ResultImage.svelte
Normal file
@ -0,0 +1,53 @@
|
||||
<style lang="sass">
|
||||
.ch
|
||||
height: 100%
|
||||
img, video, picture, a
|
||||
height: 100%
|
||||
|
||||
.cw
|
||||
width: 100%
|
||||
img, video, picture, a
|
||||
width: 100%
|
||||
|
||||
*
|
||||
display: block
|
||||
</style>
|
||||
|
||||
<div style={aspectRatio(result)} class={constrainBy === "width" ? " cw" : "ch"}>
|
||||
<a href={util.getURL(result)} on:click={() => interact("click")} on:mousedown={() => interact("mousedown")}>
|
||||
{#if util.hasFormat(results, result, "VIDEO")}
|
||||
<video controls poster={util.hasFormat(results, result, "jpegh") ? util.thumbnailURL(results, result, "jpegh") : null} preload="metadata" on:loadstart={updateCounter} on:loadedmetadata={redrawGrid} on:loadeddata={redrawGrid}>
|
||||
<source src={util.getURL(result)} />
|
||||
</video>
|
||||
{:else}
|
||||
<picture>
|
||||
{#if util.hasFormat(results, result, "avifl")}
|
||||
<source srcset={util.thumbnailURL(results, result, "avifl") + (util.hasFormat(results, result, "avifh") ? ", " + util.thumbnailURL(results, result, "avifh") + " 2x" : "")} type="image/avif" />
|
||||
{/if}
|
||||
{#if util.hasFormat(results, result, "jpegl")}
|
||||
<source srcset={util.thumbnailURL(results, result, "jpegl") + (util.hasFormat(results, result, "jpegh") ? ", " + util.thumbnailURL(results, result, "jpegh") + " 2x" : "")} type="image/jpeg" />
|
||||
{/if}
|
||||
<img src={util.getURL(result)} on:load={updateCounter} on:error={updateCounter} alt={result[1]}>
|
||||
</picture>
|
||||
{/if}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
import * as util from "./util"
|
||||
|
||||
export let result
|
||||
export let results
|
||||
export let updateCounter
|
||||
export let redrawGrid
|
||||
export let constrainBy = "width"
|
||||
|
||||
const interact = type => {
|
||||
util.sendTelemetry("interact", {
|
||||
type,
|
||||
result: result[1]
|
||||
})
|
||||
}
|
||||
|
||||
const aspectRatio = result => result[4] ? `aspect-ratio: ${result[4][0]}/${result[4][1]}` : null
|
||||
</script>
|
124
clipfront2/src/SearchResults.svelte
Normal file
124
clipfront2/src/SearchResults.svelte
Normal file
@ -0,0 +1,124 @@
|
||||
<style lang="sass">
|
||||
@use 'common' as *
|
||||
|
||||
.result
|
||||
border: 1px solid $palette-primary
|
||||
overflow: hidden
|
||||
|
||||
.result img, .result video
|
||||
width: 100%
|
||||
|
||||
.advanced
|
||||
margin-top: 1em
|
||||
|
||||
.friendly
|
||||
padding-top: 1em
|
||||
</style>
|
||||
|
||||
{#if error}
|
||||
<div class="error">{error}</div>
|
||||
{/if}
|
||||
{#if resultPromise}
|
||||
<Loading />
|
||||
{/if}
|
||||
{#if results}
|
||||
<div class={friendlyMode ? "friendly" : "advanced"}>
|
||||
<Masonry bind:refreshLayout={refreshLayout} colWidth={`minmax(Min(${friendlyMode ? "30em" : "20em"}, 100%), 1fr)`} items={displayedResults} gridGap={friendlyMode ? "1em" : "0.5em"}>
|
||||
{#each displayedResults as result}
|
||||
{#key `${queryCounter}${result.file}`}
|
||||
<div class="result">
|
||||
<ResultImage {result} {results} {updateCounter} {redrawGrid} constrainBy="width" />
|
||||
</div>
|
||||
{/key}
|
||||
{/each}
|
||||
</Masonry>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<svelte:window on:resize={redrawGrid} on:scroll={handleScroll}></svelte:window>
|
||||
|
||||
<script>
|
||||
import { tick } from "svelte"
|
||||
|
||||
import Loading from "./Loading.svelte"
|
||||
import Masonry from "./Masonry.svelte"
|
||||
import ResultImage from "./ResultImage.svelte"
|
||||
import * as util from "./util.js"
|
||||
|
||||
let refreshLayout
|
||||
|
||||
export let results
|
||||
export let resultPromise
|
||||
export let error
|
||||
export let friendlyMode
|
||||
export let queryCounter
|
||||
|
||||
const chunkSize = 40
|
||||
|
||||
let displayedResults = []
|
||||
|
||||
let heightThreshold
|
||||
let pendingImageLoads
|
||||
const recomputeScroll = () => {
|
||||
const maxOffsets = new Map()
|
||||
for (const el of document.querySelectorAll(".result")) {
|
||||
if (el.getAttribute("data-h")) { // layouted
|
||||
const rect = el.getBoundingClientRect()
|
||||
maxOffsets.set(rect.left, Math.max(maxOffsets.get(rect.left) || 0, rect.top))
|
||||
}
|
||||
}
|
||||
heightThreshold = Math.min(...maxOffsets.values())
|
||||
}
|
||||
export const redrawGrid = async () => {
|
||||
if (refreshLayout) {
|
||||
refreshLayout()
|
||||
await recomputeScroll()
|
||||
}
|
||||
}
|
||||
|
||||
const updateCounter = () => {
|
||||
pendingImageLoads -= 1
|
||||
redrawGrid()
|
||||
}
|
||||
|
||||
const handleScroll = () => {
|
||||
if (window.scrollY + window.innerHeight >= heightThreshold && pendingImageLoads === 0) {
|
||||
recomputeScroll()
|
||||
if (window.scrollY + window.innerHeight < heightThreshold) return;
|
||||
let init = displayedResults.length
|
||||
for (let i = 0; i < chunkSize; i++) {
|
||||
if (init + i >= results.matches.length) break
|
||||
displayedResults.push(results.matches[init + i])
|
||||
pendingImageLoads += 1
|
||||
}
|
||||
if (init !== displayedResults.length) {
|
||||
util.sendTelemetry("scroll", {
|
||||
results: results.matches.length,
|
||||
displayed: displayedResults.length
|
||||
})
|
||||
displayedResults = displayedResults
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let lastResults
|
||||
|
||||
$: {
|
||||
if (results && results !== lastResults) {
|
||||
displayedResults = []
|
||||
pendingImageLoads = 0
|
||||
for (let i = 0; i < chunkSize; i++) {
|
||||
if (i >= results.matches.length) break
|
||||
displayedResults.push(results.matches[i])
|
||||
pendingImageLoads += 1
|
||||
}
|
||||
redrawGrid()
|
||||
lastResults = results
|
||||
}
|
||||
}
|
||||
|
||||
$: {
|
||||
let _ = friendlyMode
|
||||
tick().then(() => redrawGrid())
|
||||
}
|
||||
</script>
|
@ -2,4 +2,4 @@ import App from "./App.svelte"
|
||||
|
||||
new App({
|
||||
target: document.body,
|
||||
})
|
||||
})
|
||||
|
@ -7,7 +7,7 @@ esbuild
|
||||
.build({
|
||||
entryPoints: [path.join(__dirname, "app.js")],
|
||||
bundle: true,
|
||||
minify: true,
|
||||
minify: false,
|
||||
outfile: path.join(__dirname, "../static/app.js"),
|
||||
plugins: [sveltePlugin({
|
||||
preprocess: {
|
||||
@ -17,7 +17,8 @@ esbuild
|
||||
loader: {
|
||||
".woff": "file",
|
||||
".woff2": "file",
|
||||
".ttf": "file"
|
||||
".ttf": "file",
|
||||
".png": "file"
|
||||
},
|
||||
logLevel: "info",
|
||||
watch: process.argv.join(" ").includes("watch")
|
||||
|
4
clipfront2/src/common.sass
Normal file
4
clipfront2/src/common.sass
Normal file
@ -0,0 +1,4 @@
|
||||
@use 'sass:color'
|
||||
|
||||
$palette-primary: #3f9b0b
|
||||
$palette-secondary: #033500
|
@ -1,26 +1,80 @@
|
||||
import * as config from "../../frontend_config.json"
|
||||
import { writable } from "svelte/store"
|
||||
import { writable, get } from "svelte/store"
|
||||
|
||||
export const getURL = x => config.image_path + x[1]
|
||||
|
||||
export const doQuery = args => fetch(config.backend_url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify(args)
|
||||
}).then(x => x.json())
|
||||
export const hardConfig = config
|
||||
|
||||
export const router = writable(new URLSearchParams(window.location.search))
|
||||
|
||||
window.addEventListener("popstate", ev => {
|
||||
router.set(new URLSearchParams(window.location.search))
|
||||
})
|
||||
|
||||
router.handleClick = ev => {
|
||||
history.pushState({}, "", ev.target.getAttribute("href"))
|
||||
ev.preventDefault()
|
||||
router.set(new URLSearchParams(window.location.search))
|
||||
}
|
||||
|
||||
router.urlForPage = page => {
|
||||
let queryStringParams = new URLSearchParams(window.location.search)
|
||||
queryStringParams.set("page", page)
|
||||
return window.location.origin + "?" + queryStringParams.toString()
|
||||
}
|
||||
|
||||
export const telemetryEnabled = writable(true)
|
||||
if (localStorage.telemetryEnabled === "false") {
|
||||
telemetryEnabled.set(false)
|
||||
}
|
||||
telemetryEnabled.subscribe(x => {
|
||||
localStorage.telemetryEnabled = x ? "true" : "false"
|
||||
})
|
||||
|
||||
const randomString = () => Math.random().toString(36).substring(2, 15)
|
||||
|
||||
localStorage.correlationId = localStorage.correlationId ?? randomString()
|
||||
let correlationId = localStorage.correlationId
|
||||
let instanceId = randomString()
|
||||
|
||||
export const sendTelemetry = async (event, data) => {
|
||||
if (!get(telemetryEnabled)) return
|
||||
if (!config.telemetry_endpoint) return
|
||||
navigator.sendBeacon(config.telemetry_endpoint, JSON.stringify({
|
||||
correlationId,
|
||||
instanceId,
|
||||
event,
|
||||
data,
|
||||
page: get(router).get("page")
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
export const doQuery = async args => {
|
||||
const res = await fetch(config.backend_url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify(args)
|
||||
})
|
||||
try {
|
||||
return await res.clone().json()
|
||||
} catch(e) {
|
||||
throw new Error(res.status + " " + await res.text())
|
||||
}
|
||||
}
|
||||
|
||||
export const hasFormat = (results, result, format) => {
|
||||
return (results.formats.indexOf(format) != -1) && ((result[3] & (1 << results.formats.indexOf(format))) != 0)
|
||||
}
|
||||
|
||||
export const thumbnailURL = (results, result, format) => {
|
||||
return `${config.thumb_path}${result[2]}${format}.${results.extensions[format]}`
|
||||
return `${config.thumb_path}${result[2]}${format}.${results.extensions[format]}`
|
||||
}
|
||||
|
||||
export let serverConfig = writable({})
|
||||
fetch(config.backend_url).then(x => x.json().then(x => {
|
||||
serverConfig.set(x)
|
||||
window.serverConfig = x
|
||||
}))
|
||||
}))
|
||||
|
@ -3,11 +3,12 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes">
|
||||
<meta name="description" content="osmarks.net meme library semantic search via CLIP; enhanced query UI edition">
|
||||
<meta name="description" content="Organizing the world's memes.">
|
||||
|
||||
<title>Meme Search Engine</title>
|
||||
</style>
|
||||
<link rel="stylesheet" href="app.css">
|
||||
<link rel="icon" type="image/png" href="logo.png">
|
||||
</head>
|
||||
<body>
|
||||
<script src="app.js"></script>
|
||||
|
BIN
clipfront2/static/logo.png
Normal file
BIN
clipfront2/static/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 22 KiB |
@ -1,5 +1,8 @@
|
||||
{
|
||||
"backend_url": "http://localhost:5601/",
|
||||
"image_path": "",
|
||||
"thumb_path": null
|
||||
"thumb_path": null,
|
||||
"description": "Organizing the world's memes.",
|
||||
"name": "Nooscope",
|
||||
"telemetry_endpoint": "/telemetry"
|
||||
}
|
||||
|
@ -8,12 +8,17 @@ import time
|
||||
import sys
|
||||
|
||||
async def fetch_list_seg(sess, list_url, query):
|
||||
async with sess.get(list_url + ".json", params=query) as res:
|
||||
if rate_limit := res.headers.get("x-ratelimit-remaining"):
|
||||
rl = float(rate_limit)
|
||||
if rl <= 5.0:
|
||||
await asyncio.sleep(float(res.headers["x-ratelimit-reset"]))
|
||||
return await res.json()
|
||||
while True:
|
||||
try:
|
||||
async with sess.get(list_url + ".json", params=query) as res:
|
||||
if rate_limit := res.headers.get("x-ratelimit-remaining"):
|
||||
rl = float(rate_limit)
|
||||
if rl <= 5.0:
|
||||
await asyncio.sleep(float(res.headers["x-ratelimit-reset"]))
|
||||
return await res.json()
|
||||
except asyncio.TimeoutError:
|
||||
await asyncio.sleep(1)
|
||||
print("timeout")
|
||||
|
||||
async def fetch_past(sess, list_url, n):
|
||||
after = None
|
||||
|
@ -2,7 +2,7 @@
|
||||
"clip_server": "http://100.64.0.10:1708",
|
||||
"db_path": "data.sqlite3",
|
||||
"port": 1707,
|
||||
"files": "/data/public/memes-or-something/",
|
||||
"files": "./mse-test-db-small",
|
||||
"enable_ocr": false,
|
||||
"thumbs_path": "./thumbtemp",
|
||||
"enable_thumbs": false
|
||||
|
@ -43,7 +43,7 @@ with torch.inference_mode():
|
||||
reconstructions = model(batch).float()
|
||||
|
||||
feature_frequencies = model.reset_counters()
|
||||
features = model.up_proj.weight.cpu().numpy()
|
||||
features = model.down_proj.weight.cpu().numpy()
|
||||
|
||||
meme_search_backend = "http://localhost:1707/"
|
||||
memes_url = "https://i.osmarks.net/memes-or-something/"
|
||||
@ -54,8 +54,8 @@ def emb_url(embedding):
|
||||
|
||||
async def get_exemplars():
|
||||
async with aiohttp.ClientSession():
|
||||
for base in tqdm(range(0, len(features), retrieve_batch_size)):
|
||||
chunk = features[base:base + retrieve_batch_size]
|
||||
for base in tqdm(range(0, features.shape[1], retrieve_batch_size)):
|
||||
chunk = features[:, base:base + retrieve_batch_size].T
|
||||
with open(f"feature_dumps/features{base}.html", "w") as f:
|
||||
f.write("""<!DOCTYPE html>
|
||||
<title>Embeddings SAE Features</title>
|
||||
@ -90,4 +90,4 @@ async def get_exemplars():
|
||||
f.write(f'<img loading="lazy" src="{memes_url+match[1]}">')
|
||||
f.write("</div>")
|
||||
|
||||
asyncio.run(get_exemplars())
|
||||
asyncio.run(get_exemplars())
|
||||
|
Loading…
x
Reference in New Issue
Block a user