mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-05 06:24:37 +00:00
feat(web): add fuzzy matching for slash commands (#63)
Co-authored-by: Liheng <liheng@example.com>
This commit is contained in:
@@ -5,6 +5,22 @@ import type { SlashCommand } from '@/types/api'
|
||||
import type { Suggestion } from '@/hooks/useActiveSuggestions'
|
||||
import { queryKeys } from '@/lib/query-keys'
|
||||
|
||||
function levenshteinDistance(a: string, b: string): number {
|
||||
if (a.length === 0) return b.length
|
||||
if (b.length === 0) return a.length
|
||||
const matrix: number[][] = []
|
||||
for (let i = 0; i <= b.length; i++) matrix[i] = [i]
|
||||
for (let j = 0; j <= a.length; j++) matrix[0][j] = j
|
||||
for (let i = 1; i <= b.length; i++) {
|
||||
for (let j = 1; j <= a.length; j++) {
|
||||
matrix[i][j] = b[i - 1] === a[j - 1]
|
||||
? matrix[i - 1][j - 1]
|
||||
: Math.min(matrix[i - 1][j - 1] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j] + 1)
|
||||
}
|
||||
}
|
||||
return matrix[b.length][a.length]
|
||||
}
|
||||
|
||||
/**
|
||||
* Built-in slash commands per agent type.
|
||||
* These are shown immediately without waiting for RPC.
|
||||
@@ -78,14 +94,38 @@ export function useSlashCommands(
|
||||
}, [agentType, query.data])
|
||||
|
||||
const getSuggestions = useCallback(async (queryText: string): Promise<Suggestion[]> => {
|
||||
// queryText will be like "/clea" - strip the leading slash
|
||||
const searchTerm = queryText.startsWith('/')
|
||||
? queryText.slice(1).toLowerCase()
|
||||
: queryText.toLowerCase()
|
||||
|
||||
if (!searchTerm) {
|
||||
return commands.map(cmd => ({
|
||||
key: `/${cmd.name}`,
|
||||
text: `/${cmd.name}`,
|
||||
label: `/${cmd.name}`,
|
||||
description: cmd.description ?? (cmd.source === 'user' ? 'Custom command' : undefined),
|
||||
content: cmd.content,
|
||||
source: cmd.source
|
||||
}))
|
||||
}
|
||||
|
||||
const maxDistance = Math.max(2, Math.floor(searchTerm.length / 2))
|
||||
return commands
|
||||
.filter(cmd => cmd.name.toLowerCase().startsWith(searchTerm))
|
||||
.map(cmd => ({
|
||||
.map(cmd => {
|
||||
const name = cmd.name.toLowerCase()
|
||||
let score: number
|
||||
if (name === searchTerm) score = 0
|
||||
else if (name.startsWith(searchTerm)) score = 1
|
||||
else if (name.includes(searchTerm)) score = 2
|
||||
else {
|
||||
const dist = levenshteinDistance(searchTerm, name)
|
||||
score = dist <= maxDistance ? 3 + dist : Infinity
|
||||
}
|
||||
return { cmd, score }
|
||||
})
|
||||
.filter(item => item.score < Infinity)
|
||||
.sort((a, b) => a.score - b.score)
|
||||
.map(({ cmd }) => ({
|
||||
key: `/${cmd.name}`,
|
||||
text: `/${cmd.name}`,
|
||||
label: `/${cmd.name}`,
|
||||
|
||||
Reference in New Issue
Block a user