baqu2213 commited on
Commit
c54c7c2
·
verified ·
1 Parent(s): cad34e4

Upload 14 files

Browse files
.gitattributes CHANGED
@@ -33,3 +33,6 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ data/autocomplete/__pycache__/artist_dictionary.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text
37
+ data/autocomplete/__pycache__/danbooru_character.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text
38
+ data/autocomplete/__pycache__/result_dupl.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text
data/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ # NAIA-WEB Data Package
data/autocomplete/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ # Autocomplete data package
data/autocomplete/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (142 Bytes). View file
 
data/autocomplete/__pycache__/artist_dictionary.cpython-312.pyc ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:5efe0ffa80b1cd2ddfa8b16e052fcf4997402fbfd6ef67b060f7e153d4060f4a
3
+ size 3033064
data/autocomplete/__pycache__/danbooru_character.cpython-312.pyc ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e968d93f060cfc751be6adf7a02ac4d93681234f9cbb5b9ea1fe9e47087a56f3
3
+ size 7657682
data/autocomplete/__pycache__/result_dupl.cpython-312.pyc ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:f66435f4d1479811739d84f300a199b7480cc09540a70610610e05fff21d7766
3
+ size 2999924
data/autocomplete/artist_dictionary.py ADDED
The diff for this file is too large to render. See raw diff
 
data/autocomplete/danbooru_character.py ADDED
The diff for this file is too large to render. See raw diff
 
data/autocomplete/result_dupl.py ADDED
The diff for this file is too large to render. See raw diff
 
