| 96 | }, |
| 97 | |
| 98 | async loadAll() { |
| 99 | // LoRAs: [{name, alias, path, metadata}, ...] |
| 100 | const loraData = await this.fetchJson('/loras'); |
| 101 | if (Array.isArray(loraData)) { |
| 102 | const items: XnItem[] = []; |
| 103 | for (const lo of loraData) { |
| 104 | if (typeof lo === 'object' && lo && 'name' in lo && typeof lo.name === 'string') items.push({ name: lo.name }); |
| 105 | if (typeof lo === 'object' && lo && 'alias' in lo && typeof lo.alias === 'string' && lo.alias !== (lo as { name?: string }).name) items.push({ name: lo.alias }); |
| 106 | } |
| 107 | this.lora = new XnIndex(items); |
| 108 | } |
| 109 | // Embeddings: {loaded: [...], skipped: [...]} |
| 110 | const embData = await this.fetchJson('/embeddings') as Record<string, unknown> | null; |
| 111 | if (embData && typeof embData === 'object') { |
| 112 | const loaded = Array.isArray(embData.loaded) ? embData.loaded : []; |
| 113 | this.embed = new XnIndex(loaded.map((name) => ({ name: String(name) }))); |
| 114 | } |
| 115 | // Wildcards: [{name}, ...] |
| 116 | const wcData = await this.fetchJson('/wildcards'); |
| 117 | if (Array.isArray(wcData)) { |
| 118 | this.wildcard = new XnIndex( |
| 119 | wcData |
| 120 | .filter((w) => typeof w === 'object' && w && 'name' in w && typeof w.name === 'string') |
| 121 | .map((w) => ({ name: w.name })), |
| 122 | ); |
| 123 | } |
| 124 | log('autoComplete', { |
| 125 | xnLoaded: true, |
| 126 | lora: this.lora.items.length, |
| 127 | embed: this.embed.items.length, |
| 128 | wildcard: this.wildcard.items.length, |
| 129 | }); |
| 130 | }, |
| 131 | |
| 132 | searchLoras(prefix, limit = 20) { |
| 133 | return this.lora.search(prefix, limit).map((item) => ({ ...item, kind: 'lora' as const })); |