- PromptStore/Frontmatter : un prompt = un fichier Markdown, en-tête au sous-ensemble strict du document d'architecture (§7), écriture UTF-8 - VariableEngine : variables typées + automatiques (selection, clipboard, date, app, cursor), résolution et profils de destination - Injector/ClipboardGuard : focus capturé/rendu, presse-papiers sauvegardé puis restauré conditionnellement, aucun envoi automatique par défaut - Search : classement flou insensible aux accents, pondéré par l'usage - UsageStats (usage.json), HotkeyManager (conflits signalés, jamais muets) - Settings : config.ini + profils par IA (claude, chatgpt, qwen, terminal…) - PromptKey.ahk : tray, instance unique, capture du contexte, surveillance de la bibliothèque, installation du pack au premier lancement
130 lines
4.2 KiB
AutoHotkey
130 lines
4.2 KiB
AutoHotkey
; Search.ahk — Recherche floue et classement (architecture §14 et Annexe B).
|
|
; Fonction pure : score déterministe, insensible aux accents, pondéré par l'usage.
|
|
|
|
class Search {
|
|
; usage : Map id -> Map(count, last_used) (peut être vide)
|
|
static Filter(prompts, query, usage) {
|
|
q := Util.Normalize(Trim(query))
|
|
scored := []
|
|
if (q = "") {
|
|
for p in prompts
|
|
scored.Push(Map("prompt", p, "score", Search.UsageWeight(p, usage)))
|
|
} else {
|
|
words := StrSplit(q, " ")
|
|
for p in prompts {
|
|
total := 0
|
|
ok := true
|
|
hayTitle := Util.Normalize(p.Title)
|
|
hayTags := Util.Normalize(Join(p.Tags, " "))
|
|
hayCat := Util.Normalize(p.Category)
|
|
hayBody := Util.Normalize(SubStr(p.Body, 1, 2000))
|
|
for w in words {
|
|
if (w = "")
|
|
continue
|
|
s := Search.WordScore(w, hayTitle, hayTags, hayCat, hayBody)
|
|
if (s = 0) {
|
|
ok := false
|
|
break
|
|
}
|
|
total += s
|
|
}
|
|
if ok
|
|
scored.Push(Map("prompt", p, "score", total * Search.UsageWeight(p, usage)))
|
|
}
|
|
}
|
|
; Tri décroissant par score, égalité : dernier usage puis titre (déterminisme, Annexe B).
|
|
SortScored(scored, usage)
|
|
out := []
|
|
for entry in scored
|
|
out.Push(entry["prompt"])
|
|
return out
|
|
}
|
|
|
|
static WordScore(w, title, tags, cat, body) {
|
|
best := 0
|
|
best := Max(best, Search.FieldScore(w, title) * 4)
|
|
best := Max(best, Search.FieldScore(w, tags) * 2)
|
|
best := Max(best, Search.FieldScore(w, cat) * 2)
|
|
best := Max(best, Search.FieldScore(w, body))
|
|
return best
|
|
}
|
|
|
|
; Score d'un champ : sous-séquence floue ; bonus début de mot et mot entier.
|
|
static FieldScore(w, field) {
|
|
if (field = "")
|
|
return 0
|
|
if InStr(field, w) {
|
|
score := 50
|
|
; Mot entier ou début de champ / de mot.
|
|
if (SubStr(field, 1, StrLen(w)) = w)
|
|
score += 50
|
|
if InStr(field, " " w)
|
|
score += 40
|
|
if InStr(field, w " ") || InStr(field, " " w " ")
|
|
score += 30
|
|
return score
|
|
}
|
|
; Sous-séquence : toutes les lettres de w, dans l'ordre.
|
|
pos := 1
|
|
loop parse w {
|
|
found := InStr(field, A_LoopField, , pos)
|
|
if (!found)
|
|
return 0
|
|
pos := found + 1
|
|
}
|
|
return 15
|
|
}
|
|
|
|
static UsageWeight(p, usage) {
|
|
wgt := 1.0
|
|
if (p.Pinned > 0)
|
|
wgt *= 1.5
|
|
if (p.Favorite)
|
|
wgt *= 1.3
|
|
if (usage is Map && usage.Has(p.Id)) {
|
|
u := usage[p.Id]
|
|
count := u.Has("count") ? Integer(u["count"]) : 0
|
|
if (count > 0)
|
|
wgt *= 1 + (Log(1 + count) / Log(10)) * 0.2
|
|
if u.Has("last_used") {
|
|
age := DateDiff(A_Now, u["last_used"], "Days")
|
|
if (age <= 7)
|
|
wgt *= 1.2
|
|
}
|
|
}
|
|
return wgt
|
|
}
|
|
}
|
|
|
|
; Tri simple par insertion (les listes sont petites ; la stabilité prime sur la vitesse).
|
|
SortScored(scored, usage) {
|
|
n := scored.Length
|
|
loop n - 1 {
|
|
i := A_Index
|
|
loop n - i {
|
|
j := A_Index
|
|
a := scored[j], b := scored[j + 1]
|
|
swap := false
|
|
if (b["score"] > a["score"])
|
|
swap := true
|
|
else if (b["score"] = a["score"]) {
|
|
la := LastUsedOf(a["prompt"], usage), lb := LastUsedOf(b["prompt"], usage)
|
|
if (lb > la)
|
|
swap := true
|
|
else if (lb = la && StrCompare(a["prompt"].Title, b["prompt"].Title) > 0)
|
|
swap := true
|
|
}
|
|
if swap {
|
|
scored[j] := b
|
|
scored[j + 1] := a
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
LastUsedOf(p, usage) {
|
|
if (usage is Map && usage.Has(p.Id) && usage[p.Id].Has("last_used"))
|
|
return usage[p.Id]["last_used"]
|
|
return ""
|
|
}
|