mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-13 00:14:33 +00:00
103 lines
2.7 KiB
Go
103 lines
2.7 KiB
Go
package agent
|
|
|
|
import (
|
|
"sort"
|
|
"strings"
|
|
"unicode"
|
|
|
|
"github.com/nextlevelbuilder/goclaw/internal/config"
|
|
"github.com/nextlevelbuilder/goclaw/internal/skills"
|
|
)
|
|
|
|
func unknownSkillSlashResult(all []skills.Info, target string, cfg config.SkillSlashCommandConfig) skillSlashCommandResult {
|
|
if !cfg.EffectiveSuggestNotFound() {
|
|
return skillSlashCommandResult{Kind: skillSlashCommandNone}
|
|
}
|
|
suggestions := similarSkills(all, target, 3)
|
|
if len(suggestions) == 0 {
|
|
return skillSlashCommandResult{Kind: skillSlashCommandNone}
|
|
}
|
|
return skillSlashCommandResult{
|
|
Kind: skillSlashCommandUnknown,
|
|
Guidance: buildSkillSlashUnknownGuidance(target, suggestions),
|
|
Suggestions: suggestions,
|
|
}
|
|
}
|
|
|
|
func similarSkills(all []skills.Info, target string, limit int) []skills.Info {
|
|
target = strings.ToLower(strings.TrimSpace(target))
|
|
if target == "" {
|
|
return nil
|
|
}
|
|
type scored struct {
|
|
info skills.Info
|
|
score int
|
|
}
|
|
var scoredSkills []scored
|
|
for _, skill := range all {
|
|
best := scoreSimilarSkill(target, skill)
|
|
if best <= 3 {
|
|
scoredSkills = append(scoredSkills, scored{info: skill, score: best})
|
|
}
|
|
}
|
|
sort.Slice(scoredSkills, func(i, j int) bool {
|
|
if scoredSkills[i].score == scoredSkills[j].score {
|
|
return scoredSkills[i].info.Slug < scoredSkills[j].info.Slug
|
|
}
|
|
return scoredSkills[i].score < scoredSkills[j].score
|
|
})
|
|
if len(scoredSkills) > limit {
|
|
scoredSkills = scoredSkills[:limit]
|
|
}
|
|
out := make([]skills.Info, len(scoredSkills))
|
|
for i, scored := range scoredSkills {
|
|
out[i] = scored.info
|
|
}
|
|
return out
|
|
}
|
|
|
|
func scoreSimilarSkill(target string, skill skills.Info) int {
|
|
targetRunes := []rune(target)
|
|
slug := strings.ToLower(skill.Slug)
|
|
best := slashSkillDistance(target, slug)
|
|
if slugRunes := []rune(slug); len(slugRunes) >= len(targetRunes) {
|
|
best = min(best, slashSkillDistance(target, string(slugRunes[:len(targetRunes)])))
|
|
}
|
|
name := strings.ToLower(skill.Name)
|
|
if name != "" {
|
|
best = min(best, slashSkillDistance(target, name))
|
|
if nameRunes := []rune(name); len(nameRunes) >= len(targetRunes) {
|
|
best = min(best, slashSkillDistance(target, string(nameRunes[:len(targetRunes)])))
|
|
}
|
|
}
|
|
if strings.HasPrefix(slug, target) || strings.Contains(name, target) {
|
|
best = 0
|
|
}
|
|
return best
|
|
}
|
|
|
|
func slashSkillDistance(a, b string) int {
|
|
ar := []rune(a)
|
|
br := []rune(b)
|
|
if len(ar) == 0 {
|
|
return len(br)
|
|
}
|
|
prev := make([]int, len(br)+1)
|
|
for j := range prev {
|
|
prev[j] = j
|
|
}
|
|
for i, ca := range ar {
|
|
cur := make([]int, len(br)+1)
|
|
cur[0] = i + 1
|
|
for j, cb := range br {
|
|
cost := 0
|
|
if unicode.ToLower(ca) != unicode.ToLower(cb) {
|
|
cost = 1
|
|
}
|
|
cur[j+1] = min(min(cur[j]+1, prev[j+1]+1), prev[j]+cost)
|
|
}
|
|
prev = cur
|
|
}
|
|
return prev[len(br)]
|
|
}
|