Refine semantic search and CPU fallback
- make Candle CUDA support opt-in so default builds work on CPU-only machines - improve semantic search loading and empty states in the gallery - keep semantic search UX clearer when no matches are found Refs: #4
This commit is contained in:
@@ -12,6 +12,10 @@ crate-type = ["staticlib", "cdylib", "rlib"]
|
|||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
tauri-build = { version = "2", features = [] }
|
tauri-build = { version = "2", features = [] }
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = []
|
||||||
|
candle-cuda = ["candle-core/cuda", "candle-nn/cuda", "candle-transformers/cuda"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tauri = { version = "2", features = ["protocol-asset"] }
|
tauri = { version = "2", features = ["protocol-asset"] }
|
||||||
tauri-plugin-opener = "2"
|
tauri-plugin-opener = "2"
|
||||||
@@ -35,8 +39,8 @@ log = "0.4"
|
|||||||
ffmpeg-sidecar = "2.5.0"
|
ffmpeg-sidecar = "2.5.0"
|
||||||
xxhash-rust = { version = "0.8", features = ["xxh3"] }
|
xxhash-rust = { version = "0.8", features = ["xxh3"] }
|
||||||
sysinfo = "0.38.4"
|
sysinfo = "0.38.4"
|
||||||
candle-core = { version = "0.10.2", features = ["cuda"] }
|
candle-core = "0.10.2"
|
||||||
candle-nn = { version = "0.10.2", features = ["cuda"] }
|
candle-nn = "0.10.2"
|
||||||
candle-transformers = { version = "0.10.2", features = ["cuda"] }
|
candle-transformers = "0.10.2"
|
||||||
hf-hub = { version = "0.5.0", default-features = false, features = ["ureq", "native-tls"] }
|
hf-hub = { version = "0.5.0", default-features = false, features = ["ureq", "native-tls"] }
|
||||||
tokenizers = "0.22.1"
|
tokenizers = "0.22.1"
|
||||||
|
|||||||
@@ -84,20 +84,17 @@ impl ClipImageEmbedder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn resolve_device() -> Result<Device> {
|
fn resolve_device() -> Result<Device> {
|
||||||
|
#[cfg(feature = "candle-cuda")]
|
||||||
|
{
|
||||||
let cuda_device = Device::cuda_if_available(0)?;
|
let cuda_device = Device::cuda_if_available(0)?;
|
||||||
if cuda_device.is_cuda() {
|
if cuda_device.is_cuda() {
|
||||||
println!("CLIP embedder using CUDA device.");
|
println!("CLIP embedder using CUDA device.");
|
||||||
return Ok(cuda_device);
|
return Ok(cuda_device);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(not(feature = "candle-cuda"))]
|
||||||
{
|
println!("CLIP embedder built without CUDA support.");
|
||||||
let metal_device = Device::metal_if_available(0)?;
|
|
||||||
if metal_device.is_metal() {
|
|
||||||
println!("CLIP embedder using Metal device.");
|
|
||||||
return Ok(metal_device);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
println!("CLIP embedder using CPU device.");
|
println!("CLIP embedder using CPU device.");
|
||||||
Ok(Device::Cpu)
|
Ok(Device::Cpu)
|
||||||
|
|||||||
@@ -234,6 +234,8 @@ export function Gallery() {
|
|||||||
const totalImages = useGalleryStore((state) => state.totalImages);
|
const totalImages = useGalleryStore((state) => state.totalImages);
|
||||||
const loadingImages = useGalleryStore((state) => state.loadingImages);
|
const loadingImages = useGalleryStore((state) => state.loadingImages);
|
||||||
const zoomPreset = useGalleryStore((state) => state.zoomPreset);
|
const zoomPreset = useGalleryStore((state) => state.zoomPreset);
|
||||||
|
const search = useGalleryStore((state) => state.search);
|
||||||
|
const searchMode = useGalleryStore((state) => state.searchMode);
|
||||||
|
|
||||||
const parentRef = useRef<HTMLDivElement>(null);
|
const parentRef = useRef<HTMLDivElement>(null);
|
||||||
const [contextMenu, setContextMenu] = useState<{ x: number; y: number; image: ImageRecord } | null>(null);
|
const [contextMenu, setContextMenu] = useState<{ x: number; y: number; image: ImageRecord } | null>(null);
|
||||||
@@ -270,6 +272,26 @@ export function Gallery() {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
if (images.length === 0 && loadingImages) {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-1 flex-col items-center justify-center gap-4 text-center px-8">
|
||||||
|
<div className="rounded-2xl border border-white/[0.06] bg-white/[0.02] p-8 min-w-72">
|
||||||
|
<div className="h-5 w-5 mx-auto rounded-full border-2 border-white/20 border-t-white/60 animate-spin" />
|
||||||
|
<p className="mt-4 text-sm text-white/40 font-medium">
|
||||||
|
{searchMode === "semantic" && search.trim().length > 0
|
||||||
|
? `Searching for matches to "${search}"`
|
||||||
|
: "Loading media"}
|
||||||
|
</p>
|
||||||
|
<p className="text-xs text-white/20 mt-1">
|
||||||
|
{searchMode === "semantic" && search.trim().length > 0
|
||||||
|
? "Semantic search can take a little longer than filename search"
|
||||||
|
: "Fetching results"}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (images.length === 0 && !loadingImages) {
|
if (images.length === 0 && !loadingImages) {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-1 flex-col items-center justify-center gap-4 text-center px-8">
|
<div className="flex flex-1 flex-col items-center justify-center gap-4 text-center px-8">
|
||||||
@@ -278,8 +300,16 @@ export function Gallery() {
|
|||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={0.75}
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={0.75}
|
||||||
d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
||||||
</svg>
|
</svg>
|
||||||
<p className="text-sm text-white/30 font-medium">No media found</p>
|
<p className="text-sm text-white/30 font-medium">
|
||||||
<p className="text-xs text-white/15 mt-1">Try adjusting your filters or add a new folder</p>
|
{searchMode === "semantic" && search.trim().length > 0
|
||||||
|
? "No semantic matches found"
|
||||||
|
: "No media found"}
|
||||||
|
</p>
|
||||||
|
<p className="text-xs text-white/15 mt-1">
|
||||||
|
{searchMode === "semantic" && search.trim().length > 0
|
||||||
|
? "Try a broader phrase, or wait for more embeddings to finish processing"
|
||||||
|
: "Try adjusting your filters or add a new folder"}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user