feat(core): moteur PromptKey — bibliothèque .md, variables, injection, raccourcis
- 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
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
; VariableEngine.ahk — Variables des prompts : automatiques et manuelles (architecture §10).
|
||||
|
||||
class VariableEngine {
|
||||
static AutoNames := ["selection", "clipboard", "date", "datetime", "time", "app", "cursor"]
|
||||
|
||||
static IsAuto(name) {
|
||||
return HasInArray(VariableEngine.AutoNames, StrLower(name))
|
||||
}
|
||||
|
||||
; Toutes les variables {{...}} d'un corps, dans l'ordre d'apparition, dédupliquées.
|
||||
static FindVars(body) {
|
||||
found := []
|
||||
seen := Map()
|
||||
pos := 1
|
||||
while (m := RegExMatch(body, "\{\{\s*([A-Za-z0-9_]+)\s*\}\}", &match, pos)) {
|
||||
name := StrLower(match[1])
|
||||
if !seen.Has(name) {
|
||||
seen[name] := true
|
||||
found.Push(name)
|
||||
}
|
||||
pos := m + match.Len[0]
|
||||
}
|
||||
return found
|
||||
}
|
||||
|
||||
; Variables manuelles requises : celles du corps qui ne sont pas automatiques.
|
||||
; Retourne [ Map {name, label, type, choices, default, required, multiline, declared} ].
|
||||
static ManualVars(prompt) {
|
||||
declared := Map()
|
||||
for v in prompt.Variables() {
|
||||
if v.Has("name")
|
||||
declared[StrLower(String(v["name"]))] := v
|
||||
}
|
||||
out := []
|
||||
for name in VariableEngine.FindVars(prompt.Body) {
|
||||
if VariableEngine.IsAuto(name)
|
||||
continue
|
||||
if declared.Has(name) {
|
||||
d := declared[name]
|
||||
out.Push(Map(
|
||||
"name", name,
|
||||
"label", d.Has("label") && d["label"] != "" ? String(d["label"]) : name,
|
||||
"type", d.Has("type") ? String(d["type"]) : "text",
|
||||
"choices", d.Has("choices") && (d["choices"] is Array) ? d["choices"] : [],
|
||||
"default", d.Has("default") ? String(d["default"]) : "",
|
||||
"required", d.Has("required") ? Truthy(d["required"]) : 0,
|
||||
"multiline", d.Has("multiline") ? Truthy(d["multiline"]) : 0,
|
||||
"declared", 1))
|
||||
} else {
|
||||
out.Push(Map("name", name, "label", name, "type", "text", "choices", [], "default", "", "required", 0, "multiline", 0, "declared", 0))
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
; Valeurs automatiques à partir du contexte d'invocation (figées à l'invocation, §10.3).
|
||||
static AutoValues(ctx) {
|
||||
vals := Map()
|
||||
vals["selection"] := ctx.Has("selection") ? ctx["selection"] : ""
|
||||
vals["clipboard"] := ctx.Has("clipboardText") ? ctx["clipboardText"] : ""
|
||||
vals["date"] := FormatTime(, "yyyy-MM-dd")
|
||||
vals["datetime"] := FormatTime(, "yyyy-MM-dd HH:mm")
|
||||
vals["time"] := FormatTime(, "HH:mm")
|
||||
vals["app"] := ctx.Has("process") ? ctx["process"] : ""
|
||||
return vals
|
||||
}
|
||||
|
||||
; Résolution (§10.5). Retourne Map { text, unresolved: [], cursorFromEnd: Integer }.
|
||||
static Resolve(body, manualValues, ctx) {
|
||||
auto := VariableEngine.AutoValues(ctx)
|
||||
text := body
|
||||
unresolved := []
|
||||
; Remplacement variable par variable (toutes occurrences).
|
||||
for name in VariableEngine.FindVars(body) {
|
||||
token := ""
|
||||
if (name = "cursor")
|
||||
continue
|
||||
if auto.Has(name)
|
||||
value := auto[name]
|
||||
else if manualValues.Has(name)
|
||||
value := String(manualValues[name])
|
||||
else {
|
||||
unresolved.Push(name)
|
||||
continue
|
||||
}
|
||||
text := RegExReplace(text, "\{\{\s*" name "\s*\}\}", VariableEngine.EscapeReplacement(value))
|
||||
}
|
||||
; Marqueur de curseur : on retient la distance à la fin, puis on retire le marqueur.
|
||||
cursorFromEnd := 0
|
||||
if RegExMatch(text, "\{\{\s*cursor\s*\}\}", &cm) {
|
||||
cursorFromEnd := StrLen(text) - (cm.Pos[0] + cm.Len[0] - 1)
|
||||
text := StrReplace(text, cm[0], "")
|
||||
}
|
||||
return Map("text", text, "unresolved", unresolved, "cursorFromEnd", cursorFromEnd)
|
||||
}
|
||||
|
||||
; Dans RegExReplace, $ a un sens spécial : on le neutralise pour insérer le texte tel quel.
|
||||
static EscapeReplacement(value) {
|
||||
return StrReplace(value, "$", "$$")
|
||||
}
|
||||
|
||||
; Enveloppe du profil (§12.2) : préambule + corps + postambule.
|
||||
static ApplyProfile(text, profile) {
|
||||
out := text
|
||||
pre := profile.Has("preambule") ? profile["preambule"] : ""
|
||||
post := profile.Has("postambule") ? profile["postambule"] : ""
|
||||
if (pre != "")
|
||||
out := pre "`n`n" out
|
||||
if (post != "")
|
||||
out := out "`n`n" post
|
||||
return out
|
||||
}
|
||||
}
|
||||
|
||||
; Vrai pour 1, "1", "true" (insensible à la casse) — valeurs lues depuis l'en-tête.
|
||||
Truthy(v) {
|
||||
if (v = 1)
|
||||
return 1
|
||||
if (v is String) {
|
||||
lv := StrLower(v)
|
||||
if (lv = "true" || lv = "1" || lv = "oui")
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
Reference in New Issue
Block a user