Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 | 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 147x 145x 145x 21x 145x 15x 15x 145x 132x 132x 1x 14x 14x 1x 167x 167x 1x 32x 32x 22x 22x 22x 2x 22x 20x 20x 22x 32x 32x 1x 19x 19x 1x 20x 13x 13x 13x 13x 13x 7x 7x 7x 7x 7x 1x 5x 5x 1x 418x 418x 1x 13x 13x 13x 24x 2x 2x 2x 2x 22x 13x 13x 13x 1x 11x 11x 1x 11x 11x 1x 7x 7x 6x 6x 6x 6x 3x 3x 4x 2x 2x 2x 6x 6x | import type { App } from "obsidian";
import type { CatalogEntry, CatalogTab, KeyStatus, ModelTask } from "../types";
import { CATALOG_SOURCE, CATALOG_TAB, KEY_STATUS, MODEL_TASK } from "../types";
import { MESSAGES } from "../locales/en";
const DISCOVER_RAIL_LIMIT = 12;
const TASK_TO_TAB: Record<ModelTask, CatalogTab> = {
[MODEL_TASK.CHAT]: CATALOG_TAB.CHAT,
[MODEL_TASK.EMBEDDING]: CATALOG_TAB.EMBED,
[MODEL_TASK.VISION]: CATALOG_TAB.VISION,
[MODEL_TASK.RERANK]: CATALOG_TAB.RERANK,
};
const TAB_TO_TASK: Partial<Record<CatalogTab, ModelTask>> = {
[CATALOG_TAB.CHAT]: MODEL_TASK.CHAT,
[CATALOG_TAB.EMBED]: MODEL_TASK.EMBEDDING,
[CATALOG_TAB.VISION]: MODEL_TASK.VISION,
[CATALOG_TAB.RERANK]: MODEL_TASK.RERANK,
};
export const KEY_STATUS_PILL_CLASS = {
READY: "lilbee-key-status-pill-ready",
NEEDS_KEY: "lilbee-key-status-pill-needs-key",
} as const;
/** Catalog modal and model picker hide frontier rows entirely until this is true. */
export function hasReadyFrontierRow(rows: CatalogEntry[]): boolean {
for (const row of rows) {
if (
row.source === CATALOG_SOURCE.FRONTIER &&
(row as CatalogEntry & { key_status?: KeyStatus }).key_status === KEY_STATUS.READY
) {
return true;
}
}
return false;
}
export function frontierRowsOnly(rows: CatalogEntry[]): CatalogEntry[] {
return rows.filter((row) => row.source === CATALOG_SOURCE.FRONTIER);
}
export function localRowsOnly(rows: CatalogEntry[]): CatalogEntry[] {
return rows.filter((row) => row.source !== CATALOG_SOURCE.FRONTIER);
}
export function groupByProvider(rows: CatalogEntry[]): [string, CatalogEntry[]][] {
const groups = new Map<string, CatalogEntry[]>();
for (const row of rows) {
const provider = (row as CatalogEntry & { provider?: string }).provider ?? "";
const existing = groups.get(provider);
if (existing) {
existing.push(row);
} else {
groups.set(provider, [row]);
}
}
return [...groups.entries()];
}
export function renderProviderPill(parent: HTMLElement, provider: string): HTMLElement {
return parent.createSpan({ cls: "lilbee-provider-pill", text: provider });
}
export function renderKeyStatusPill(parent: HTMLElement, status: KeyStatus): HTMLElement {
if (status === KEY_STATUS.READY) {
return parent.createSpan({
cls: `lilbee-key-status-pill ${KEY_STATUS_PILL_CLASS.READY}`,
text: MESSAGES.PILL_KEY_READY,
});
}
return parent.createSpan({
cls: `lilbee-key-status-pill ${KEY_STATUS_PILL_CLASS.NEEDS_KEY}`,
text: MESSAGES.PILL_KEY_NEEDS_KEY,
});
}
export function taskToTabId(task: ModelTask): CatalogTab {
return TASK_TO_TAB[task];
}
export function tabIdToTask(tab: CatalogTab): ModelTask | null {
return TAB_TO_TASK[tab] ?? null;
}
/**
* Featured-first ordering, capped at 12. When the user has an active chat
* model the chat-task entries float to the top so the rail leads with rows
* matching what they're already using.
*/
export function forYouRail(entries: CatalogEntry[], activeChatModelRef: string): CatalogEntry[] {
const featured = entries.filter((e) => e.featured);
const preferChat = activeChatModelRef !== "";
const sorted = [...featured].sort((a, b) => {
if (preferChat) {
const aChat = a.task === MODEL_TASK.CHAT ? 0 : 1;
const bChat = b.task === MODEL_TASK.CHAT ? 0 : 1;
if (aChat !== bChat) return aChat - bChat;
}
return b.downloads - a.downloads;
});
return sorted.slice(0, DISCOVER_RAIL_LIMIT);
}
export function yourCollectionRail(entries: CatalogEntry[]): CatalogEntry[] {
return entries.filter((e) => e.installed);
}
export function freshRail(entries: CatalogEntry[]): CatalogEntry[] {
return [...entries].sort((a, b) => b.downloads - a.downloads).slice(0, DISCOVER_RAIL_LIMIT);
}
export function deepLinkToApiKeySettings(app: App, provider: string): void {
const settingApi = (app as App & { setting?: { open(): void; openTabById(id: string): void } }).setting;
if (!settingApi) return;
settingApi.open();
settingApi.openTabById("lilbee");
setTimeout(() => {
// Timer can fire after the modal closes; Node test envs don't have a global document.
if (typeof document === "undefined") return;
const escaped = provider.toLowerCase().replace(/[^a-z0-9-]/g, "-");
const target = document.querySelector(`[data-lilbee-api-key="${escaped}"]`);
if (target instanceof HTMLElement) {
target.scrollIntoView({ behavior: "smooth", block: "center" });
target.focus();
}
}, 50);
}
|