mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-06-20 17:32:05 -03:00
Adds custom words autocomplete functionality similar to comfyui-custom-scripts, with the following features: Backend (Python): - Create CustomWordsService for CSV parsing and priority-based search - Add API endpoints: GET/POST /api/lm/custom-words and GET /api/lm/custom-words/search - Share storage with pysssss plugin (checks for their user/autocomplete.txt first) - Fallback to Lora Manager's user directory for storage Frontend (JavaScript/Vue): - Add 'custom_words' and 'prompt' model types to autocomplete system - Prompt node now supports dual-mode autocomplete: * Type 'emb:' prefix → search embeddings * Type normally → search custom words (no prefix required) - Add AUTOCOMPLETE_TEXT_PROMPT widget type - Update Vue component and composable types Key Features: - CSV format: word[,priority] compatible with danbooru-tags.txt - Priority-based sorting: 20% top priority + prefix + include matches - Preview tooltip for embeddings (not for custom words) - Dynamic endpoint switching based on prefix detection Breaking Changes: - Prompt (LoraManager) node widget type changed from AUTOCOMPLETE_TEXT_EMBEDDINGS to AUTOCOMPLETE_TEXT_PROMPT - Removed standalone web/comfyui/prompt.js (integrated into main widgets) Fixes comfy_dir path calculation by prioritizing folder_paths.base_path from ComfyUI when available, with fallback to computed path.
111 lines
2.8 KiB
TypeScript
111 lines
2.8 KiB
TypeScript
import { ref, onMounted, onUnmounted, type Ref } from 'vue'
|
|
|
|
// Dynamic import type for AutoComplete class
|
|
type AutoCompleteClass = new (
|
|
inputElement: HTMLTextAreaElement,
|
|
modelType: 'loras' | 'embeddings' | 'custom_words' | 'prompt',
|
|
options?: AutocompleteOptions
|
|
) => AutoCompleteInstance
|
|
|
|
interface AutocompleteOptions {
|
|
maxItems?: number
|
|
minChars?: number
|
|
debounceDelay?: number
|
|
showPreview?: boolean
|
|
}
|
|
|
|
interface AutoCompleteInstance {
|
|
destroy: () => void
|
|
isValid: () => boolean
|
|
refreshCaretHelper: () => void
|
|
}
|
|
|
|
export interface UseAutocompleteOptions {
|
|
showPreview?: boolean
|
|
maxItems?: number
|
|
minChars?: number
|
|
debounceDelay?: number
|
|
}
|
|
|
|
export function useAutocomplete(
|
|
textareaRef: Ref<HTMLTextAreaElement | null>,
|
|
modelType: 'loras' | 'embeddings' | 'custom_words' | 'prompt' = 'loras',
|
|
options: UseAutocompleteOptions = {}
|
|
) {
|
|
const autocompleteInstance = ref<AutoCompleteInstance | null>(null)
|
|
const isInitialized = ref(false)
|
|
|
|
const defaultOptions: AutocompleteOptions = {
|
|
maxItems: 20,
|
|
minChars: 1,
|
|
debounceDelay: 200,
|
|
showPreview: true,
|
|
...options
|
|
}
|
|
|
|
const initAutocomplete = async () => {
|
|
if (!textareaRef.value) {
|
|
console.warn('[useAutocomplete] Textarea ref is null, cannot initialize')
|
|
return
|
|
}
|
|
|
|
if (autocompleteInstance.value) {
|
|
console.log('[useAutocomplete] Already initialized, skipping')
|
|
return
|
|
}
|
|
|
|
try {
|
|
// Dynamically import the AutoComplete class
|
|
const module = await import(/* @vite-ignore */ `${'../autocomplete.js'}`)
|
|
const AutoComplete: AutoCompleteClass = module.AutoComplete
|
|
|
|
autocompleteInstance.value = new AutoComplete(
|
|
textareaRef.value,
|
|
modelType,
|
|
defaultOptions
|
|
)
|
|
isInitialized.value = true
|
|
console.log(`[useAutocomplete] Initialized for ${modelType}`)
|
|
} catch (error) {
|
|
console.error('[useAutocomplete] Failed to initialize:', error)
|
|
}
|
|
}
|
|
|
|
const destroyAutocomplete = () => {
|
|
if (autocompleteInstance.value) {
|
|
autocompleteInstance.value.destroy()
|
|
autocompleteInstance.value = null
|
|
isInitialized.value = false
|
|
console.log('[useAutocomplete] Destroyed')
|
|
}
|
|
}
|
|
|
|
const refreshCaretHelper = () => {
|
|
if (autocompleteInstance.value) {
|
|
autocompleteInstance.value.refreshCaretHelper()
|
|
}
|
|
}
|
|
|
|
onMounted(() => {
|
|
// Initialize autocomplete after component is mounted
|
|
// Use nextTick-like delay to ensure DOM is fully ready
|
|
setTimeout(() => {
|
|
initAutocomplete()
|
|
}, 0)
|
|
})
|
|
|
|
onUnmounted(() => {
|
|
destroyAutocomplete()
|
|
})
|
|
|
|
return {
|
|
autocompleteInstance,
|
|
isInitialized,
|
|
initAutocomplete,
|
|
destroyAutocomplete,
|
|
refreshCaretHelper
|
|
}
|
|
}
|
|
|
|
export type UseAutocompleteReturn = ReturnType<typeof useAutocomplete>
|