feat: ML tag suggestions, character/fandom integrity, underscores, modal polish
Consolidated merge of feat/tag-suggestions branch. Original 64-commit history was lost to git-object corruption in a Nextcloud-synced checkout; this single commit captures the equivalent diff. Includes: - pgvector-backed tag suggestion infra (WD14 + SigLIP centroids, ml-worker container, Celery tasks, suggestion service, accept/reject endpoints + modal UI with green/red chip buttons) - Character/fandom integrity: title-case normalization on every write path, fandom-id backfill, maintenance task + settings button, migrations g26041901 + h26041901 to canonicalize legacy rows with case-only duplicate merging - Tag-underscores + modal polish: WD14 name canonicalization at emit + accept + add/bulk-add paths, migration i26041901 for legacy-row rename-or-merge across character/fandom/NULL kinds, suggestion-accept refresh parity via awaited loadTags, persistent chip tint
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
from pgvector.sqlalchemy import Vector
|
||||
|
||||
from . import db
|
||||
|
||||
# tag to object relationship table
|
||||
@@ -218,3 +220,61 @@ class ImportTask(db.Model):
|
||||
db.Index('ix_import_task_status_type', 'status', 'task_type'),
|
||||
db.Index('ix_import_task_batch_status', 'batch_id', 'status'),
|
||||
)
|
||||
|
||||
|
||||
class ImageTagPrediction(db.Model):
|
||||
__tablename__ = "image_tag_prediction"
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
image_id = db.Column(db.Integer, db.ForeignKey("image_record.id", ondelete="CASCADE"), nullable=False)
|
||||
tag_name = db.Column(db.Text, nullable=False)
|
||||
tag_category = db.Column(db.Text, nullable=False) # general/character/copyright/rating/meta
|
||||
confidence = db.Column(db.Float, nullable=False)
|
||||
model_version = db.Column(db.Text, nullable=False)
|
||||
created_at = db.Column(db.DateTime(timezone=True), server_default=db.func.now(), nullable=False)
|
||||
|
||||
__table_args__ = (
|
||||
db.Index('idx_tag_predictions_image', 'image_id'),
|
||||
db.Index('idx_tag_predictions_tag', 'tag_name'),
|
||||
db.Index('idx_tag_predictions_model', 'model_version'),
|
||||
)
|
||||
|
||||
|
||||
class ImageEmbedding(db.Model):
|
||||
__tablename__ = "image_embedding"
|
||||
image_id = db.Column(db.Integer, db.ForeignKey("image_record.id", ondelete="CASCADE"), primary_key=True)
|
||||
model_version = db.Column(db.Text, primary_key=True)
|
||||
embedding = db.Column(Vector(1152), nullable=False)
|
||||
created_at = db.Column(db.DateTime(timezone=True), server_default=db.func.now(), nullable=False)
|
||||
|
||||
|
||||
class TagReferenceEmbedding(db.Model):
|
||||
__tablename__ = "tag_reference_embedding"
|
||||
tag_name = db.Column(db.Text, primary_key=True)
|
||||
model_version = db.Column(db.Text, primary_key=True)
|
||||
tag_kind = db.Column(db.Text, nullable=True)
|
||||
centroid = db.Column(Vector(1152), nullable=False)
|
||||
reference_count = db.Column(db.Integer, nullable=False)
|
||||
computed_at = db.Column(db.DateTime(timezone=True), server_default=db.func.now(), nullable=False)
|
||||
|
||||
|
||||
class SuggestionFeedback(db.Model):
|
||||
__tablename__ = "suggestion_feedback"
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
image_id = db.Column(db.Integer, db.ForeignKey("image_record.id", ondelete="CASCADE"), nullable=False)
|
||||
tag_name = db.Column(db.Text, nullable=False)
|
||||
suggestion_source = db.Column(db.Text, nullable=False) # wd14 | embedding_similarity
|
||||
confidence = db.Column(db.Float, nullable=False)
|
||||
decision = db.Column(db.Text, nullable=False) # accepted | rejected
|
||||
decided_at = db.Column(db.DateTime(timezone=True), server_default=db.func.now(), nullable=False)
|
||||
|
||||
__table_args__ = (
|
||||
db.Index('idx_feedback_image', 'image_id'),
|
||||
db.Index('idx_feedback_tag', 'tag_name'),
|
||||
)
|
||||
|
||||
|
||||
class TagSuggestionConfig(db.Model):
|
||||
__tablename__ = "tag_suggestion_config"
|
||||
key = db.Column(db.Text, primary_key=True)
|
||||
value = db.Column(db.Text, nullable=False)
|
||||
description = db.Column(db.Text, nullable=True)
|
||||
|
||||
Reference in New Issue
Block a user