printhive/test/autotag.test.js
dlawler489 0f49fe8c1a AI tags and descriptions, search by meaning, and automatic tags
- AI tagging through any OpenAI-compatible API: DeepSeek by default, or
  OpenRouter, OpenAI, or a local server (Ollama, LM Studio). Settings → AI
  with presets, prices and "Save & test connection"; the key never leaves
  the server except to the provider.
- Runs over untagged models, all models, or one model from its page: live
  progress, tokens and cost, stop/resume (retrying failures), an optional
  spending limit, and paused rather than resumed after a restart. Adapts to
  models without JSON mode or image input; stops on a rejected key or no
  credit. Tags are only added; descriptions filled only where empty unless
  asked.
- Search by meaning: local bge-small-en-v1.5 embeddings (transformers.js),
  indexed in the background whenever a model's text changes; Words/Meaning
  switch on the search box. Stored as REAL[] in Postgres, no pgvector needed.
- Automatic tags: strict whole-word rules (part types, mechanisms, materials,
  techniques) over names, file names and archive contents, plus your own
  word -> tag rules; on/off (optionally removing its tags) and re-apply.
- Tags remember where they came from (user / rule / ai); edits keep that,
  rule and AI passes never touch your own tags, and the model page marks them.
- Docker: ships the embedding model and only Linux onnxruntime binaries.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 11:12:58 +10:00

33 lines
1.7 KiB
JavaScript

// Automatic tagging rules (lib/autotag.js).
const test = require('node:test');
const assert = require('node:assert/strict');
const { tokenize, matchTags } = require('../lib/autotag');
test('names split into words at punctuation, camelCase and numbers', () => {
assert.deepEqual(tokenize('PETGWallHook_v2.stl'), ['petg', 'wall', 'hook', 'v', '2', 'stl']);
assert.deepEqual(tokenize('Gridfinity-6x4 baseplate'), ['gridfinity', '6', 'x', '4', 'baseplate']);
});
test('rules match whole words only, plurals included', () => {
assert.deepEqual(matchTags(['Wall Hooks.stl']), ['hook']);
assert.deepEqual(matchTags(['facebook_logo.stl', 'blocked-path.3mf', 'planter.stl', 'absolute.obj']), []);
assert.deepEqual(matchTags(['gears_and_threads.3mf']).sort(), ['gear', 'threaded']);
});
test('phrases match across separators but not across different names', () => {
assert.deepEqual(matchTags(['Print-In-Place_Dragon.3mf']), ['print-in-place']);
assert.deepEqual(matchTags(['Dragon Print', 'in place']), []);
assert.deepEqual(matchTags(['Spiral Vase Mode lamp']), ['vase mode']);
});
test('materials and techniques are picked up', () => {
assert.deepEqual(matchTags(['benchy_PLA.3mf', 'TPU tyre']).sort(), ['pla', 'tpu']);
assert.deepEqual(matchTags(['MultiColour Keyring']).sort(), ['keychain', 'multicolor']);
});
test('your own rules add tags the same strict way', () => {
const rules = [{ phrase: 'benchy', tag: 'Calibration' }, { phrase: 'cable tie', tag: 'cable management' }];
assert.deepEqual(matchTags(['3DBenchy.stl'], rules), ['calibration']);
assert.deepEqual(matchTags(['cable_ties_holder.stl'], rules).sort(), ['cable management', 'holder']);
assert.deepEqual(matchTags(['benchymark.stl'], rules), []);
});