noosphere 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +72 -3
- package/dist/index.cjs +190 -34
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +190 -34
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,11 +6,14 @@ One import. Every model. Every modality.
|
|
|
6
6
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
9
|
-
- **
|
|
9
|
+
- **7 modalities** — LLM, image, video, TTS, STT, music, and embeddings
|
|
10
10
|
- **Always up-to-date models** — Dynamic auto-fetch from ALL provider APIs at runtime (OpenAI, Anthropic, Google, Groq, Mistral, xAI, Cerebras, OpenRouter)
|
|
11
|
+
- **Dynamic descriptions** — Model descriptions fetched from source (Ollama library, HuggingFace READMEs, CivitAI API) — no hardcoded strings
|
|
12
|
+
- **Modality-filtered sync** — `syncModels('llm')` only fetches LLM providers, avoiding unnecessary requests
|
|
11
13
|
- **867+ media endpoints** — via FAL (Flux, SDXL, Kling, Sora 2, VEO 3, Kokoro, ElevenLabs, and hundreds more)
|
|
12
14
|
- **30+ HuggingFace tasks** — LLM, image, TTS, translation, summarization, classification, and more
|
|
13
|
-
- **Local-first architecture** — Auto-detects ComfyUI,
|
|
15
|
+
- **Local-first architecture** — Auto-detects Ollama, ComfyUI, Whisper, AudioCraft, Piper, and Kokoro on your machine
|
|
16
|
+
- **Org-aware logos** — HuggingFace models show the real org logo (Meta, Google, NVIDIA) instead of generic HF logo
|
|
14
17
|
- **Agentic capabilities** — Tool use, function calling, reasoning/thinking, vision, and agent loops via Pi-AI
|
|
15
18
|
- **Failover & retry** — Automatic retries with exponential backoff and cross-provider failover
|
|
16
19
|
- **Usage tracking** — Real-time cost, latency, and token tracking across all providers
|
|
@@ -366,13 +369,45 @@ await ai.uninstallModel('deepseek-r1:14b');
|
|
|
366
369
|
|---|---|---|---|---|
|
|
367
370
|
| **pi-ai** | LLM | 482 | OpenAI, Anthropic, Google, Groq, Mistral, xAI, OpenRouter, Cerebras | API keys |
|
|
368
371
|
| **ollama** | LLM, embedding | 70 | 38 installed + 32 from Ollama web catalog | `localhost:11434` |
|
|
369
|
-
| **hf-local** | image, video, tts, stt | 220 | HuggingFace catalog (FLUX, SDXL, Wan2.2, Whisper, MusicGen) | Always |
|
|
372
|
+
| **hf-local** | image, video, tts, stt, music | 220 | HuggingFace catalog (FLUX, SDXL, Wan2.2, Whisper, MusicGen) | Always (no API key) |
|
|
373
|
+
| **huggingface** | LLM, image, tts | dynamic | HuggingFace Inference API | `HUGGINGFACE_TOKEN` |
|
|
370
374
|
| **comfyui** | image, video | dynamic | Installed checkpoints + CivitAI catalog | `localhost:8188` |
|
|
371
375
|
| **openai-compat** | LLM | dynamic | llama.cpp, LM Studio, vLLM, LocalAI, KoboldCpp, Jan, TabbyAPI | Scans ports |
|
|
376
|
+
| **fal** | image, video, tts | 867+ | FAL.ai (Flux, SDXL, Kling, Sora 2, Kokoro, ElevenLabs) | `FAL_KEY` |
|
|
372
377
|
| **piper** | TTS | 2+ | Piper voices installed locally | Binary detection |
|
|
373
378
|
| **whisper-local** | STT | 8 | Whisper/Faster-Whisper (tiny → large-v3) | Python detection |
|
|
374
379
|
| **audiocraft** | music | 5 | MusicGen (small/medium/large/melody) + AudioGen | Python detection |
|
|
375
380
|
|
|
381
|
+
### Modality-Filtered Sync — Only Fetch What You Need
|
|
382
|
+
|
|
383
|
+
Sync **only the providers relevant to a specific modality** instead of fetching everything. This avoids unnecessary network requests (e.g., fetching 270+ HuggingFace READMEs when you only need LLMs).
|
|
384
|
+
|
|
385
|
+
```typescript
|
|
386
|
+
// Sync only LLM providers (Ollama, pi-ai, openai-compat, huggingface)
|
|
387
|
+
await ai.syncModels('llm');
|
|
388
|
+
|
|
389
|
+
// Sync only image providers (hf-local, comfyui, fal, huggingface)
|
|
390
|
+
await ai.syncModels('image');
|
|
391
|
+
|
|
392
|
+
// Sync only STT providers (whisper-local, hf-local)
|
|
393
|
+
await ai.syncModels('stt');
|
|
394
|
+
|
|
395
|
+
// Sync everything (backward compatible)
|
|
396
|
+
await ai.syncModels();
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
**Which providers sync for each modality:**
|
|
400
|
+
|
|
401
|
+
| Modality | Providers Synced |
|
|
402
|
+
|---|---|
|
|
403
|
+
| `llm` | pi-ai, ollama, openai-compat, huggingface (cloud, needs API key) |
|
|
404
|
+
| `image` | hf-local, comfyui, fal, huggingface (cloud) |
|
|
405
|
+
| `video` | hf-local, comfyui, fal |
|
|
406
|
+
| `tts` | hf-local (speech models), fal, piper, kokoro, huggingface (cloud) |
|
|
407
|
+
| `stt` | hf-local, whisper-local |
|
|
408
|
+
| `music` | hf-local (MusicGen, AudioLDM, etc.), audiocraft |
|
|
409
|
+
| `embedding` | ollama |
|
|
410
|
+
|
|
376
411
|
### Models by Modality
|
|
377
412
|
|
|
378
413
|
```typescript
|
|
@@ -478,6 +513,38 @@ const comfyModels = models.filter(m => m.provider === 'comfyui');
|
|
|
478
513
|
const civitai = comfyModels.filter(m => m.status === 'available');
|
|
479
514
|
```
|
|
480
515
|
|
|
516
|
+
### Model Descriptions — Dynamic from Source
|
|
517
|
+
|
|
518
|
+
Every model includes a `description` field fetched dynamically from its source — no hardcoded strings:
|
|
519
|
+
|
|
520
|
+
```typescript
|
|
521
|
+
const models = await ai.getModels('llm');
|
|
522
|
+
|
|
523
|
+
for (const m of models) {
|
|
524
|
+
console.log(m.name, m.description);
|
|
525
|
+
// "llama3.1" "Llama 3.1 is a new state-of-the-art model from Meta available in 8B, 70B and 405B"
|
|
526
|
+
// "qwen3" "Qwen3 is the latest generation of large language models in Qwen series"
|
|
527
|
+
// "gemma3" "The current, most capable model that runs on a single GPU"
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
const imageModels = await ai.getModels('image');
|
|
531
|
+
for (const m of imageModels) {
|
|
532
|
+
console.log(m.name, m.description);
|
|
533
|
+
// "stable-diffusion-xl-base-1.0" "Stable Diffusion XL (SDXL) is a latent text-to-image..."
|
|
534
|
+
// "FLUX.1-dev" "FLUX.1 [dev] is a 12 billion parameter rectified flow..."
|
|
535
|
+
}
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
| Provider | Description Source |
|
|
539
|
+
|---|---|
|
|
540
|
+
| **Ollama** | Scraped from `ollama.com/library` page |
|
|
541
|
+
| **HuggingFace Local** | Parsed from each model's `README.md` on HuggingFace Hub |
|
|
542
|
+
| **CivitAI/ComfyUI** | Extracted from CivitAI API response |
|
|
543
|
+
| **Whisper** | Parsed from OpenAI's Whisper README on HuggingFace |
|
|
544
|
+
| **AudioCraft** | Parsed from Meta's AudioCraft README on HuggingFace |
|
|
545
|
+
|
|
546
|
+
All description fetches are **parallel and fail-safe** — if a source is unreachable, models are returned without descriptions. No API keys required.
|
|
547
|
+
|
|
481
548
|
### Model Status & Local Info
|
|
482
549
|
|
|
483
550
|
Every local model includes rich metadata:
|
|
@@ -486,6 +553,8 @@ Every local model includes rich metadata:
|
|
|
486
553
|
interface ModelInfo {
|
|
487
554
|
id: string;
|
|
488
555
|
provider: string;
|
|
556
|
+
name: string;
|
|
557
|
+
description?: string; // Dynamic from source (Ollama library, HF README, CivitAI)
|
|
489
558
|
modality: 'llm' | 'image' | 'video' | 'tts' | 'stt' | 'music' | 'embedding';
|
|
490
559
|
status?: 'installed' | 'available' | 'downloading' | 'running' | 'error';
|
|
491
560
|
local: boolean;
|
package/dist/index.cjs
CHANGED
|
@@ -342,20 +342,22 @@ var Registry = class {
|
|
|
342
342
|
const cached = this.modelCache.get(provider);
|
|
343
343
|
return cached?.models.find((m) => m.id === modelId) ?? null;
|
|
344
344
|
}
|
|
345
|
-
async syncProvider(providerId) {
|
|
345
|
+
async syncProvider(providerId, modality) {
|
|
346
346
|
const provider = this.providers.get(providerId);
|
|
347
347
|
if (!provider) return 0;
|
|
348
|
-
|
|
348
|
+
if (modality && !provider.modalities.includes(modality)) return 0;
|
|
349
|
+
const models = await provider.listModels(modality);
|
|
349
350
|
this.modelCache.set(providerId, { models, syncedAt: Date.now() });
|
|
350
351
|
return models.length;
|
|
351
352
|
}
|
|
352
|
-
async syncAll() {
|
|
353
|
+
async syncAll(modality) {
|
|
353
354
|
const byProvider = {};
|
|
354
355
|
const errors = [];
|
|
355
356
|
let synced = 0;
|
|
356
357
|
for (const provider of this.providers.values()) {
|
|
358
|
+
if (modality && !provider.modalities.includes(modality)) continue;
|
|
357
359
|
try {
|
|
358
|
-
const count = await this.syncProvider(provider.id);
|
|
360
|
+
const count = await this.syncProvider(provider.id, modality);
|
|
359
361
|
byProvider[provider.id] = count;
|
|
360
362
|
synced += count;
|
|
361
363
|
} catch (err) {
|
|
@@ -1140,6 +1142,7 @@ var ComfyUIProvider = class {
|
|
|
1140
1142
|
id: `civitai-${item.id}`,
|
|
1141
1143
|
provider: "comfyui",
|
|
1142
1144
|
name: item.name ?? `CivitAI Model ${item.id}`,
|
|
1145
|
+
description: item.description ? item.description.replace(/<[^>]+>/g, "").trim().slice(0, 300) || void 0 : void 0,
|
|
1143
1146
|
modality: "image",
|
|
1144
1147
|
local: true,
|
|
1145
1148
|
cost: { price: 0, unit: "free" },
|
|
@@ -1578,6 +1581,28 @@ async function fetchJson(url, options) {
|
|
|
1578
1581
|
clearTimeout(timer);
|
|
1579
1582
|
}
|
|
1580
1583
|
}
|
|
1584
|
+
async function fetchOllamaDescriptions() {
|
|
1585
|
+
const controller = new AbortController();
|
|
1586
|
+
const timer = setTimeout(() => controller.abort(), 5e3);
|
|
1587
|
+
try {
|
|
1588
|
+
const res = await fetch("https://ollama.com/library", { signal: controller.signal });
|
|
1589
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
1590
|
+
const html = await res.text();
|
|
1591
|
+
const descriptions = /* @__PURE__ */ new Map();
|
|
1592
|
+
const cardRegex = /href="\/library\/([^"]+)"[\s\S]*?<p[^>]*>([\s\S]*?)<\/p>/g;
|
|
1593
|
+
let match;
|
|
1594
|
+
while ((match = cardRegex.exec(html)) !== null) {
|
|
1595
|
+
const modelName = match[1].trim();
|
|
1596
|
+
const desc = match[2].replace(/<[^>]*>/g, "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'").replace(/\s+/g, " ").trim();
|
|
1597
|
+
if (modelName && desc) {
|
|
1598
|
+
descriptions.set(modelName, desc);
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
return descriptions;
|
|
1602
|
+
} finally {
|
|
1603
|
+
clearTimeout(timer);
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1581
1606
|
var OllamaProvider = class {
|
|
1582
1607
|
id = "ollama";
|
|
1583
1608
|
name = "Ollama (Local)";
|
|
@@ -1601,10 +1626,11 @@ var OllamaProvider = class {
|
|
|
1601
1626
|
}
|
|
1602
1627
|
async listModels(_modality) {
|
|
1603
1628
|
if (_modality && _modality !== "llm") return [];
|
|
1604
|
-
const [localData, catalogData, runningData] = await Promise.all([
|
|
1629
|
+
const [localData, catalogData, runningData, descriptions] = await Promise.all([
|
|
1605
1630
|
fetchJson(`${this.baseUrl}/api/tags`, { timeoutMs: 5e3 }).catch(() => null),
|
|
1606
1631
|
fetchJson("https://ollama.com/api/tags", { timeoutMs: 5e3 }).catch(() => null),
|
|
1607
|
-
fetchJson(`${this.baseUrl}/api/ps`, { timeoutMs: 5e3 }).catch(() => null)
|
|
1632
|
+
fetchJson(`${this.baseUrl}/api/ps`, { timeoutMs: 5e3 }).catch(() => null),
|
|
1633
|
+
fetchOllamaDescriptions().catch(() => /* @__PURE__ */ new Map())
|
|
1608
1634
|
]);
|
|
1609
1635
|
const runningNames = /* @__PURE__ */ new Set();
|
|
1610
1636
|
if (runningData?.models) {
|
|
@@ -1617,27 +1643,30 @@ var OllamaProvider = class {
|
|
|
1617
1643
|
if (localData?.models) {
|
|
1618
1644
|
for (const m of localData.models) {
|
|
1619
1645
|
const isRunning = runningNames.has(m.name) || runningNames.has(m.model);
|
|
1620
|
-
models.set(m.name, this.toModelInfo(m, isRunning ? "running" : "installed", true));
|
|
1646
|
+
models.set(m.name, this.toModelInfo(m, isRunning ? "running" : "installed", true, descriptions));
|
|
1621
1647
|
}
|
|
1622
1648
|
}
|
|
1623
1649
|
if (catalogData?.models) {
|
|
1624
1650
|
for (const m of catalogData.models) {
|
|
1625
1651
|
const name = m.name;
|
|
1626
1652
|
if (!models.has(name)) {
|
|
1627
|
-
models.set(name, this.toModelInfo(m, "available", false));
|
|
1653
|
+
models.set(name, this.toModelInfo(m, "available", false, descriptions));
|
|
1628
1654
|
}
|
|
1629
1655
|
}
|
|
1630
1656
|
}
|
|
1631
1657
|
return Array.from(models.values());
|
|
1632
1658
|
}
|
|
1633
|
-
toModelInfo(m, status, isLocal) {
|
|
1659
|
+
toModelInfo(m, status, isLocal, descriptions) {
|
|
1634
1660
|
const name = m.name ?? m.model ?? "unknown";
|
|
1635
1661
|
const family = m.details?.family;
|
|
1636
1662
|
const logoProvider = inferLogoProvider(name, family);
|
|
1663
|
+
const baseName = name.split(":")[0];
|
|
1664
|
+
const description = descriptions?.get(baseName);
|
|
1637
1665
|
return {
|
|
1638
1666
|
id: name,
|
|
1639
1667
|
provider: "ollama",
|
|
1640
1668
|
name,
|
|
1669
|
+
...description ? { description } : {},
|
|
1641
1670
|
modality: "llm",
|
|
1642
1671
|
local: true,
|
|
1643
1672
|
cost: { price: 0, unit: "free" },
|
|
@@ -1846,16 +1875,79 @@ var OllamaProvider = class {
|
|
|
1846
1875
|
}
|
|
1847
1876
|
};
|
|
1848
1877
|
|
|
1878
|
+
// src/utils/parse-readme.ts
|
|
1879
|
+
async function fetchReadmeDescription(modelId, timeoutMs = 5e3) {
|
|
1880
|
+
try {
|
|
1881
|
+
const controller = new AbortController();
|
|
1882
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
1883
|
+
try {
|
|
1884
|
+
const res = await fetch(
|
|
1885
|
+
`https://huggingface.co/${modelId}/raw/main/README.md`,
|
|
1886
|
+
{ signal: controller.signal }
|
|
1887
|
+
);
|
|
1888
|
+
if (!res.ok) return void 0;
|
|
1889
|
+
const text = await res.text();
|
|
1890
|
+
return parseReadmeDescription(text);
|
|
1891
|
+
} finally {
|
|
1892
|
+
clearTimeout(timer);
|
|
1893
|
+
}
|
|
1894
|
+
} catch {
|
|
1895
|
+
return void 0;
|
|
1896
|
+
}
|
|
1897
|
+
}
|
|
1898
|
+
function parseReadmeDescription(readme) {
|
|
1899
|
+
const withoutFrontmatter = readme.replace(/^---[\s\S]*?---\s*/, "");
|
|
1900
|
+
const lines = withoutFrontmatter.split("\n");
|
|
1901
|
+
let paragraph = "";
|
|
1902
|
+
for (const line of lines) {
|
|
1903
|
+
const trimmed = line.trim();
|
|
1904
|
+
if (!trimmed) {
|
|
1905
|
+
if (paragraph) break;
|
|
1906
|
+
continue;
|
|
1907
|
+
}
|
|
1908
|
+
if (trimmed.startsWith("#")) {
|
|
1909
|
+
if (paragraph) break;
|
|
1910
|
+
continue;
|
|
1911
|
+
}
|
|
1912
|
+
if (/^\[?!\[/.test(trimmed) || /^</.test(trimmed)) continue;
|
|
1913
|
+
if (/^\[.*\]\(.*\)$/.test(trimmed)) continue;
|
|
1914
|
+
paragraph += (paragraph ? " " : "") + trimmed;
|
|
1915
|
+
}
|
|
1916
|
+
if (!paragraph) return void 0;
|
|
1917
|
+
paragraph = paragraph.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/\*\*([^*]+)\*\*/g, "$1").replace(/`([^`]+)`/g, "$1").replace(/<[^>]+>/g, "").trim();
|
|
1918
|
+
if (!paragraph) return void 0;
|
|
1919
|
+
if (paragraph.length > 300) paragraph = paragraph.slice(0, 297) + "...";
|
|
1920
|
+
return paragraph;
|
|
1921
|
+
}
|
|
1922
|
+
|
|
1849
1923
|
// src/providers/hf-local.ts
|
|
1850
1924
|
var import_promises = require("fs/promises");
|
|
1851
1925
|
var import_node_path = require("path");
|
|
1852
1926
|
var import_node_os = require("os");
|
|
1853
1927
|
var FETCH_TIMEOUT_MS3 = 5e3;
|
|
1854
1928
|
var HF_HUB_API2 = "https://huggingface.co/api/models";
|
|
1929
|
+
var HF_ORG_TO_LOGO_PROVIDER = {
|
|
1930
|
+
"meta-llama": "meta",
|
|
1931
|
+
"facebook": "meta",
|
|
1932
|
+
"google": "google",
|
|
1933
|
+
"microsoft": "microsoft",
|
|
1934
|
+
"nvidia": "nvidia",
|
|
1935
|
+
"mistralai": "mistral",
|
|
1936
|
+
"Qwen": "qwen",
|
|
1937
|
+
"deepseek-ai": "deepseek",
|
|
1938
|
+
"openai": "openai",
|
|
1939
|
+
"CohereForAI": "cohere",
|
|
1940
|
+
"rhasspy": "piper",
|
|
1941
|
+
"stabilityai": "huggingface",
|
|
1942
|
+
"black-forest-labs": "huggingface",
|
|
1943
|
+
"tiiuae": "huggingface",
|
|
1944
|
+
"allenai": "huggingface",
|
|
1945
|
+
"Salesforce": "huggingface"
|
|
1946
|
+
};
|
|
1855
1947
|
var PIPELINE_TAG_TO_MODALITY = {
|
|
1856
1948
|
"text-to-image": "image",
|
|
1857
1949
|
"text-to-video": "video",
|
|
1858
|
-
"text-to-audio": "
|
|
1950
|
+
"text-to-audio": "music",
|
|
1859
1951
|
"text-to-speech": "tts",
|
|
1860
1952
|
"automatic-speech-recognition": "stt"
|
|
1861
1953
|
};
|
|
@@ -1883,7 +1975,7 @@ async function fetchJsonTimeout(url, timeoutMs = FETCH_TIMEOUT_MS3) {
|
|
|
1883
1975
|
var HfLocalProvider = class {
|
|
1884
1976
|
id = "hf-local";
|
|
1885
1977
|
name = "HuggingFace Local Models";
|
|
1886
|
-
modalities = ["image", "video", "tts", "stt"];
|
|
1978
|
+
modalities = ["image", "video", "tts", "stt", "music"];
|
|
1887
1979
|
isLocal = true;
|
|
1888
1980
|
cachedModels = null;
|
|
1889
1981
|
async ping() {
|
|
@@ -1905,8 +1997,7 @@ var HfLocalProvider = class {
|
|
|
1905
1997
|
}
|
|
1906
1998
|
async fetchCatalog() {
|
|
1907
1999
|
const seen = /* @__PURE__ */ new Set();
|
|
1908
|
-
const
|
|
1909
|
-
const logo = getProviderLogo("huggingface");
|
|
2000
|
+
const entries = [];
|
|
1910
2001
|
const results = await Promise.allSettled(
|
|
1911
2002
|
CATALOG_QUERIES.map(async (q) => {
|
|
1912
2003
|
const params = new URLSearchParams({
|
|
@@ -1924,32 +2015,56 @@ var HfLocalProvider = class {
|
|
|
1924
2015
|
const id = entry.id ?? entry.modelId;
|
|
1925
2016
|
if (!id || seen.has(id)) continue;
|
|
1926
2017
|
seen.add(id);
|
|
1927
|
-
|
|
1928
|
-
const modality = PIPELINE_TAG_TO_MODALITY[pipelineTag] ?? "image";
|
|
1929
|
-
models.push({
|
|
2018
|
+
entries.push({
|
|
1930
2019
|
id,
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
modality,
|
|
1934
|
-
local: true,
|
|
1935
|
-
cost: { price: 0, unit: "free" },
|
|
1936
|
-
logo,
|
|
1937
|
-
status: "available",
|
|
1938
|
-
localInfo: {
|
|
1939
|
-
sizeBytes: 0,
|
|
1940
|
-
runtime: "huggingface",
|
|
1941
|
-
family: entry.library_name
|
|
1942
|
-
},
|
|
1943
|
-
capabilities: {}
|
|
2020
|
+
pipelineTag: entry.pipeline_tag ?? "",
|
|
2021
|
+
libraryName: entry.library_name
|
|
1944
2022
|
});
|
|
1945
2023
|
}
|
|
1946
2024
|
}
|
|
2025
|
+
const descriptionMap = /* @__PURE__ */ new Map();
|
|
2026
|
+
for (let i = 0; i < entries.length; i += 10) {
|
|
2027
|
+
const batch = entries.slice(i, i + 10);
|
|
2028
|
+
const descs = await Promise.allSettled(
|
|
2029
|
+
batch.map(async (e) => {
|
|
2030
|
+
const desc = await fetchReadmeDescription(e.id);
|
|
2031
|
+
return { id: e.id, desc };
|
|
2032
|
+
})
|
|
2033
|
+
);
|
|
2034
|
+
for (const d of descs) {
|
|
2035
|
+
if (d.status === "fulfilled" && d.value.desc) {
|
|
2036
|
+
descriptionMap.set(d.value.id, d.value.desc);
|
|
2037
|
+
}
|
|
2038
|
+
}
|
|
2039
|
+
}
|
|
2040
|
+
const models = [];
|
|
2041
|
+
for (const e of entries) {
|
|
2042
|
+
const modality = PIPELINE_TAG_TO_MODALITY[e.pipelineTag] ?? "image";
|
|
2043
|
+
const org = e.id.includes("/") ? e.id.split("/")[0] : void 0;
|
|
2044
|
+
const logoProvider = org ? HF_ORG_TO_LOGO_PROVIDER[org] ?? "huggingface" : "huggingface";
|
|
2045
|
+
models.push({
|
|
2046
|
+
id: e.id,
|
|
2047
|
+
provider: "hf-local",
|
|
2048
|
+
name: e.id.split("/").pop() ?? e.id,
|
|
2049
|
+
modality,
|
|
2050
|
+
local: true,
|
|
2051
|
+
cost: { price: 0, unit: "free" },
|
|
2052
|
+
logo: getProviderLogo(logoProvider),
|
|
2053
|
+
description: descriptionMap.get(e.id),
|
|
2054
|
+
status: "available",
|
|
2055
|
+
localInfo: {
|
|
2056
|
+
sizeBytes: 0,
|
|
2057
|
+
runtime: "huggingface",
|
|
2058
|
+
family: e.libraryName
|
|
2059
|
+
},
|
|
2060
|
+
capabilities: {}
|
|
2061
|
+
});
|
|
2062
|
+
}
|
|
1947
2063
|
return models;
|
|
1948
2064
|
}
|
|
1949
2065
|
async scanLocalCache() {
|
|
1950
2066
|
const models = [];
|
|
1951
2067
|
const cacheDir = (0, import_node_path.join)((0, import_node_os.homedir)(), ".cache", "huggingface", "hub");
|
|
1952
|
-
const logo = getProviderLogo("huggingface");
|
|
1953
2068
|
try {
|
|
1954
2069
|
const entries = await (0, import_promises.readdir)(cacheDir, { withFileTypes: true });
|
|
1955
2070
|
for (const entry of entries) {
|
|
@@ -1981,6 +2096,8 @@ var HfLocalProvider = class {
|
|
|
1981
2096
|
}
|
|
1982
2097
|
}
|
|
1983
2098
|
const modality = PIPELINE_TAG_TO_MODALITY[pipelineTag] ?? "image";
|
|
2099
|
+
const org = modelId.includes("/") ? modelId.split("/")[0] : void 0;
|
|
2100
|
+
const logoProvider = org ? HF_ORG_TO_LOGO_PROVIDER[org] ?? "huggingface" : "huggingface";
|
|
1984
2101
|
models.push({
|
|
1985
2102
|
id: modelId,
|
|
1986
2103
|
provider: "hf-local",
|
|
@@ -1988,7 +2105,7 @@ var HfLocalProvider = class {
|
|
|
1988
2105
|
modality,
|
|
1989
2106
|
local: true,
|
|
1990
2107
|
cost: { price: 0, unit: "free" },
|
|
1991
|
-
logo,
|
|
2108
|
+
logo: getProviderLogo(logoProvider),
|
|
1992
2109
|
status: "installed",
|
|
1993
2110
|
localInfo: {
|
|
1994
2111
|
sizeBytes: 0,
|
|
@@ -2009,6 +2126,25 @@ var import_promises2 = require("fs/promises");
|
|
|
2009
2126
|
var import_node_path2 = require("path");
|
|
2010
2127
|
var import_node_os2 = require("os");
|
|
2011
2128
|
var WHISPER_MODELS = ["tiny", "base", "small", "medium", "large", "large-v2", "large-v3", "turbo"];
|
|
2129
|
+
var WHISPER_HF_REPOS = {
|
|
2130
|
+
"tiny": "openai/whisper-tiny",
|
|
2131
|
+
"base": "openai/whisper-base",
|
|
2132
|
+
"small": "openai/whisper-small",
|
|
2133
|
+
"medium": "openai/whisper-medium",
|
|
2134
|
+
"large": "openai/whisper-large",
|
|
2135
|
+
"large-v2": "openai/whisper-large-v2",
|
|
2136
|
+
"large-v3": "openai/whisper-large-v3",
|
|
2137
|
+
"turbo": "openai/whisper-large-v3-turbo"
|
|
2138
|
+
};
|
|
2139
|
+
async function fetchWhisperDescriptions() {
|
|
2140
|
+
const descriptions = /* @__PURE__ */ new Map();
|
|
2141
|
+
const fetches = Object.entries(WHISPER_HF_REPOS).map(async ([size, repo]) => {
|
|
2142
|
+
const desc = await fetchReadmeDescription(repo, 8e3);
|
|
2143
|
+
if (desc) descriptions.set(size, desc);
|
|
2144
|
+
});
|
|
2145
|
+
await Promise.allSettled(fetches);
|
|
2146
|
+
return descriptions;
|
|
2147
|
+
}
|
|
2012
2148
|
function runPython(code, timeoutMs = 5e3) {
|
|
2013
2149
|
return new Promise((resolve, reject) => {
|
|
2014
2150
|
const proc = (0, import_node_child_process.execFile)("python3", ["-c", code], { timeout: timeoutMs }, (err, stdout) => {
|
|
@@ -2054,7 +2190,8 @@ var WhisperLocalProvider = class {
|
|
|
2054
2190
|
if (_modality && _modality !== "stt") return [];
|
|
2055
2191
|
const runtime = await this.detectRuntime();
|
|
2056
2192
|
if (!runtime) return [];
|
|
2057
|
-
const
|
|
2193
|
+
const descMap = await fetchWhisperDescriptions().catch(() => /* @__PURE__ */ new Map());
|
|
2194
|
+
const logo = getProviderLogo("openai");
|
|
2058
2195
|
const models = [];
|
|
2059
2196
|
for (const name of WHISPER_MODELS) {
|
|
2060
2197
|
const installed = await this.isModelCached(name, runtime);
|
|
@@ -2062,6 +2199,7 @@ var WhisperLocalProvider = class {
|
|
|
2062
2199
|
id: `whisper-${name}`,
|
|
2063
2200
|
provider: "whisper-local",
|
|
2064
2201
|
name: `Whisper ${name}`,
|
|
2202
|
+
description: descMap.get(name),
|
|
2065
2203
|
modality: "stt",
|
|
2066
2204
|
local: true,
|
|
2067
2205
|
cost: { price: 0, unit: "free" },
|
|
@@ -2125,6 +2263,22 @@ var AUDIOCRAFT_MODELS = [
|
|
|
2125
2263
|
{ id: "musicgen-melody", name: "MusicGen Melody" },
|
|
2126
2264
|
{ id: "audiogen-medium", name: "AudioGen Medium" }
|
|
2127
2265
|
];
|
|
2266
|
+
var AUDIOCRAFT_HF_REPOS = {
|
|
2267
|
+
"musicgen-small": "facebook/musicgen-small",
|
|
2268
|
+
"musicgen-medium": "facebook/musicgen-medium",
|
|
2269
|
+
"musicgen-large": "facebook/musicgen-large",
|
|
2270
|
+
"musicgen-melody": "facebook/musicgen-melody",
|
|
2271
|
+
"audiogen-medium": "facebook/audiogen-medium"
|
|
2272
|
+
};
|
|
2273
|
+
async function fetchAudioCraftDescriptions() {
|
|
2274
|
+
const descriptions = /* @__PURE__ */ new Map();
|
|
2275
|
+
const fetches = Object.entries(AUDIOCRAFT_HF_REPOS).map(async ([id, repo]) => {
|
|
2276
|
+
const desc = await fetchReadmeDescription(repo, 8e3);
|
|
2277
|
+
if (desc) descriptions.set(id, desc);
|
|
2278
|
+
});
|
|
2279
|
+
await Promise.allSettled(fetches);
|
|
2280
|
+
return descriptions;
|
|
2281
|
+
}
|
|
2128
2282
|
function runPython2(code, timeoutMs = 5e3) {
|
|
2129
2283
|
return new Promise((resolve, reject) => {
|
|
2130
2284
|
(0, import_node_child_process2.execFile)("python3", ["-c", code], { timeout: timeoutMs }, (err, stdout) => {
|
|
@@ -2160,6 +2314,7 @@ var AudioCraftProvider = class {
|
|
|
2160
2314
|
async listModels(_modality) {
|
|
2161
2315
|
if (_modality && _modality !== "music") return [];
|
|
2162
2316
|
if (!await this.ping()) return [];
|
|
2317
|
+
const descMap = await fetchAudioCraftDescriptions().catch(() => /* @__PURE__ */ new Map());
|
|
2163
2318
|
const logo = getProviderLogo("meta");
|
|
2164
2319
|
const models = [];
|
|
2165
2320
|
for (const m of AUDIOCRAFT_MODELS) {
|
|
@@ -2169,6 +2324,7 @@ var AudioCraftProvider = class {
|
|
|
2169
2324
|
id: m.id,
|
|
2170
2325
|
provider: "audiocraft",
|
|
2171
2326
|
name: m.name,
|
|
2327
|
+
description: descMap.get(m.id),
|
|
2172
2328
|
modality: "music",
|
|
2173
2329
|
local: true,
|
|
2174
2330
|
cost: { price: 0, unit: "free" },
|
|
@@ -2564,9 +2720,9 @@ var Noosphere = class {
|
|
|
2564
2720
|
if (!this.initialized) await this.init();
|
|
2565
2721
|
return this.registry.getModel(provider, modelId);
|
|
2566
2722
|
}
|
|
2567
|
-
async syncModels() {
|
|
2723
|
+
async syncModels(modality) {
|
|
2568
2724
|
if (!this.initialized) await this.init();
|
|
2569
|
-
return this.registry.syncAll();
|
|
2725
|
+
return this.registry.syncAll(modality);
|
|
2570
2726
|
}
|
|
2571
2727
|
// --- Tracking Methods ---
|
|
2572
2728
|
getUsage(options) {
|