feat: implement rule application engine with full-text variant support, batch entry processing, and tag visualization in UI.

This commit is contained in:
2026-08-25 22:57:48 -07:00
parent 235966e933
commit 83c70cc3b9
10 changed files with 667 additions and 107 deletions
+65
View File
@@ -725,6 +725,22 @@ func (d *DB) ListEntries(filter EntryFilter) ([]models.FeedEntry, int, error) {
entries = append(entries, e)
}
// Batch-load variants for all returned entries
if len(entries) > 0 {
ids := make([]string, len(entries))
for i, e := range entries {
ids[i] = e.ID
}
variantMap, err := d.batchLoadVariants(ids)
if err == nil {
for i := range entries {
if v, ok := variantMap[entries[i].ID]; ok {
entries[i].Variants = v
}
}
}
}
return entries, total, nil
}
@@ -810,6 +826,55 @@ func (d *DB) ListVariantsForEntry(entryID string) (map[string]models.EntryVarian
return variants, nil
}
// batchLoadVariants loads lightweight variants (tags, ai_summary keys, etc.) for multiple entries.
// Excludes large text variants like readability_full_text for performance.
func (d *DB) batchLoadVariants(entryIDs []string) (map[string]map[string]models.EntryVariant, error) {
if len(entryIDs) == 0 {
return nil, nil
}
placeholders := make([]string, len(entryIDs))
args := make([]interface{}, len(entryIDs))
for i, id := range entryIDs {
placeholders[i] = "?"
args[i] = id
}
query := `
SELECT entry_id, variant_key, content_type,
CASE WHEN variant_key IN ('readability_full_text') THEN '' ELSE COALESCE(text_value, '') END,
COALESCE(blob_path, ''), metadata, created_at, updated_at
FROM entry_variants
WHERE entry_id IN (` + strings.Join(placeholders, ",") + `)
`
rows, err := d.Query(query, args...)
if err != nil {
return nil, err
}
defer func() {
_ = rows.Close()
}()
result := make(map[string]map[string]models.EntryVariant)
for rows.Next() {
var v models.EntryVariant
var metaStr sql.NullString
err := rows.Scan(
&v.EntryID, &v.VariantKey, &v.ContentType, &v.TextValue, &v.BlobPath, &metaStr, &v.CreatedAt, &v.UpdatedAt,
)
if err != nil {
return nil, err
}
v.Metadata = make(map[string]interface{})
if metaStr.Valid && metaStr.String != "" {
_ = json.Unmarshal([]byte(metaStr.String), &v.Metadata)
}
if result[v.EntryID] == nil {
result[v.EntryID] = make(map[string]models.EntryVariant)
}
result[v.EntryID][v.VariantKey] = v
}
return result, nil
}
func (d *DB) SetVariant(v models.EntryVariant) error {
now := time.Now().UTC()
metaBytes, _ := json.Marshal(v.Metadata)