- 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
126 lines
4.7 KiB
AutoHotkey
126 lines
4.7 KiB
AutoHotkey
; 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
|
|
}
|