feat: related-tags atlas in Explore view
Hovering a tag in Explore now loads and displays co-occurring tags as a weighted cloud. New `get_related_tags` SQL self-join (db.rs/commands.rs), `loadRelatedTags` store action with per-folder keyed cache, and TagCloud atlas UI with ResizeObserver-driven layout and RAF animation. Explore tag limit raised to 180; tag cloud auto-refreshes 700ms after new AI tagging completes.
This commit is contained in:
@@ -180,6 +180,19 @@ pub struct GetExploreTagsParams {
|
||||
pub limit: Option<usize>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct GetRelatedTagsParams {
|
||||
pub tag: String,
|
||||
pub folder_id: Option<i64>,
|
||||
pub limit: Option<usize>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct RelatedTagEntry {
|
||||
pub tag: String,
|
||||
pub shared_count: i64,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct SearchTagsAutocompleteParams {
|
||||
pub query: String,
|
||||
@@ -1277,7 +1290,28 @@ pub async fn get_explore_tags(
|
||||
params: GetExploreTagsParams,
|
||||
) -> Result<Vec<ExploreTagEntry>, String> {
|
||||
let conn = db.get().map_err(|e| e.to_string())?;
|
||||
db::get_explore_tags(&conn, params.folder_id, params.limit.unwrap_or(48))
|
||||
db::get_explore_tags(&conn, params.folder_id, params.limit.unwrap_or(180))
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_related_tags(
|
||||
db: State<'_, DbState>,
|
||||
params: GetRelatedTagsParams,
|
||||
) -> Result<Vec<RelatedTagEntry>, String> {
|
||||
let tag = params.tag.trim();
|
||||
if tag.is_empty() {
|
||||
return Ok(vec![]);
|
||||
}
|
||||
|
||||
let conn = db.get().map_err(|e| e.to_string())?;
|
||||
db::get_related_tags(&conn, tag, params.folder_id, params.limit.unwrap_or(16))
|
||||
.map(|entries| {
|
||||
entries
|
||||
.into_iter()
|
||||
.map(|(tag, shared_count)| RelatedTagEntry { tag, shared_count })
|
||||
.collect()
|
||||
})
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
|
||||
@@ -2436,6 +2436,34 @@ pub fn get_explore_tags(
|
||||
Ok(rows)
|
||||
}
|
||||
|
||||
pub fn get_related_tags(
|
||||
conn: &Connection,
|
||||
tag: &str,
|
||||
folder_id: Option<i64>,
|
||||
limit: usize,
|
||||
) -> Result<Vec<(String, i64)>> {
|
||||
let mut stmt = conn.prepare(
|
||||
"SELECT other.tag, COUNT(DISTINCT other.image_id) AS shared_count
|
||||
FROM image_tags base
|
||||
JOIN image_tags other ON other.image_id = base.image_id
|
||||
JOIN images i ON i.id = base.image_id
|
||||
WHERE base.tag = ?1
|
||||
AND other.tag != base.tag
|
||||
AND (?2 IS NULL OR i.folder_id = ?2)
|
||||
GROUP BY other.tag
|
||||
ORDER BY shared_count DESC, other.tag ASC
|
||||
LIMIT ?3",
|
||||
)?;
|
||||
|
||||
let rows = stmt
|
||||
.query_map(params![tag, folder_id, limit as i64], |row| {
|
||||
Ok((row.get::<_, String>(0)?, row.get::<_, i64>(1)?))
|
||||
})?
|
||||
.collect::<rusqlite::Result<Vec<_>>>()?;
|
||||
|
||||
Ok(rows)
|
||||
}
|
||||
|
||||
type FailedEmbeddingRow = (i64, String, String, Option<String>);
|
||||
|
||||
pub fn get_failed_embedding_images(
|
||||
|
||||
@@ -197,6 +197,7 @@ pub fn run() {
|
||||
commands::get_worker_states,
|
||||
commands::get_tag_cloud,
|
||||
commands::get_explore_tags,
|
||||
commands::get_related_tags,
|
||||
commands::get_images_by_ids,
|
||||
commands::get_failed_embedding_images,
|
||||
commands::get_failed_tagging_images,
|
||||
|
||||
Reference in New Issue
Block a user