data/character_store.py ADDED
@@ -0,0 +1,146 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Character Store for NAIA-WEB
3
+ Provides character search functionality using danbooru_character data
4
+ """
5
+
6
+ from typing import List, Tuple, Optional, Set
7
+ from dataclasses import dataclass
8
+ from pathlib import Path
9
+
10
+
11
+ @dataclass
12
+ class CharacterInfo:
13
+ """Character information"""
14
+ name: str
15
+ tags: str
16
+ count: int
17
+
18
+
19
+ class CharacterStore:
20
+ """
21
+ Character data store for search functionality.
22
+ Loads data from danbooru_character.py on first access.
23
+ Filters tags using characteristic_list.txt.
24
+ """
25
+
26
+ _instance = None
27
+ _loaded = False
28
+ _characters: dict = {}
29
+ _counts: dict = {}
30
+ _characteristic_set: Set[str] = set()
31
+
32
+ def __new__(cls):
33
+ if cls._instance is None:
34
+ cls._instance = super().__new__(cls)
35
+ return cls._instance
36
+
37
+ def __init__(self):
38
+ if not CharacterStore._loaded:
39
+ self._load_data()
40
+
41
+ def _load_characteristic_list(self) -> Set[str]:
42
+ """Load characteristic list from file"""
43
+ characteristic_set = set()
44
+ try:
45
+ list_path = Path(__file__).parent / "characteristic_list.txt"
46
+ if list_path.exists():
47
+ with open(list_path, 'r', encoding='utf-8') as f:
48
+ for line in f:
49
+ tag = line.strip().lower()
50
+ if tag:
51
+ characteristic_set.add(tag)
52
+ print(f"NAIA-WEB: Loaded {len(characteristic_set)} characteristic tags")
53
+ else:
54
+ print(f"NAIA-WEB: characteristic_list.txt not found at {list_path}")
55
+ except Exception as e:
56
+ print(f"NAIA-WEB: Failed to load characteristic list: {e}")
57
+ return characteristic_set
58
+
59
+ def _filter_tags(self, tags_str: str) -> str:
60
+ """Filter tags to only include characteristic tags"""
61
+ if not CharacterStore._characteristic_set:
62
+ return tags_str
63
+
64
+ tags = [t.strip() for t in tags_str.split(',')]
65
+ filtered = [t for t in tags if t.lower() in CharacterStore._characteristic_set]
66
+ return ', '.join(filtered)
67
+
68
+ def _load_data(self):
69
+ """Load character data from danbooru_character.py"""
70
+ try:
71
+ # Load characteristic list first
72
+ CharacterStore._characteristic_set = self._load_characteristic_list()
73
+
74
+ from data.danbooru_character import character_dict, character_dict_count
75
+ CharacterStore._characters = character_dict
76
+ CharacterStore._counts = character_dict_count
77
+ CharacterStore._loaded = True
78
+ print(f"NAIA-WEB: Loaded {len(character_dict)} characters")
79
+ except ImportError as e:
80
+ print(f"NAIA-WEB: Failed to load character data: {e}")
81
+ CharacterStore._characters = {}
82
+ CharacterStore._counts = {}
83
+ CharacterStore._loaded = True
84
+
85
+ def search(self, query: str, min_count: int = 20, limit: int = 50) -> List[CharacterInfo]:
86
+ """
87
+ Search characters by name.
88
+
89
+ Args:
90
+ query: Search query (character name)
91
+ min_count: Minimum count threshold
92
+ limit: Maximum results to return
93
+
94
+ Returns:
95
+ List of CharacterInfo sorted by count (descending)
96
+ """
97
+ query = query.lower().strip()
98
+ results = []
99
+
100
+ for name, tags in CharacterStore._characters.items():
101
+ count = CharacterStore._counts.get(name, 0)
102
+
103
+ # Filter by minimum count
104
+ if count < min_count:
105
+ continue
106
+
107
+ # Filter by query
108
+ if query and query not in name.lower():
109
+ continue
110
+
111
+ results.append(CharacterInfo(
112
+ name=name,
113
+ tags=self._filter_tags(tags),
114
+ count=count
115
+ ))
116
+
117
+ # Sort by count descending
118
+ results.sort(key=lambda x: -x.count)
119
+
120
+ return results[:limit]
121
+
122
+ def get_character(self, name: str) -> Optional[CharacterInfo]:
123
+ """Get character info by exact name"""
124
+ name = name.lower()
125
+ if name in CharacterStore._characters:
126
+ return CharacterInfo(
127
+ name=name,
128
+ tags=self._filter_tags(CharacterStore._characters[name]),
129
+ count=CharacterStore._counts.get(name, 0)
130
+ )
131
+ return None
132
+
133
+ def get_popular_characters(self, limit: int = 50) -> List[CharacterInfo]:
134
+ """Get most popular characters"""
135
+ return self.search("", min_count=50, limit=limit)
136
+
137
+
138
+ # Global instance
139
+ _store = None
140
+
141
+ def get_character_store() -> CharacterStore:
142
+ """Get the global CharacterStore instance"""
143
+ global _store
144
+ if _store is None:
145
+ _store = CharacterStore()
146
+ return _store
data/characteristic_list.txt ADDED
@@ -0,0 +1,881 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ flat chest
2
+ small breasts
3
+ medium breasts
4
+ large breasts
5
+ huge breasts
6
+ aqua eyes
7
+ black eyes
8
+ blue eyes
9
+ brown eyes
10
+ green eyes
11
+ grey eyes
12
+ orange eyes
13
+ purple eyes
14
+ pink eyes
15
+ red eyes
16
+ white eyes
17
+ yellow eyes
18
+ amber eyes
19
+ heterochromia
20
+ multicolored eyes
21
+ aqua pupils
22
+ blue pupils
23
+ brown pupils
24
+ green pupils
25
+ grey pupils
26
+ orange pupils
27
+ pink pupils
28
+ purple pupils
29
+ red pupils
30
+ white pupils
31
+ yellow pupils
32
+ pointy ears
33
+ long pointy ears
34
+ aqua hair
35
+ black hair
36
+ blonde hair
37
+ blue hair
38
+ light blue hair
39
+ dark blue hair
40
+ brown hair
41
+ light brown hair
42
+ green hair
43
+ dark green hair
44
+ light green hair
45
+ grey hair
46
+ orange hair
47
+ pink hair
48
+ purple hair
49
+ light purple hair
50
+ red hair
51
+ white hair
52
+ multicolored hair
53
+ colored inner hair
54
+ colored tips
55
+ roots (hair)
56
+ gradient hair
57
+ print hair
58
+ rainbow hair
59
+ split-color hair
60
+ spotted hair
61
+ streaked hair
62
+ two-tone hair
63
+ very short hair
64
+ short hair
65
+ medium hair
66
+ long hair
67
+ very long hair
68
+ absurdly long hair
69
+ big hair
70
+ bald
71
+ bald girl
72
+ bob cut
73
+ inverted bob
74
+ bowl cut
75
+ buzz cut
76
+ chonmage
77
+ crew cut
78
+ flattop
79
+ okappa
80
+ pixie cut
81
+ undercut
82
+ flipped hair
83
+ wolf cut
84
+ cornrows
85
+ dreadlocks
86
+ hime cut
87
+ mullet
88
+ bow-shaped hair
89
+ braid
90
+ braided bangs
91
+ front braid
92
+ side braid
93
+ french braid
94
+ crown braid
95
+ single braid
96
+ multiple braids
97
+ twin braids
98
+ low twin braids
99
+ tri braids
100
+ quad braids
101
+ flower-shaped hair
102
+ hair bun
103
+ braided bun
104
+ single hair bun
105
+ double bun
106
+ cone hair bun
107
+ doughnut hair bun
108
+ heart hair bun
109
+ triple bun
110
+ hair rings
111
+ single hair ring
112
+ half updo
113
+ one side up
114
+ two side up
115
+ low-braided long hair
116
+ low-tied long hair
117
+ mizura
118
+ multi-tied hair
119
+ nihongami
120
+ ponytail
121
+ folded ponytail
122
+ front ponytail
123
+ high ponytail
124
+ short ponytail
125
+ side ponytail
126
+ split ponytail
127
+ star-shaped hair
128
+ topknot
129
+ twintails
130
+ low twintails
131
+ short twintails
132
+ uneven twintails
133
+ tri tails
134
+ quad tails
135
+ quin tails
136
+ twisted hair
137
+ afro
138
+ huge afro
139
+ beehive hairdo
140
+ crested hair
141
+ pompadour
142
+ quiff
143
+ shouten pegasus mix mori
144
+ curly hair
145
+ drill hair
146
+ twin drills
147
+ tri drills
148
+ hair flaps
149
+ messy hair
150
+ pointy hair
151
+ ringlets
152
+ spiked hair
153
+ straight hair
154
+ wavy hair
155
+ bangs
156
+ arched bangs
157
+ asymmetrical bangs
158
+ bangs pinned back
159
+ blunt bangs
160
+ crossed bangs
161
+ diagonal bangs
162
+ dyed bangs
163
+ fanged bangs
164
+ hair over eyes
165
+ hair over one eye
166
+ long bangs
167
+ parted bangs
168
+ curtained hair
169
+ ribbon bangs
170
+ short bangs
171
+ swept bangs
172
+ hair between eyes
173
+ hair intakes
174
+ single hair intake
175
+ sidelocks
176
+ asymmetrical sidelocks
177
+ drill sidelocks
178
+ low-tied sidelocks
179
+ sidelocks tied back
180
+ single sidelock
181
+ ahoge
182
+ heart ahoge
183
+ huge ahoge
184
+ antenna hair
185
+ heart antenna hair
186
+ comb over
187
+ hair pulled back
188
+ hair slicked back
189
+ mohawk
190
+ oseledets
191
+ lone nape hair
192
+ hair bikini
193
+ hair censor
194
+ hair in own mouth
195
+ hair over breasts
196
+ hair over one breast
197
+ hair over crotch
198
+ hair over shoulder
199
+ hair scarf
200
+ alternate hairstyle
201
+ hair down
202
+ hair up
203
+ asymmetrical hair
204
+ sidecut
205
+ blunt ends
206
+ dark skin
207
+ dark-skinned female
208
+ pale skin
209
+ sun tatoo
210
+ black skin
211
+ blue skin
212
+ green skin
213
+ grey skin
214
+ orange skin
215
+ pink skin
216
+ purple skin
217
+ red skin
218
+ white skin
219
+ yellow skin
220
+ colored skin
221
+ multiple tails
222
+ demon tail
223
+ dragon tail
224
+ ghost tail
225
+ pikachu tail
226
+ snake head tail
227
+ fiery tail
228
+ bear tail
229
+ rabbit tail
230
+ cat tail
231
+ cow tail
232
+ deer tail
233
+ dog tail
234
+ ermine tail
235
+ fox tail
236
+ horse tail
237
+ leopard tail
238
+ lion tail
239
+ monkey tail
240
+ mouse tail
241
+ pig tail
242
+ sheep tail
243
+ squirrel tail
244
+ tiger tail
245
+ wolf tail
246
+ crocodilian tail
247
+ fish tail
248
+ scorpion tail
249
+ snake tail
250
+ tadpole tail
251
+ animal ears
252
+ bat ears
253
+ bear ears
254
+ rabbit ears
255
+ cat ears
256
+ cow ears
257
+ deer ears
258
+ dog ears
259
+ ferret ears
260
+ fox ears
261
+ goat ears
262
+ horse ears
263
+ kemonomimi mode
264
+ lion ears
265
+ monkey ears
266
+ mouse ears
267
+ panda ears
268
+ pikachu ears
269
+ pig ears
270
+ raccoon ears
271
+ sheep ears
272
+ squirrel ears
273
+ tiger ears
274
+ wolf ears
275
+ fake animal ears
276
+ animal ear headphones
277
+ bear ear headphones
278
+ cat ear headphones
279
+ rabbit ear headphones
280
+ hair ears
281
+ robot ears
282
+ extra ears
283
+ ear piercing
284
+ ear protection
285
+ object behind ear
286
+ roots
287
+ alternate hair color
288
+ translucent hair
289
+ widow's peak
290
+ neckbeard
291
+ stroking beard
292
+ two-tone beard
293
+ braided beard
294
+ long beard
295
+ tied beard
296
+ very long beard
297
+ goatee
298
+ mustache
299
+ stubble
300
+ alternate facial hair
301
+ bearded girl
302
+ fake mustache
303
+ beard
304
+ toothbrush mustache
305
+ mustached girl
306
+ head fins
307
+ single head wing
308
+ wings
309
+ alternate wings
310
+ multiple wings
311
+ no wings
312
+ single wing
313
+ large wings
314
+ mini wings
315
+ angel wings
316
+ demon wings
317
+ dragon wings
318
+ fairy wings
319
+ insect wings
320
+ butterfly wings
321
+ dragonfly wings
322
+ ladybug wings
323
+ moth wings
324
+ bat wings
325
+ crystal wings
326
+ energy wings
327
+ fiery wings
328
+ ice wings
329
+ light hawk wings
330
+ liquid wings
331
+ artificial wings
332
+ fake wings
333
+ hair wings
334
+ mechanical wings
335
+ metal wings
336
+ plant wings
337
+ feathered wings
338
+ black wings
339
+ gradient wings
340
+ red wings
341
+ white wings
342
+ blue wings
343
+ green wings
344
+ brown wings
345
+ transparent wings
346
+ yellow wings
347
+ pink wings
348
+ rainbow wings
349
+ grey wings
350
+ ankle wings
351
+ detached wings
352
+ head wings
353
+ low wings
354
+ leg wings
355
+ wrist wings
356
+ wing ears
357
+ winged arms
358
+ winged bag
359
+ winged hat
360
+ winged helmet
361
+ winged footwear
362
+ asymmetrical wings
363
+ bloody wings
364
+ bowed wings
365
+ flapping
366
+ glowing wings
367
+ heart wings
368
+ torn wings
369
+ wing censor
370
+ grabbing another's wing
371
+ wing hug
372
+ wing umbrella
373
+ wingjob
374
+ wing ribbon
375
+ constricted pupils
376
+ dilated pupils
377
+ extra pupils
378
+ horizontal pupils
379
+ no pupils
380
+ slit pupils
381
+ symbol-shaped pupils
382
+ diamond-shaped pupils
383
+ flower-shaped pupils
384
+ heart-shaped pupils
385
+ star-shaped pupils
386
+ cross-shaped pupils
387
+ x-shaped pupils
388
+ mismatched pupils
389
+ blue sclera
390
+ black sclera
391
+ blank eyes
392
+ bloodshot eyes
393
+ green sclera
394
+ mismatched sclera
395
+ no sclera
396
+ orange sclera
397
+ red sclera
398
+ yellow sclera
399
+ bags under eyes
400
+ aegyo sal
401
+ bruised eye
402
+ flaming eyes
403
+ glowing eyes
404
+ glowing eye
405
+ missing eye
406
+ one-eyed
407
+ third eye
408
+ extra eyes
409
+ no eyes
410
+ covering own eyes
411
+ bandage over one eye
412
+ eyelashes
413
+ colored eyelashes
414
+ fake eyelashes
415
+ eyes visible through hair
416
+ makeup
417
+ eyeliner
418
+ eyeshadow
419
+ mascara
420
+ forehead mark
421
+ forehead
422
+ aqua lips
423
+ black lips
424
+ blue lips
425
+ grey lips
426
+ green lips
427
+ orange lips
428
+ pink lips
429
+ purple lips
430
+ red lips
431
+ shiny lips
432
+ yellow lips
433
+ tail
434
+ tail bell
435
+ tail bow
436
+ tail ornament
437
+ tail piercing
438
+ tail ribbon
439
+ tail ring
440
+ fake tail
441
+ heart tail
442
+ heart tail duo
443
+ holding another's tail
444
+ holding own tail
445
+ holding with tail
446
+ intertwined tails
447
+ panties around tail
448
+ prehensile tail
449
+ spiked tail
450
+ stiff tail
451
+ tail between legs
452
+ tail biting
453
+ tail censor
454
+ tail grab
455
+ tail fondling
456
+ tail pull
457
+ tail raised
458
+ tail stand
459
+ tail wagging
460
+ tail wrap
461
+ dark-skinned male
462
+ dark-skinned other
463
+ matching hairstyle
464
+ official alternate hairstyle
465
+ single side bun
466
+ side up bun
467
+ dog human
468
+ gorilla human
469
+ cat human
470
+ bear human
471
+ raccoon human
472
+ werewolf
473
+ squirrel human
474
+ pig human
475
+ horse human
476
+ bat human
477
+ deer human
478
+ lion human
479
+ cow human
480
+ sheep human
481
+ fox human
482
+ goat human
483
+ monkey human
484
+ ferret human
485
+ mouse human
486
+ rabbit human
487
+ panda human
488
+ tiger human
489
+ dog furry
490
+ gorilla furry
491
+ cat furry
492
+ bear furry
493
+ raccoon furry
494
+ wolf furry
495
+ squirrel furry
496
+ pig furry
497
+ horse furry
498
+ bat furry
499
+ deer furry
500
+ lion furry
501
+ cow furry
502
+ sheep furry
503
+ fox furry
504
+ goat furry
505
+ monkey furry
506
+ ferret furry
507
+ mouse furry
508
+ rabbit furry
509
+ panda furry
510
+ tiger furry
511
+ furry
512
+ yellow hair
513
+ sky blue hair
514
+ ombre
515
+ sombre
516
+ absurdly short hair
517
+ gray hair streak
518
+ white hair streak
519
+ brown hair streak
520
+ red hair streak
521
+ pink hair streak
522
+ orange hair streak
523
+ yellow hair streak
524
+ blonde hair streak
525
+ light green hair streak
526
+ green hair streak
527
+ sky blue hair streak
528
+ blue hair streak
529
+ purple hair streak
530
+ black hair streak
531
+ rainbow hair streak
532
+ short eyes
533
+ long eyes
534
+ short eyebrow
535
+ long eyebrow
536
+ slender eyebrow
537
+ heavy eyebrow
538
+ short eyelashes
539
+ long eyelashes
540
+ slender eyelashes
541
+ heavy eyelashes
542
+ gray eyes
543
+ golden eyes
544
+ light green eyes
545
+ sky blue eyes
546
+ odd eye
547
+ multicolored eye
548
+ spade-shaped pupils
549
+ clover-shaped pupils
550
+ blanked eyes
551
+ empty eyes
552
+ bloodshot pupils
553
+ flaming pupils
554
+ glowing pupils
555
+ halo
556
+ horns
557
+ horn bow
558
+ horn ornament
559
+ horn ribbon
560
+ horn ring
561
+ horned headwear
562
+ horned helmet
563
+ horned hood
564
+ horned mask
565
+ black horns
566
+ blue horns
567
+ brown horns
568
+ purple horns
569
+ red horns
570
+ white horns
571
+ single horn
572
+ multiple horns
573
+ asymmetrical horns
574
+ mismatched horns
575
+ uneven horns
576
+ broken horns
577
+ broken horn
578
+ cone horns
579
+ cow horns
580
+ curled horns
581
+ demon horns
582
+ dragon horns
583
+ fake horns
584
+ fiery horns
585
+ glowing horns
586
+ giraffe horns
587
+ goat horns
588
+ gradient horns
589
+ hair horns
590
+ huge horns
591
+ low horns
592
+ mechanical horns
593
+ sheep horns
594
+ tree horns
595
+ skin-covered horns
596
+ broken halo
597
+ compass rose halo
598
+ crescent halo
599
+ cruciform halo
600
+ dark halo
601
+ double halo
602
+ drawn halo
603
+ faded halo
604
+ flaming halo
605
+ fake halo
606
+ liquid halo
607
+ mechanical halo
608
+ melting halo
609
+ star halo
610
+ square halo
611
+ winged halo
612
+ animal ear fluff
613
+ fangs
614
+ horse girl
615
+ fang
616
+ abs
617
+ cat girl
618
+ sharp teeth
619
+ demon girl
620
+ robot
621
+ elf
622
+ monster girl
623
+ goblin
624
+ slime
625
+ dark elf
626
+ high elf
627
+ orc
628
+ monster
629
+ fairy
630
+ dragon
631
+ ghost
632
+ spirit
633
+ angel
634
+ devil
635
+ vampire
636
+ dog
637
+ gorilla
638
+ cat
639
+ bear
640
+ raccoon
641
+ wolf
642
+ squirrel
643
+ pig
644
+ horse
645
+ bat
646
+ deer
647
+ lion
648
+ cow
649
+ sheep
650
+ fox
651
+ goat
652
+ ferret
653
+ mouse
654
+ rabbit
655
+ panda
656
+ tiger
657
+ short hair with long locks
658
+ oni horns
659
+ bird wings
660
+ two tails
661
+ shark tail
662
+ raccoon tail
663
+ bird tail
664
+ striped tail
665
+ black tail
666
+ thorns
667
+ multicolored wings
668
+ purple wings
669
+ large tail
670
+ spread wings
671
+ flame-tipped tail
672
+ small horns
673
+ mechanical tail
674
+ striped horns
675
+ grey horns
676
+ horns through headwear
677
+ white tail
678
+ purple tail
679
+ lizard tail
680
+ multicolored horns
681
+ tapir tail
682
+ cetacean tail
683
+ brown tail
684
+ stuffed winged unicorn
685
+ glowing hair
686
+ jaguar tail
687
+ green tail
688
+ pink tail
689
+ orange tail
690
+ yellow tail
691
+ glowing tattoo
692
+ grey tail
693
+ blue tail
694
+ eevee tail
695
+ zebra tail
696
+ no horns
697
+ skunk tail
698
+ trimmed tail
699
+ elephant tail
700
+ leaf wings
701
+ aqua horns
702
+ very long tail
703
+ glowing feather
704
+ tamandua tail
705
+ party horn
706
+ extra tails
707
+ spiked horns
708
+ drawn tail
709
+ meerkat tail
710
+ airplane wing
711
+ panther tail
712
+ glowing hands
713
+ winged unicorn
714
+ tail insertion
715
+ skeletal wings
716
+ fur-tipped tail
717
+ antelope horns
718
+ glowing skin
719
+ cheetah tail
720
+ tasmanian devil tail
721
+ french horn
722
+ moose tail
723
+ hyena tail
724
+ panda tail
725
+ gold horns
726
+ giraffe tail
727
+ implied tail plug
728
+ horn flower
729
+ hugging another's tail
730
+ electric plug tail
731
+ chameleon tail
732
+ anteater tail
733
+ large horns
734
+ aqua wings
735
+ aardwolf tail
736
+ cattail
737
+ mismatched wings
738
+ beaver tail
739
+ tailjob
740
+ chipmunk tail
741
+ jackal tail
742
+ dinosaur tail
743
+ fuse tail
744
+ drawn wings
745
+ red panda tail
746
+ two-tone wings
747
+ yellow horns
748
+ no tail
749
+ red tail
750
+ hair on horn
751
+ otter tail
752
+ hugging tail
753
+ cable tail
754
+ green horns
755
+ multicolored tail
756
+ hedgehog tail
757
+ lifted by tail
758
+ pink horns
759
+ penguin tail
760
+ alpaca tail
761
+ orange horns
762
+ pegasus wings
763
+ long horns
764
+ weasel tail
765
+ pokemon tail
766
+ hugging own tail
767
+ orange wings
768
+ ox horns
769
+ goat tail
770
+ expressive tail
771
+ forked tail
772
+ hair behind ear
773
+ notched ear
774
+ hand on own ear
775
+ hand on another's ear
776
+ grabbing another's ear
777
+ blowing in ear
778
+ bandaid on ear
779
+ fox girl
780
+ magical girl
781
+ dragon girl
782
+ rabbit girl
783
+ wolf girl
784
+ dog girl
785
+ minigirl
786
+ cow girl
787
+ shark girl
788
+ mouse girl
789
+ tiger girl
790
+ arthropod girl
791
+ raccoon girl
792
+ lion girl
793
+ sheep girl
794
+ bird girl
795
+ slime girl
796
+ squirrel girl
797
+ fish girl
798
+ bear girl
799
+ spider girl
800
+ goat girl
801
+ plant girl
802
+ bat girl
803
+ robot girl
804
+ deer girl
805
+ leopard girl
806
+ frog girl
807
+ reptile girl
808
+ moth girl
809
+ monkey girl
810
+ owl girl
811
+ penguin girl
812
+ weasel girl
813
+ chipmunk girl
814
+ dolphin girl
815
+ orca girl
816
+ bee girl
817
+ hedgehog girl
818
+ panda girl
819
+ pig girl
820
+ jaguar girl
821
+ dinosaur girl
822
+ hyena girl
823
+ ox girl
824
+ reindeer girl
825
+ alpaca girl
826
+ prairie dog girl
827
+ red panda girl
828
+ otter girl
829
+ mantis girl
830
+ hamster girl
831
+ unicorn girl
832
+ mushroom girl
833
+ ferret girl
834
+ cheetah girl
835
+ whale girl
836
+ cockroach girl
837
+ zebra girl
838
+ slug girl
839
+ snake girl
840
+ ghost girl
841
+ capybara girl
842
+ donkey girl
843
+ scorpion girl
844
+ monster boy
845
+ cat boy
846
+ tomboy
847
+ demon boy
848
+ dog boy
849
+ fox boy
850
+ wolf boy
851
+ tiger boy
852
+ miniboy
853
+ dragon boy
854
+ cow boy
855
+ rabbit boy
856
+ lion boy
857
+ fish boy
858
+ bear boy
859
+ arthropod boy
860
+ game boy
861
+ bird boy
862
+ monkey boy
863
+ leopard boy
864
+ magical boy
865
+ goat boy
866
+ horse boy
867
+ jackal boy
868
+ girly boy
869
+ cowboy
870
+ reptile boy
871
+ jaguar boy
872
+ shark boy
873
+ octopus boy
874
+ boar boy
875
+ slime boy
876
+ harpy boy
877
+ mouse boy
878
+ cuntboy
879
+ sheep boy
880
+ deer boy
881
+ raccoon boy
data/danbooru_character.py ADDED
The diff for this file is too large to render. See raw diff
 
data/partition_loader.py ADDED
@@ -0,0 +1,281 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ NAIA-WEB Partition Loader
3
+ TGP file loading for Quick Search functionality
4
+
5
+ Reference: NAIA2.0/ui/remote/quick_search_tab.py (145-255)
6
+ """
7
+
8
+ import struct
9
+ import lzma
10
+ import pickle
11
+ from pathlib import Path
12
+ from typing import Dict, List, Set, Optional
13
+ from collections import Counter
14
+
15
+ try:
16
+ import numpy as np
17
+ HAS_NUMPY = True
18
+ except ImportError:
19
+ HAS_NUMPY = False
20
+ np = None
21
+
22
+
23
+ # Data directory
24
+ DATA_DIR = Path(__file__).parent / "quick_search"
25
+
26
+
27
+ class SinglePartitionStore:
28
+ """
29
+ Single partition storage (inverted index based) - Quick Search lightweight version
30
+
31
+ Reference: NAIA2.0/ui/remote/quick_search_tab.py SinglePartitionStore class
32
+ """
33
+
34
+ MAGIC = b'TGP1'
35
+ VERSION = 1
36
+
37
+ def __init__(self):
38
+ self.num_events: int = 0
39
+ self._event_tag_indices = None
40
+ self._event_tag_indptr = None
41
+ self._event_counts = None
42
+ self._tag_to_events: Dict[int, object] = {}
43
+ self._loaded: bool = False
44
+
45
+ @classmethod
46
+ def load(cls, input_path: str) -> 'SinglePartitionStore':
47
+ """Load partition file"""
48
+ if not HAS_NUMPY:
49
+ raise RuntimeError("NumPy is required")
50
+
51
+ store = cls()
52
+
53
+ with open(input_path, 'rb') as f:
54
+ magic = f.read(4)
55
+ if magic != cls.MAGIC:
56
+ raise ValueError(f"Invalid format: {magic}")
57
+
58
+ _ = struct.unpack('<H', f.read(2))[0] # version
59
+ compressed_len = struct.unpack('<I', f.read(4))[0]
60
+ compressed = f.read(compressed_len)
61
+
62
+ serialized = lzma.decompress(compressed)
63
+ data = pickle.loads(serialized)
64
+
65
+ store.num_events = data['num_events']
66
+ store._event_tag_indices = np.frombuffer(data['event_tag_indices'], dtype=np.uint16).copy()
67
+ store._event_tag_indptr = np.frombuffer(data['event_tag_indptr'], dtype=np.int32).copy()
68
+ store._event_counts = np.frombuffer(data['event_counts'], dtype=np.int32).copy()
69
+
70
+ store._tag_to_events = {
71
+ int(k): np.frombuffer(v, dtype=np.int32).copy()
72
+ for k, v in data['tag_to_events'].items()
73
+ }
74
+
75
+ store._loaded = True
76
+ return store
77
+
78
+ def filter_events(
79
+ self,
80
+ required_tags: Optional[List[str]] = None,
81
+ excluded_tags: Optional[List[str]] = None,
82
+ tag_to_id: Optional[Dict[str, int]] = None
83
+ ):
84
+ """Return event indices matching conditions"""
85
+ if not self._loaded or not HAS_NUMPY:
86
+ return np.array([], dtype=np.int32) if HAS_NUMPY else []
87
+
88
+ # Start with all events
89
+ candidates = set(range(self.num_events))
90
+
91
+ # Required tags
92
+ if required_tags and tag_to_id:
93
+ for tag in required_tags:
94
+ if tag in tag_to_id:
95
+ tag_id = tag_to_id[tag]
96
+ if tag_id in self._tag_to_events:
97
+ candidates &= set(self._tag_to_events[tag_id])
98
+ else:
99
+ return np.array([], dtype=np.int32)
100
+ else:
101
+ return np.array([], dtype=np.int32)
102
+
103
+ # Excluded tags
104
+ if excluded_tags and tag_to_id:
105
+ for tag in excluded_tags:
106
+ if tag in tag_to_id:
107
+ tag_id = tag_to_id[tag]
108
+ if tag_id in self._tag_to_events:
109
+ candidates -= set(self._tag_to_events[tag_id])
110
+
111
+ return np.array(sorted(candidates), dtype=np.int32)
112
+
113
+ def get_tag_counts(
114
+ self,
115
+ event_indices=None,
116
+ id_to_tag: Optional[Dict[int, str]] = None
117
+ ) -> Counter:
118
+ """Count events per tag"""
119
+ if not HAS_NUMPY or id_to_tag is None:
120
+ return Counter()
121
+
122
+ if event_indices is None or len(event_indices) == 0:
123
+ # Total tag counts
124
+ return Counter({
125
+ id_to_tag[tag_id]: len(events)
126
+ for tag_id, events in self._tag_to_events.items()
127
+ if tag_id in id_to_tag
128
+ })
129
+
130
+ event_set = set(event_indices)
131
+ return Counter({
132
+ id_to_tag[tag_id]: len(set(events) & event_set)
133
+ for tag_id, events in self._tag_to_events.items()
134
+ if tag_id in id_to_tag
135
+ })
136
+
137
+ def get_event_tags(
138
+ self,
139
+ event_idx: int,
140
+ id_to_tag: Optional[Dict[int, str]] = None
141
+ ) -> Set[str]:
142
+ """Return tags for an event"""
143
+ if not self._loaded or id_to_tag is None:
144
+ return set()
145
+
146
+ if event_idx < 0 or event_idx >= self.num_events:
147
+ return set()
148
+
149
+ start = self._event_tag_indptr[event_idx]
150
+ end = self._event_tag_indptr[event_idx + 1]
151
+ tag_ids = self._event_tag_indices[start:end]
152
+
153
+ return {id_to_tag[int(tid)] for tid in tag_ids if int(tid) in id_to_tag}
154
+
155
+
156
+ class PartitionMetadata:
157
+ """
158
+ Metadata for partition files
159
+
160
+ Reference: NAIA2.0/ui/remote/quick_search_tab.py metadata loading
161
+ """
162
+
163
+ MAGIC = b'TGPS'
164
+
165
+ def __init__(self):
166
+ self.tag_to_id: Dict[str, int] = {}
167
+ self.id_to_tag: Dict[int, str] = {}
168
+ self.tag_freq: Dict[str, int] = {}
169
+ self.partitions: Dict[str, Dict] = {}
170
+ self._loaded: bool = False
171
+
172
+ @classmethod
173
+ def load(cls, input_path: str) -> 'PartitionMetadata':
174
+ """Load metadata file"""
175
+ meta = cls()
176
+
177
+ with open(input_path, 'rb') as f:
178
+ magic = f.read(4)
179
+ if magic != cls.MAGIC:
180
+ raise ValueError(f"Invalid metadata format: {magic}")
181
+
182
+ _ = struct.unpack('<H', f.read(2))[0] # version
183
+ compressed_len = struct.unpack('<I', f.read(4))[0]
184
+ compressed = f.read(compressed_len)
185
+
186
+ serialized = lzma.decompress(compressed)
187
+ data = pickle.loads(serialized)
188
+
189
+ meta.tag_to_id = data.get('tag_to_id', {})
190
+ meta.id_to_tag = data.get('id_to_tag', {})
191
+ meta.tag_freq = data.get('tag_freq', {})
192
+ meta.partitions = data.get('partitions', {})
193
+ meta._loaded = True
194
+
195
+ return meta
196
+
197
+ @property
198
+ def is_loaded(self) -> bool:
199
+ return self._loaded
200
+
201
+ def get_partition_names(self) -> List[str]:
202
+ """Return list of available partition names"""
203
+ return list(self.partitions.keys())
204
+
205
+
206
+ class PartitionManager:
207
+ """
208
+ Manages partition loading and caching
209
+ """
210
+
211
+ def __init__(self):
212
+ self._metadata: Optional[PartitionMetadata] = None
213
+ self._loaded_partitions: Dict[str, SinglePartitionStore] = {}
214
+ self._data_dir = DATA_DIR
215
+
216
+ def is_data_available(self) -> bool:
217
+ """Check if partition data files are available"""
218
+ metadata_path = self._data_dir / "metadata.tgpm"
219
+ return metadata_path.exists()
220
+
221
+ def load_metadata(self) -> Optional[PartitionMetadata]:
222
+ """Load partition metadata"""
223
+ if self._metadata is not None:
224
+ return self._metadata
225
+
226
+ metadata_path = self._data_dir / "metadata.tgpm"
227
+ if not metadata_path.exists():
228
+ return None
229
+
230
+ try:
231
+ self._metadata = PartitionMetadata.load(str(metadata_path))
232
+ return self._metadata
233
+ except Exception as e:
234
+ print(f"Error loading metadata: {e}")
235
+ return None
236
+
237
+ def get_metadata(self) -> Optional[PartitionMetadata]:
238
+ """Get loaded metadata (load if needed)"""
239
+ if self._metadata is None:
240
+ self.load_metadata()
241
+ return self._metadata
242
+
243
+ def load_partition(self, partition_name: str) -> Optional[SinglePartitionStore]:
244
+ """Load a specific partition"""
245
+ if partition_name in self._loaded_partitions:
246
+ return self._loaded_partitions[partition_name]
247
+
248
+ partition_path = self._data_dir / f"{partition_name}.tgp"
249
+ if not partition_path.exists():
250
+ print(f"Partition file not found: {partition_path}")
251
+ return None
252
+
253
+ try:
254
+ store = SinglePartitionStore.load(str(partition_path))
255
+ self._loaded_partitions[partition_name] = store
256
+ return store
257
+ except Exception as e:
258
+ print(f"Error loading partition {partition_name}: {e}")
259
+ return None
260
+
261
+ def unload_partition(self, partition_name: str):
262
+ """Unload a partition to free memory"""
263
+ if partition_name in self._loaded_partitions:
264
+ del self._loaded_partitions[partition_name]
265
+
266
+ def unload_all(self):
267
+ """Unload all partitions"""
268
+ self._loaded_partitions.clear()
269
+
270
+ def get_partition_filename(self, rating: str, person: str) -> str:
271
+ """
272
+ Get partition filename from rating and person category
273
+
274
+ Args:
275
+ rating: 'g', 's', 'q', or 'e'
276
+ person: person category like '1girl_solo'
277
+
278
+ Returns:
279
+ Partition name like 'g_1girl_solo'
280
+ """
281
+ return f"{rating}_{person}"
data/tag_store.py ADDED
@@ -0,0 +1,319 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ NAIA-WEB Tag Store
3
+ High-level interface for tag search and Quick Search functionality
4
+
5
+ Reference: NAIA2.0/ui/remote/quick_search_tab.py
6
+ """
7
+
8
+ import random
9
+ from typing import List, Optional, Set, Tuple
10
+ from dataclasses import dataclass
11
+ from collections import Counter
12
+
13
+ from .partition_loader import PartitionManager, SinglePartitionStore, PartitionMetadata
14
+ from utils.constants import PERSON_AUTO_TAGS
15
+
16
+
17
+ @dataclass
18
+ class TagInfo:
19
+ """Information about a single tag"""
20
+ tag: str
21
+ count: int
22
+
23
+
24
+ @dataclass
25
+ class QuickSearchResult:
26
+ """Result of a quick search operation"""
27
+ success: bool
28
+ prompt: str = ""
29
+ tags: List[str] = None
30
+ event_count: int = 0
31
+ error_message: str = ""
32
+
33
+ def __post_init__(self):
34
+ if self.tags is None:
35
+ self.tags = []
36
+
37
+
38
+ class TagStore:
39
+ """
40
+ High-level interface for Quick Search functionality.
41
+
42
+ Provides:
43
+ - Partition loading and management
44
+ - Tag filtering by rating and person category
45
+ - Include/Exclude tag filtering
46
+ - Random event sampling for prompt generation
47
+ - Tag frequency information
48
+ """
49
+
50
+ def __init__(self):
51
+ self._manager = PartitionManager()
52
+ self._current_partition: Optional[SinglePartitionStore] = None
53
+ self._current_partition_name: str = ""
54
+
55
+ def is_available(self) -> bool:
56
+ """Check if tag data is available"""
57
+ return self._manager.is_data_available()
58
+
59
+ def get_available_partitions(self) -> List[str]:
60
+ """Get list of available partition names"""
61
+ metadata = self._manager.get_metadata()
62
+ if metadata is None:
63
+ return []
64
+ return metadata.get_partition_names()
65
+
66
+ def load_partition(self, rating: str, person: str) -> bool:
67
+ """
68
+ Load partition for given rating and person category.
69
+
70
+ Args:
71
+ rating: 'g', 's', 'q', or 'e'
72
+ person: Person category like '1girl_solo'
73
+
74
+ Returns:
75
+ True if loaded successfully
76
+ """
77
+ partition_name = self._manager.get_partition_filename(rating, person)
78
+
79
+ if partition_name == self._current_partition_name and self._current_partition is not None:
80
+ return True # Already loaded
81
+
82
+ # Unload previous partition to save memory
83
+ if self._current_partition_name:
84
+ self._manager.unload_partition(self._current_partition_name)
85
+
86
+ partition = self._manager.load_partition(partition_name)
87
+ if partition is None:
88
+ self._current_partition = None
89
+ self._current_partition_name = ""
90
+ return False
91
+
92
+ self._current_partition = partition
93
+ self._current_partition_name = partition_name
94
+ return True
95
+
96
+ def get_event_count(self) -> int:
97
+ """Get number of events in current partition"""
98
+ if self._current_partition is None:
99
+ return 0
100
+ return self._current_partition.num_events
101
+
102
+ def get_filtered_event_count(
103
+ self,
104
+ include_tags: Optional[List[str]] = None,
105
+ exclude_tags: Optional[List[str]] = None
106
+ ) -> int:
107
+ """Get number of events matching filter criteria"""
108
+ if self._current_partition is None:
109
+ return 0
110
+
111
+ metadata = self._manager.get_metadata()
112
+ if metadata is None:
113
+ return 0
114
+
115
+ filtered = self._current_partition.filter_events(
116
+ required_tags=include_tags,
117
+ excluded_tags=exclude_tags,
118
+ tag_to_id=metadata.tag_to_id
119
+ )
120
+
121
+ return len(filtered)
122
+
123
+ def get_top_tags(
124
+ self,
125
+ include_tags: Optional[List[str]] = None,
126
+ exclude_tags: Optional[List[str]] = None,
127
+ limit: int = 50,
128
+ offset: int = 0
129
+ ) -> List[TagInfo]:
130
+ """
131
+ Get most frequent tags in current partition with filters applied.
132
+
133
+ Returns tags sorted by frequency (descending).
134
+ """
135
+ if self._current_partition is None:
136
+ return []
137
+
138
+ metadata = self._manager.get_metadata()
139
+ if metadata is None:
140
+ return []
141
+
142
+ # Filter events
143
+ filtered_indices = self._current_partition.filter_events(
144
+ required_tags=include_tags,
145
+ excluded_tags=exclude_tags,
146
+ tag_to_id=metadata.tag_to_id
147
+ )
148
+
149
+ if len(filtered_indices) == 0:
150
+ return []
151
+
152
+ # Count tags in filtered events
153
+ tag_counts = self._current_partition.get_tag_counts(
154
+ event_indices=filtered_indices,
155
+ id_to_tag=metadata.id_to_tag
156
+ )
157
+
158
+ # Exclude already included/excluded tags from results
159
+ excluded_set = set(include_tags or []) | set(exclude_tags or [])
160
+
161
+ # Sort by count and return with pagination
162
+ sorted_tags = sorted(
163
+ [(tag, count) for tag, count in tag_counts.items() if tag not in excluded_set],
164
+ key=lambda x: x[1],
165
+ reverse=True
166
+ )[offset:offset + limit]
167
+
168
+ return [TagInfo(tag=tag, count=count) for tag, count in sorted_tags]
169
+
170
+ def get_total_tag_count(
171
+ self,
172
+ include_tags: Optional[List[str]] = None,
173
+ exclude_tags: Optional[List[str]] = None
174
+ ) -> int:
175
+ """Get total number of unique tags matching filter criteria"""
176
+ if self._current_partition is None:
177
+ return 0
178
+
179
+ metadata = self._manager.get_metadata()
180
+ if metadata is None:
181
+ return 0
182
+
183
+ # Filter events
184
+ filtered_indices = self._current_partition.filter_events(
185
+ required_tags=include_tags,
186
+ excluded_tags=exclude_tags,
187
+ tag_to_id=metadata.tag_to_id
188
+ )
189
+
190
+ if len(filtered_indices) == 0:
191
+ return 0
192
+
193
+ # Count tags in filtered events
194
+ tag_counts = self._current_partition.get_tag_counts(
195
+ event_indices=filtered_indices,
196
+ id_to_tag=metadata.id_to_tag
197
+ )
198
+
199
+ # Exclude already included/excluded tags
200
+ excluded_set = set(include_tags or []) | set(exclude_tags or [])
201
+ return len([tag for tag in tag_counts.keys() if tag not in excluded_set])
202
+
203
+ def generate_random_prompt(
204
+ self,
205
+ rating: str,
206
+ person: str,
207
+ include_tags: Optional[List[str]] = None,
208
+ exclude_tags: Optional[List[str]] = None
209
+ ) -> QuickSearchResult:
210
+ """
211
+ Generate a random prompt from the partition.
212
+
213
+ Args:
214
+ rating: Rating code ('g', 's', 'q', 'e')
215
+ person: Person category
216
+ include_tags: Tags that must be present
217
+ exclude_tags: Tags that must not be present
218
+
219
+ Returns:
220
+ QuickSearchResult with generated prompt
221
+ """
222
+ # Load partition
223
+ if not self.load_partition(rating, person):
224
+ return QuickSearchResult(
225
+ success=False,
226
+ error_message=f"Failed to load partition: {rating}_{person}"
227
+ )
228
+
229
+ metadata = self._manager.get_metadata()
230
+ if metadata is None:
231
+ return QuickSearchResult(
232
+ success=False,
233
+ error_message="Metadata not available"
234
+ )
235
+
236
+ # Get auto-tags for person category
237
+ auto_tags = PERSON_AUTO_TAGS.get(person, [])
238
+
239
+ # Combine include tags with auto tags
240
+ all_include = list(set((include_tags or []) + auto_tags))
241
+
242
+ # Filter events
243
+ filtered_indices = self._current_partition.filter_events(
244
+ required_tags=all_include if all_include else None,
245
+ excluded_tags=exclude_tags,
246
+ tag_to_id=metadata.tag_to_id
247
+ )
248
+
249
+ if len(filtered_indices) == 0:
250
+ return QuickSearchResult(
251
+ success=False,
252
+ error_message="No events match the filter criteria",
253
+ event_count=0
254
+ )
255
+
256
+ # Select random event
257
+ random_idx = random.choice(filtered_indices)
258
+
259
+ # Get tags for this event
260
+ event_tags = self._current_partition.get_event_tags(
261
+ event_idx=int(random_idx),
262
+ id_to_tag=metadata.id_to_tag
263
+ )
264
+
265
+ if not event_tags:
266
+ return QuickSearchResult(
267
+ success=False,
268
+ error_message="Failed to get tags for selected event"
269
+ )
270
+
271
+ # Convert to list and create prompt
272
+ tags_list = sorted(list(event_tags))
273
+ prompt = ", ".join(tags_list)
274
+
275
+ return QuickSearchResult(
276
+ success=True,
277
+ prompt=prompt,
278
+ tags=tags_list,
279
+ event_count=len(filtered_indices)
280
+ )
281
+
282
+ def search_tags(
283
+ self,
284
+ query: str,
285
+ limit: int = 20
286
+ ) -> List[TagInfo]:
287
+ """
288
+ Search for tags matching query string.
289
+
290
+ Args:
291
+ query: Search query (partial match)
292
+ limit: Maximum results
293
+
294
+ Returns:
295
+ List of matching tags with frequencies
296
+ """
297
+ metadata = self._manager.get_metadata()
298
+ if metadata is None or not query:
299
+ return []
300
+
301
+ query_lower = query.lower()
302
+
303
+ # Find matching tags
304
+ matches = []
305
+ for tag, freq in metadata.tag_freq.items():
306
+ if query_lower in tag.lower():
307
+ matches.append(TagInfo(tag=tag, count=freq))
308
+
309
+ # Sort by frequency and return top N
310
+ matches.sort(key=lambda x: x.count, reverse=True)
311
+ return matches[:limit]
312
+
313
+ def get_partition_info(self) -> dict:
314
+ """Get information about current partition"""
315
+ return {
316
+ "partition_name": self._current_partition_name,
317
+ "event_count": self.get_event_count(),
318
+ "is_loaded": self._current_partition is not None
319
+ }