File size: 3,284 Bytes
de78f87
 
 
750ca83
de78f87
353687b
750ca83
 
260bf19
353687b
 
 
750ca83
 
 
 
 
de78f87
750ca83
 
 
 
 
 
 
de78f87
 
 
 
353687b
de78f87
 
 
 
353687b
 
750ca83
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
de78f87
750ca83
de78f87
 
 
353687b
750ca83
 
 
 
 
 
de78f87
750ca83
 
 
de78f87
353687b
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
// Recommended settings presets. Each preset can set the text engine/model, voice
// provider, and portrait model together. Manual changes move the picker to Custom.
import { setEngine, getEngineId, setModel, currentModelId, listModels, onModelChange } from '/web/runtime.js'
import { setTtsEngine, getTtsEngineId, onTtsEngineChange } from '/web/tts.js'
import { setImageEngine, getImageEngineId, onImageEngineChange } from '/web/imagen.js'
import { setCodingModel, getCodingModelId, onCodingModelChange } from '/web/codingModel.js'

export const PRESETS = [
  { id: 'build-small', label: 'BUILD SMALL', engine: 'server', model: 'tiny-aya-global-zerogpu', family: 'tiny-aya', voice: 'voxcpm', image: 'klein-zerogpu', coding: 'mellum2-zerogpu', sub: 'Tiny Aya - VoxCPM2 - FLUX.2 klein - Mellum2 (Nemotron NIM fallback)' },
  { id: 'high', label: 'High', engine: 'webllm', model: 'qwen3-0.6b', family: 'qwen3', voice: 'kokoro', image: '', coding: 'mellum2-zerogpu', sub: 'Qwen3 - Kokoro 82M' },
  { id: 'medium', label: 'Medium', engine: 'transformers', model: 'qwen2.5-0.5b', family: 'qwen2.5', voice: 'kitten', image: '', coding: 'mellum2-zerogpu', sub: 'Qwen2.5 - Kitten TTS' },
  { id: 'low', label: 'Low', engine: 'transformers', model: 'qwen2.5-0.5b', family: 'qwen2.5', voice: 'webspeech', image: '', coding: 'mellum2-zerogpu', sub: 'Qwen2.5 - Web Speech' },
]
const KEY = 'tinyarmy.qualityPreset'
const VALID = new Set([...PRESETS.map((p) => p.id), 'custom'])

const byId = (id) => PRESETS.find((p) => p.id === id)

function resolveModel(p) {
  const ms = listModels()
  const m = ms.find((x) => x.id === p.model) || ms.find((x) => x.id.startsWith(p.family))
  return m ? m.id : ''
}

export function detectPreset() {
  const eid = getEngineId()
  const mid = currentModelId()
  const voice = getTtsEngineId()
  const image = getImageEngineId()
  const coding = getCodingModelId()
  const p = PRESETS.find((x) =>
    (!x.engine || eid === x.engine) &&
    (mid === x.model || mid.startsWith(x.family)) &&
    voice === x.voice &&
    (!x.image || image === x.image) &&
    (!x.coding || coding === x.coding))
  return p ? p.id : 'custom'
}

let _applying = false
let _current = (() => {
  let saved = ''
  try { saved = localStorage.getItem(KEY) || '' } catch { /* ignore */ }
  if (!VALID.has(saved)) return detectPreset()
  if (saved === 'custom') return 'custom'
  return detectPreset() === saved ? saved : detectPreset()
})()

export const getActivePreset = () => _current

function commit(id) {
  _current = id
  try { localStorage.setItem(KEY, id) } catch { /* ignore */ }
  for (const fn of _listeners) { try { fn(id) } catch { /* ignore */ } }
}

export function applyPreset(id) {
  const p = byId(id)
  if (!p) return
  _applying = true
  if (p.engine) setEngine(p.engine)
  const mid = resolveModel(p)
  if (mid) setModel(mid)
  setTtsEngine(p.voice)
  if (p.image) setImageEngine(p.image)
  if (p.coding) setCodingModel(p.coding)
  _applying = false
  commit(id)
}

const _listeners = new Set()
export function onPresetChange(fn) { _listeners.add(fn); return () => _listeners.delete(fn) }

const _onManual = () => { if (!_applying) commit('custom') }
onModelChange(_onManual)
onTtsEngineChange(_onManual)
onImageEngineChange(_onManual)
onCodingModelChange(_onManual)