noosphere 0.5.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 +287 -2
- package/dist/index.cjs +1201 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +150 -3
- package/dist/index.d.ts +150 -3
- package/dist/index.js +1195 -14
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -20,10 +20,16 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
+
AudioCraftProvider: () => AudioCraftProvider,
|
|
24
|
+
HfLocalProvider: () => HfLocalProvider,
|
|
23
25
|
Noosphere: () => Noosphere,
|
|
24
26
|
NoosphereError: () => NoosphereError,
|
|
27
|
+
OllamaProvider: () => OllamaProvider,
|
|
28
|
+
OpenAICompatProvider: () => OpenAICompatProvider,
|
|
25
29
|
PROVIDER_IDS: () => PROVIDER_IDS,
|
|
26
30
|
PROVIDER_LOGOS: () => PROVIDER_LOGOS,
|
|
31
|
+
WhisperLocalProvider: () => WhisperLocalProvider,
|
|
32
|
+
detectOpenAICompatServers: () => detectOpenAICompatServers,
|
|
27
33
|
getAllProviderLogos: () => getAllProviderLogos,
|
|
28
34
|
getProviderLogo: () => getProviderLogo
|
|
29
35
|
});
|
|
@@ -336,20 +342,22 @@ var Registry = class {
|
|
|
336
342
|
const cached = this.modelCache.get(provider);
|
|
337
343
|
return cached?.models.find((m) => m.id === modelId) ?? null;
|
|
338
344
|
}
|
|
339
|
-
async syncProvider(providerId) {
|
|
345
|
+
async syncProvider(providerId, modality) {
|
|
340
346
|
const provider = this.providers.get(providerId);
|
|
341
347
|
if (!provider) return 0;
|
|
342
|
-
|
|
348
|
+
if (modality && !provider.modalities.includes(modality)) return 0;
|
|
349
|
+
const models = await provider.listModels(modality);
|
|
343
350
|
this.modelCache.set(providerId, { models, syncedAt: Date.now() });
|
|
344
351
|
return models.length;
|
|
345
352
|
}
|
|
346
|
-
async syncAll() {
|
|
353
|
+
async syncAll(modality) {
|
|
347
354
|
const byProvider = {};
|
|
348
355
|
const errors = [];
|
|
349
356
|
let synced = 0;
|
|
350
357
|
for (const provider of this.providers.values()) {
|
|
358
|
+
if (modality && !provider.modalities.includes(modality)) continue;
|
|
351
359
|
try {
|
|
352
|
-
const count = await this.syncProvider(provider.id);
|
|
360
|
+
const count = await this.syncProvider(provider.id, modality);
|
|
353
361
|
byProvider[provider.id] = count;
|
|
354
362
|
synced += count;
|
|
355
363
|
} catch (err) {
|
|
@@ -418,7 +426,7 @@ var UsageTracker = class {
|
|
|
418
426
|
filtered = filtered.filter((e) => e.modality === options.modality);
|
|
419
427
|
}
|
|
420
428
|
const byProvider = {};
|
|
421
|
-
const byModality = { llm: 0, image: 0, video: 0, tts: 0 };
|
|
429
|
+
const byModality = { llm: 0, image: 0, video: 0, tts: 0, stt: 0, music: 0, embedding: 0 };
|
|
422
430
|
let totalCost = 0;
|
|
423
431
|
for (const event of filtered) {
|
|
424
432
|
totalCost += event.cost;
|
|
@@ -1043,11 +1051,55 @@ var ComfyUIProvider = class {
|
|
|
1043
1051
|
}
|
|
1044
1052
|
}
|
|
1045
1053
|
async listModels(modality) {
|
|
1054
|
+
const models = [];
|
|
1055
|
+
const logo = getProviderLogo("comfyui");
|
|
1046
1056
|
try {
|
|
1047
|
-
const
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1057
|
+
const controller = new AbortController();
|
|
1058
|
+
const timer = setTimeout(() => controller.abort(), 5e3);
|
|
1059
|
+
try {
|
|
1060
|
+
const res = await fetch(`${this.baseUrl}/object_info`, { signal: controller.signal });
|
|
1061
|
+
if (res.ok) {
|
|
1062
|
+
const objectInfo = await res.json();
|
|
1063
|
+
const ckptNode = objectInfo?.["CheckpointLoaderSimple"];
|
|
1064
|
+
const ckptNames = ckptNode?.input?.required?.ckpt_name?.[0] ?? [];
|
|
1065
|
+
for (const name of ckptNames) {
|
|
1066
|
+
if (modality && modality !== "image") continue;
|
|
1067
|
+
models.push({
|
|
1068
|
+
id: `comfyui-ckpt-${name}`,
|
|
1069
|
+
provider: "comfyui",
|
|
1070
|
+
name: `ComfyUI: ${name}`,
|
|
1071
|
+
modality: "image",
|
|
1072
|
+
local: true,
|
|
1073
|
+
cost: { price: 0, unit: "free" },
|
|
1074
|
+
logo,
|
|
1075
|
+
status: "installed",
|
|
1076
|
+
localInfo: { sizeBytes: 0, runtime: "comfyui" },
|
|
1077
|
+
capabilities: { maxWidth: 2048, maxHeight: 2048, supportsNegativePrompt: true }
|
|
1078
|
+
});
|
|
1079
|
+
}
|
|
1080
|
+
const loraNode = objectInfo?.["LoraLoader"];
|
|
1081
|
+
const loraNames = loraNode?.input?.required?.lora_name?.[0] ?? [];
|
|
1082
|
+
for (const name of loraNames) {
|
|
1083
|
+
if (modality && modality !== "image") continue;
|
|
1084
|
+
models.push({
|
|
1085
|
+
id: `comfyui-lora-${name}`,
|
|
1086
|
+
provider: "comfyui",
|
|
1087
|
+
name: `LoRA: ${name}`,
|
|
1088
|
+
modality: "image",
|
|
1089
|
+
local: true,
|
|
1090
|
+
cost: { price: 0, unit: "free" },
|
|
1091
|
+
logo,
|
|
1092
|
+
status: "installed",
|
|
1093
|
+
localInfo: { sizeBytes: 0, runtime: "comfyui" }
|
|
1094
|
+
});
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
} finally {
|
|
1098
|
+
clearTimeout(timer);
|
|
1099
|
+
}
|
|
1100
|
+
} catch {
|
|
1101
|
+
}
|
|
1102
|
+
if (models.length === 0) {
|
|
1051
1103
|
if (!modality || modality === "image") {
|
|
1052
1104
|
models.push({
|
|
1053
1105
|
id: "comfyui-txt2img",
|
|
@@ -1072,10 +1124,44 @@ var ComfyUIProvider = class {
|
|
|
1072
1124
|
capabilities: { maxDuration: 10, supportsImageToVideo: true }
|
|
1073
1125
|
});
|
|
1074
1126
|
}
|
|
1075
|
-
return models;
|
|
1076
|
-
} catch {
|
|
1077
|
-
return [];
|
|
1078
1127
|
}
|
|
1128
|
+
if (!modality || modality === "image") {
|
|
1129
|
+
try {
|
|
1130
|
+
const controller = new AbortController();
|
|
1131
|
+
const timer = setTimeout(() => controller.abort(), 5e3);
|
|
1132
|
+
try {
|
|
1133
|
+
const res = await fetch(
|
|
1134
|
+
"https://civitai.com/api/v1/models?types=Checkpoint&sort=Highest%20Rated&limit=50&nsfw=false",
|
|
1135
|
+
{ signal: controller.signal }
|
|
1136
|
+
);
|
|
1137
|
+
if (res.ok) {
|
|
1138
|
+
const data = await res.json();
|
|
1139
|
+
for (const item of data.items ?? []) {
|
|
1140
|
+
const version = item.modelVersions?.[0];
|
|
1141
|
+
models.push({
|
|
1142
|
+
id: `civitai-${item.id}`,
|
|
1143
|
+
provider: "comfyui",
|
|
1144
|
+
name: item.name ?? `CivitAI Model ${item.id}`,
|
|
1145
|
+
description: item.description ? item.description.replace(/<[^>]+>/g, "").trim().slice(0, 300) || void 0 : void 0,
|
|
1146
|
+
modality: "image",
|
|
1147
|
+
local: true,
|
|
1148
|
+
cost: { price: 0, unit: "free" },
|
|
1149
|
+
logo,
|
|
1150
|
+
status: "available",
|
|
1151
|
+
localInfo: {
|
|
1152
|
+
sizeBytes: version?.files?.[0]?.sizeKB ? version.files[0].sizeKB * 1024 : 0,
|
|
1153
|
+
runtime: "comfyui"
|
|
1154
|
+
}
|
|
1155
|
+
});
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
} finally {
|
|
1159
|
+
clearTimeout(timer);
|
|
1160
|
+
}
|
|
1161
|
+
} catch {
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
return models;
|
|
1079
1165
|
}
|
|
1080
1166
|
async image(options) {
|
|
1081
1167
|
const start = Date.now();
|
|
@@ -1415,6 +1501,1043 @@ var HuggingFaceProvider = class {
|
|
|
1415
1501
|
}
|
|
1416
1502
|
};
|
|
1417
1503
|
|
|
1504
|
+
// src/providers/ollama.ts
|
|
1505
|
+
var OLLAMA_FAMILY_TO_PROVIDER = {
|
|
1506
|
+
"llama": "meta",
|
|
1507
|
+
"codellama": "meta",
|
|
1508
|
+
"gemma": "google",
|
|
1509
|
+
"gemma2": "google",
|
|
1510
|
+
"gemma3": "google",
|
|
1511
|
+
"qwen": "qwen",
|
|
1512
|
+
"qwen2": "qwen",
|
|
1513
|
+
"qwen2.5": "qwen",
|
|
1514
|
+
"qwen3": "qwen",
|
|
1515
|
+
"deepseek": "deepseek",
|
|
1516
|
+
"deepcoder": "deepseek",
|
|
1517
|
+
"deepscaler": "deepseek",
|
|
1518
|
+
"qwq": "qwen",
|
|
1519
|
+
"phi": "microsoft",
|
|
1520
|
+
"phi3": "microsoft",
|
|
1521
|
+
"phi4": "microsoft",
|
|
1522
|
+
"mistral": "mistral",
|
|
1523
|
+
"mixtral": "mistral",
|
|
1524
|
+
"codestral": "mistral",
|
|
1525
|
+
"ministral": "mistral",
|
|
1526
|
+
"nemotron": "nvidia",
|
|
1527
|
+
"command": "cohere",
|
|
1528
|
+
"command-r": "cohere",
|
|
1529
|
+
"gpt-oss": "openai",
|
|
1530
|
+
"starcoder": "huggingface",
|
|
1531
|
+
"falcon": "meta",
|
|
1532
|
+
"glm": "zai",
|
|
1533
|
+
"granite": "ibm",
|
|
1534
|
+
"olmo": "meta",
|
|
1535
|
+
"yi": "zai",
|
|
1536
|
+
"minimax": "minimax",
|
|
1537
|
+
"kimi": "meta",
|
|
1538
|
+
"dolphin": "ollama",
|
|
1539
|
+
"wizard": "ollama",
|
|
1540
|
+
"nomic": "ollama",
|
|
1541
|
+
"mxbai": "ollama",
|
|
1542
|
+
"bge": "ollama",
|
|
1543
|
+
"all-minilm": "ollama",
|
|
1544
|
+
"moondream": "ollama"
|
|
1545
|
+
};
|
|
1546
|
+
var VISION_MODELS = /* @__PURE__ */ new Set([
|
|
1547
|
+
"llava",
|
|
1548
|
+
"moondream",
|
|
1549
|
+
"minicpm-v",
|
|
1550
|
+
"llama3.2-vision",
|
|
1551
|
+
"qwen2.5vl",
|
|
1552
|
+
"gemma3",
|
|
1553
|
+
"llava-llama3",
|
|
1554
|
+
"llava-phi3",
|
|
1555
|
+
"bakllava"
|
|
1556
|
+
]);
|
|
1557
|
+
function inferLogoProvider(modelName, _family) {
|
|
1558
|
+
const base = modelName.split(":")[0].toLowerCase().replace(/^[^/]+\//, "");
|
|
1559
|
+
const sortedPrefixes = Object.entries(OLLAMA_FAMILY_TO_PROVIDER).sort((a, b) => b[0].length - a[0].length);
|
|
1560
|
+
for (const [prefix, provider] of sortedPrefixes) {
|
|
1561
|
+
if (base === prefix || base.startsWith(prefix)) return provider;
|
|
1562
|
+
}
|
|
1563
|
+
return "ollama";
|
|
1564
|
+
}
|
|
1565
|
+
function supportsVision(modelName) {
|
|
1566
|
+
const base = modelName.split(":")[0].toLowerCase();
|
|
1567
|
+
for (const v of VISION_MODELS) {
|
|
1568
|
+
if (base === v || base.startsWith(v)) return true;
|
|
1569
|
+
}
|
|
1570
|
+
return false;
|
|
1571
|
+
}
|
|
1572
|
+
async function fetchJson(url, options) {
|
|
1573
|
+
const timeoutMs = options?.timeoutMs ?? 5e3;
|
|
1574
|
+
const controller = new AbortController();
|
|
1575
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
1576
|
+
try {
|
|
1577
|
+
const res = await fetch(url, { ...options, signal: controller.signal });
|
|
1578
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
1579
|
+
return await res.json();
|
|
1580
|
+
} finally {
|
|
1581
|
+
clearTimeout(timer);
|
|
1582
|
+
}
|
|
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
|
+
}
|
|
1606
|
+
var OllamaProvider = class {
|
|
1607
|
+
id = "ollama";
|
|
1608
|
+
name = "Ollama (Local)";
|
|
1609
|
+
modalities = ["llm"];
|
|
1610
|
+
isLocal = true;
|
|
1611
|
+
baseUrl;
|
|
1612
|
+
constructor(config) {
|
|
1613
|
+
const host = config?.host ?? "http://localhost";
|
|
1614
|
+
const port = config?.port ?? 11434;
|
|
1615
|
+
const cleanHost = host.replace(/\/+$/, "");
|
|
1616
|
+
const hasPort = /:\d+$/.test(cleanHost);
|
|
1617
|
+
this.baseUrl = hasPort ? cleanHost : `${cleanHost}:${port}`;
|
|
1618
|
+
}
|
|
1619
|
+
async ping() {
|
|
1620
|
+
try {
|
|
1621
|
+
await fetchJson(`${this.baseUrl}/api/version`, { timeoutMs: 2e3 });
|
|
1622
|
+
return true;
|
|
1623
|
+
} catch {
|
|
1624
|
+
return false;
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
async listModels(_modality) {
|
|
1628
|
+
if (_modality && _modality !== "llm") return [];
|
|
1629
|
+
const [localData, catalogData, runningData, descriptions] = await Promise.all([
|
|
1630
|
+
fetchJson(`${this.baseUrl}/api/tags`, { timeoutMs: 5e3 }).catch(() => null),
|
|
1631
|
+
fetchJson("https://ollama.com/api/tags", { timeoutMs: 5e3 }).catch(() => null),
|
|
1632
|
+
fetchJson(`${this.baseUrl}/api/ps`, { timeoutMs: 5e3 }).catch(() => null),
|
|
1633
|
+
fetchOllamaDescriptions().catch(() => /* @__PURE__ */ new Map())
|
|
1634
|
+
]);
|
|
1635
|
+
const runningNames = /* @__PURE__ */ new Set();
|
|
1636
|
+
if (runningData?.models) {
|
|
1637
|
+
for (const m of runningData.models) {
|
|
1638
|
+
runningNames.add(m.name);
|
|
1639
|
+
runningNames.add(m.model);
|
|
1640
|
+
}
|
|
1641
|
+
}
|
|
1642
|
+
const models = /* @__PURE__ */ new Map();
|
|
1643
|
+
if (localData?.models) {
|
|
1644
|
+
for (const m of localData.models) {
|
|
1645
|
+
const isRunning = runningNames.has(m.name) || runningNames.has(m.model);
|
|
1646
|
+
models.set(m.name, this.toModelInfo(m, isRunning ? "running" : "installed", true, descriptions));
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
if (catalogData?.models) {
|
|
1650
|
+
for (const m of catalogData.models) {
|
|
1651
|
+
const name = m.name;
|
|
1652
|
+
if (!models.has(name)) {
|
|
1653
|
+
models.set(name, this.toModelInfo(m, "available", false, descriptions));
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
return Array.from(models.values());
|
|
1658
|
+
}
|
|
1659
|
+
toModelInfo(m, status, isLocal, descriptions) {
|
|
1660
|
+
const name = m.name ?? m.model ?? "unknown";
|
|
1661
|
+
const family = m.details?.family;
|
|
1662
|
+
const logoProvider = inferLogoProvider(name, family);
|
|
1663
|
+
const baseName = name.split(":")[0];
|
|
1664
|
+
const description = descriptions?.get(baseName);
|
|
1665
|
+
return {
|
|
1666
|
+
id: name,
|
|
1667
|
+
provider: "ollama",
|
|
1668
|
+
name,
|
|
1669
|
+
...description ? { description } : {},
|
|
1670
|
+
modality: "llm",
|
|
1671
|
+
local: true,
|
|
1672
|
+
cost: { price: 0, unit: "free" },
|
|
1673
|
+
logo: getProviderLogo(logoProvider),
|
|
1674
|
+
status,
|
|
1675
|
+
localInfo: {
|
|
1676
|
+
sizeBytes: m.size ?? 0,
|
|
1677
|
+
family: family ?? m.details?.family,
|
|
1678
|
+
parameterSize: m.details?.parameter_size,
|
|
1679
|
+
quantization: m.details?.quantization_level,
|
|
1680
|
+
format: m.details?.format,
|
|
1681
|
+
digest: m.digest,
|
|
1682
|
+
modifiedAt: m.modified_at,
|
|
1683
|
+
running: status === "running",
|
|
1684
|
+
runtime: "ollama"
|
|
1685
|
+
},
|
|
1686
|
+
capabilities: {
|
|
1687
|
+
contextWindow: 128e3,
|
|
1688
|
+
supportsVision: supportsVision(name),
|
|
1689
|
+
supportsStreaming: true
|
|
1690
|
+
}
|
|
1691
|
+
};
|
|
1692
|
+
}
|
|
1693
|
+
async chat(options) {
|
|
1694
|
+
const start = Date.now();
|
|
1695
|
+
const messages = options.messages.map((m) => ({
|
|
1696
|
+
role: m.role,
|
|
1697
|
+
content: m.content
|
|
1698
|
+
}));
|
|
1699
|
+
const body = {
|
|
1700
|
+
model: options.model ?? "llama3.2",
|
|
1701
|
+
messages,
|
|
1702
|
+
stream: false
|
|
1703
|
+
};
|
|
1704
|
+
if (options.temperature !== void 0 || options.maxTokens !== void 0) {
|
|
1705
|
+
body.options = {};
|
|
1706
|
+
if (options.temperature !== void 0) body.options.temperature = options.temperature;
|
|
1707
|
+
if (options.maxTokens !== void 0) body.options.num_predict = options.maxTokens;
|
|
1708
|
+
}
|
|
1709
|
+
const res = await fetch(`${this.baseUrl}/api/chat`, {
|
|
1710
|
+
method: "POST",
|
|
1711
|
+
headers: { "Content-Type": "application/json" },
|
|
1712
|
+
body: JSON.stringify(body)
|
|
1713
|
+
});
|
|
1714
|
+
if (!res.ok) {
|
|
1715
|
+
throw new Error(`Ollama chat failed: ${res.status} ${await res.text()}`);
|
|
1716
|
+
}
|
|
1717
|
+
const data = await res.json();
|
|
1718
|
+
return {
|
|
1719
|
+
content: data.message?.content ?? "",
|
|
1720
|
+
provider: "ollama",
|
|
1721
|
+
model: options.model ?? "llama3.2",
|
|
1722
|
+
modality: "llm",
|
|
1723
|
+
latencyMs: Date.now() - start,
|
|
1724
|
+
usage: {
|
|
1725
|
+
cost: 0,
|
|
1726
|
+
input: data.prompt_eval_count ?? 0,
|
|
1727
|
+
output: data.eval_count ?? 0,
|
|
1728
|
+
unit: "tokens"
|
|
1729
|
+
}
|
|
1730
|
+
};
|
|
1731
|
+
}
|
|
1732
|
+
stream(options) {
|
|
1733
|
+
const self = this;
|
|
1734
|
+
const start = Date.now();
|
|
1735
|
+
let aborted = false;
|
|
1736
|
+
let resolveResult = null;
|
|
1737
|
+
let rejectResult = null;
|
|
1738
|
+
const resultPromise = new Promise((resolve, reject) => {
|
|
1739
|
+
resolveResult = resolve;
|
|
1740
|
+
rejectResult = reject;
|
|
1741
|
+
});
|
|
1742
|
+
const messages = options.messages.map((m) => ({
|
|
1743
|
+
role: m.role,
|
|
1744
|
+
content: m.content
|
|
1745
|
+
}));
|
|
1746
|
+
const body = {
|
|
1747
|
+
model: options.model ?? "llama3.2",
|
|
1748
|
+
messages,
|
|
1749
|
+
stream: true
|
|
1750
|
+
};
|
|
1751
|
+
if (options.temperature !== void 0 || options.maxTokens !== void 0) {
|
|
1752
|
+
body.options = {};
|
|
1753
|
+
if (options.temperature !== void 0) body.options.temperature = options.temperature;
|
|
1754
|
+
if (options.maxTokens !== void 0) body.options.num_predict = options.maxTokens;
|
|
1755
|
+
}
|
|
1756
|
+
const asyncIterator = {
|
|
1757
|
+
async *[Symbol.asyncIterator]() {
|
|
1758
|
+
try {
|
|
1759
|
+
const res = await fetch(`${self.baseUrl}/api/chat`, {
|
|
1760
|
+
method: "POST",
|
|
1761
|
+
headers: { "Content-Type": "application/json" },
|
|
1762
|
+
body: JSON.stringify(body)
|
|
1763
|
+
});
|
|
1764
|
+
if (!res.ok) {
|
|
1765
|
+
throw new Error(`Ollama stream failed: ${res.status} ${await res.text()}`);
|
|
1766
|
+
}
|
|
1767
|
+
const reader = res.body.getReader();
|
|
1768
|
+
const decoder = new TextDecoder();
|
|
1769
|
+
let fullContent = "";
|
|
1770
|
+
let finalData = null;
|
|
1771
|
+
let buffer = "";
|
|
1772
|
+
while (!aborted) {
|
|
1773
|
+
const { done, value } = await reader.read();
|
|
1774
|
+
if (done) break;
|
|
1775
|
+
buffer += decoder.decode(value, { stream: true });
|
|
1776
|
+
const lines = buffer.split("\n");
|
|
1777
|
+
buffer = lines.pop() ?? "";
|
|
1778
|
+
for (const line of lines) {
|
|
1779
|
+
if (!line.trim()) continue;
|
|
1780
|
+
try {
|
|
1781
|
+
const chunk = JSON.parse(line);
|
|
1782
|
+
if (chunk.message?.content) {
|
|
1783
|
+
fullContent += chunk.message.content;
|
|
1784
|
+
yield { type: "text_delta", delta: chunk.message.content };
|
|
1785
|
+
}
|
|
1786
|
+
if (chunk.done) {
|
|
1787
|
+
finalData = chunk;
|
|
1788
|
+
}
|
|
1789
|
+
} catch {
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
}
|
|
1793
|
+
const result = {
|
|
1794
|
+
content: fullContent,
|
|
1795
|
+
provider: "ollama",
|
|
1796
|
+
model: options.model ?? "llama3.2",
|
|
1797
|
+
modality: "llm",
|
|
1798
|
+
latencyMs: Date.now() - start,
|
|
1799
|
+
usage: {
|
|
1800
|
+
cost: 0,
|
|
1801
|
+
input: finalData?.prompt_eval_count ?? 0,
|
|
1802
|
+
output: finalData?.eval_count ?? 0,
|
|
1803
|
+
unit: "tokens"
|
|
1804
|
+
}
|
|
1805
|
+
};
|
|
1806
|
+
resolveResult?.(result);
|
|
1807
|
+
yield { type: "done", result };
|
|
1808
|
+
} catch (err) {
|
|
1809
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
1810
|
+
rejectResult?.(error);
|
|
1811
|
+
yield { type: "error", error };
|
|
1812
|
+
}
|
|
1813
|
+
}
|
|
1814
|
+
};
|
|
1815
|
+
return {
|
|
1816
|
+
[Symbol.asyncIterator]: () => asyncIterator[Symbol.asyncIterator](),
|
|
1817
|
+
result: () => resultPromise,
|
|
1818
|
+
abort: () => {
|
|
1819
|
+
aborted = true;
|
|
1820
|
+
}
|
|
1821
|
+
};
|
|
1822
|
+
}
|
|
1823
|
+
// --- Extra model management methods ---
|
|
1824
|
+
async *pullModel(name) {
|
|
1825
|
+
const res = await fetch(`${this.baseUrl}/api/pull`, {
|
|
1826
|
+
method: "POST",
|
|
1827
|
+
headers: { "Content-Type": "application/json" },
|
|
1828
|
+
body: JSON.stringify({ name, stream: true })
|
|
1829
|
+
});
|
|
1830
|
+
if (!res.ok) {
|
|
1831
|
+
throw new Error(`Ollama pull failed: ${res.status} ${await res.text()}`);
|
|
1832
|
+
}
|
|
1833
|
+
const reader = res.body.getReader();
|
|
1834
|
+
const decoder = new TextDecoder();
|
|
1835
|
+
let buffer = "";
|
|
1836
|
+
while (true) {
|
|
1837
|
+
const { done, value } = await reader.read();
|
|
1838
|
+
if (done) break;
|
|
1839
|
+
buffer += decoder.decode(value, { stream: true });
|
|
1840
|
+
const lines = buffer.split("\n");
|
|
1841
|
+
buffer = lines.pop() ?? "";
|
|
1842
|
+
for (const line of lines) {
|
|
1843
|
+
if (!line.trim()) continue;
|
|
1844
|
+
try {
|
|
1845
|
+
yield JSON.parse(line);
|
|
1846
|
+
} catch {
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1849
|
+
}
|
|
1850
|
+
}
|
|
1851
|
+
async deleteModel(name) {
|
|
1852
|
+
const res = await fetch(`${this.baseUrl}/api/delete`, {
|
|
1853
|
+
method: "DELETE",
|
|
1854
|
+
headers: { "Content-Type": "application/json" },
|
|
1855
|
+
body: JSON.stringify({ name })
|
|
1856
|
+
});
|
|
1857
|
+
if (!res.ok) {
|
|
1858
|
+
throw new Error(`Ollama delete failed: ${res.status} ${await res.text()}`);
|
|
1859
|
+
}
|
|
1860
|
+
}
|
|
1861
|
+
async showModel(name) {
|
|
1862
|
+
const res = await fetch(`${this.baseUrl}/api/show`, {
|
|
1863
|
+
method: "POST",
|
|
1864
|
+
headers: { "Content-Type": "application/json" },
|
|
1865
|
+
body: JSON.stringify({ name })
|
|
1866
|
+
});
|
|
1867
|
+
if (!res.ok) {
|
|
1868
|
+
throw new Error(`Ollama show failed: ${res.status} ${await res.text()}`);
|
|
1869
|
+
}
|
|
1870
|
+
return await res.json();
|
|
1871
|
+
}
|
|
1872
|
+
async getRunningModels() {
|
|
1873
|
+
const data = await fetchJson(`${this.baseUrl}/api/ps`, { timeoutMs: 5e3 });
|
|
1874
|
+
return data?.models ?? [];
|
|
1875
|
+
}
|
|
1876
|
+
};
|
|
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
|
+
|
|
1923
|
+
// src/providers/hf-local.ts
|
|
1924
|
+
var import_promises = require("fs/promises");
|
|
1925
|
+
var import_node_path = require("path");
|
|
1926
|
+
var import_node_os = require("os");
|
|
1927
|
+
var FETCH_TIMEOUT_MS3 = 5e3;
|
|
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
|
+
};
|
|
1947
|
+
var PIPELINE_TAG_TO_MODALITY = {
|
|
1948
|
+
"text-to-image": "image",
|
|
1949
|
+
"text-to-video": "video",
|
|
1950
|
+
"text-to-audio": "music",
|
|
1951
|
+
"text-to-speech": "tts",
|
|
1952
|
+
"automatic-speech-recognition": "stt"
|
|
1953
|
+
};
|
|
1954
|
+
var CATALOG_QUERIES = [
|
|
1955
|
+
{ pipeline_tag: "text-to-image", limit: 50 },
|
|
1956
|
+
{ pipeline_tag: "text-to-video", limit: 30 },
|
|
1957
|
+
{ pipeline_tag: "text-to-audio", limit: 30 },
|
|
1958
|
+
{ pipeline_tag: "text-to-speech", limit: 30 },
|
|
1959
|
+
{ pipeline_tag: "automatic-speech-recognition", limit: 30 },
|
|
1960
|
+
{ pipeline_tag: "text-to-image", limit: 100, library: "diffusers" }
|
|
1961
|
+
];
|
|
1962
|
+
async function fetchJsonTimeout(url, timeoutMs = FETCH_TIMEOUT_MS3) {
|
|
1963
|
+
const controller = new AbortController();
|
|
1964
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
1965
|
+
try {
|
|
1966
|
+
const res = await fetch(url, { signal: controller.signal });
|
|
1967
|
+
if (!res.ok) return null;
|
|
1968
|
+
return await res.json();
|
|
1969
|
+
} catch {
|
|
1970
|
+
return null;
|
|
1971
|
+
} finally {
|
|
1972
|
+
clearTimeout(timer);
|
|
1973
|
+
}
|
|
1974
|
+
}
|
|
1975
|
+
var HfLocalProvider = class {
|
|
1976
|
+
id = "hf-local";
|
|
1977
|
+
name = "HuggingFace Local Models";
|
|
1978
|
+
modalities = ["image", "video", "tts", "stt", "music"];
|
|
1979
|
+
isLocal = true;
|
|
1980
|
+
cachedModels = null;
|
|
1981
|
+
async ping() {
|
|
1982
|
+
return true;
|
|
1983
|
+
}
|
|
1984
|
+
async listModels(modality) {
|
|
1985
|
+
if (!this.cachedModels) {
|
|
1986
|
+
const [catalog, installed] = await Promise.all([
|
|
1987
|
+
this.fetchCatalog(),
|
|
1988
|
+
this.scanLocalCache()
|
|
1989
|
+
]);
|
|
1990
|
+
const modelMap = /* @__PURE__ */ new Map();
|
|
1991
|
+
for (const m of catalog) modelMap.set(m.id, m);
|
|
1992
|
+
for (const m of installed) modelMap.set(m.id, m);
|
|
1993
|
+
this.cachedModels = Array.from(modelMap.values());
|
|
1994
|
+
}
|
|
1995
|
+
if (modality) return this.cachedModels.filter((m) => m.modality === modality);
|
|
1996
|
+
return this.cachedModels;
|
|
1997
|
+
}
|
|
1998
|
+
async fetchCatalog() {
|
|
1999
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2000
|
+
const entries = [];
|
|
2001
|
+
const results = await Promise.allSettled(
|
|
2002
|
+
CATALOG_QUERIES.map(async (q) => {
|
|
2003
|
+
const params = new URLSearchParams({
|
|
2004
|
+
pipeline_tag: q.pipeline_tag,
|
|
2005
|
+
sort: "downloads",
|
|
2006
|
+
limit: String(q.limit)
|
|
2007
|
+
});
|
|
2008
|
+
if (q.library) params.set("library", q.library);
|
|
2009
|
+
return fetchJsonTimeout(`${HF_HUB_API2}?${params}`);
|
|
2010
|
+
})
|
|
2011
|
+
);
|
|
2012
|
+
for (const result of results) {
|
|
2013
|
+
if (result.status !== "fulfilled" || !Array.isArray(result.value)) continue;
|
|
2014
|
+
for (const entry of result.value) {
|
|
2015
|
+
const id = entry.id ?? entry.modelId;
|
|
2016
|
+
if (!id || seen.has(id)) continue;
|
|
2017
|
+
seen.add(id);
|
|
2018
|
+
entries.push({
|
|
2019
|
+
id,
|
|
2020
|
+
pipelineTag: entry.pipeline_tag ?? "",
|
|
2021
|
+
libraryName: entry.library_name
|
|
2022
|
+
});
|
|
2023
|
+
}
|
|
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
|
+
}
|
|
2063
|
+
return models;
|
|
2064
|
+
}
|
|
2065
|
+
async scanLocalCache() {
|
|
2066
|
+
const models = [];
|
|
2067
|
+
const cacheDir = (0, import_node_path.join)((0, import_node_os.homedir)(), ".cache", "huggingface", "hub");
|
|
2068
|
+
try {
|
|
2069
|
+
const entries = await (0, import_promises.readdir)(cacheDir, { withFileTypes: true });
|
|
2070
|
+
for (const entry of entries) {
|
|
2071
|
+
if (!entry.isDirectory() || !entry.name.startsWith("models--")) continue;
|
|
2072
|
+
const parts = entry.name.replace("models--", "").split("--");
|
|
2073
|
+
const modelId = parts.join("/");
|
|
2074
|
+
const modelDir = (0, import_node_path.join)(cacheDir, entry.name);
|
|
2075
|
+
let snapshotHash;
|
|
2076
|
+
try {
|
|
2077
|
+
snapshotHash = (await (0, import_promises.readFile)((0, import_node_path.join)(modelDir, "refs", "main"), "utf-8")).trim();
|
|
2078
|
+
} catch {
|
|
2079
|
+
continue;
|
|
2080
|
+
}
|
|
2081
|
+
let pipelineTag = "";
|
|
2082
|
+
const snapshotDir = (0, import_node_path.join)(modelDir, "snapshots", snapshotHash);
|
|
2083
|
+
try {
|
|
2084
|
+
const modelIndex = JSON.parse(await (0, import_promises.readFile)((0, import_node_path.join)(snapshotDir, "model_index.json"), "utf-8"));
|
|
2085
|
+
if (modelIndex._class_name?.includes("Stable") || modelIndex._class_name?.includes("Flux")) {
|
|
2086
|
+
pipelineTag = "text-to-image";
|
|
2087
|
+
} else if (modelIndex._class_name?.includes("Video") || modelIndex._class_name?.includes("Animate")) {
|
|
2088
|
+
pipelineTag = "text-to-video";
|
|
2089
|
+
}
|
|
2090
|
+
} catch {
|
|
2091
|
+
try {
|
|
2092
|
+
const config = JSON.parse(await (0, import_promises.readFile)((0, import_node_path.join)(snapshotDir, "config.json"), "utf-8"));
|
|
2093
|
+
if (config.task_specific_params?.["text-to-image"]) pipelineTag = "text-to-image";
|
|
2094
|
+
else if (config.model_type?.includes("whisper")) pipelineTag = "automatic-speech-recognition";
|
|
2095
|
+
} catch {
|
|
2096
|
+
}
|
|
2097
|
+
}
|
|
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";
|
|
2101
|
+
models.push({
|
|
2102
|
+
id: modelId,
|
|
2103
|
+
provider: "hf-local",
|
|
2104
|
+
name: modelId.split("/").pop() ?? modelId,
|
|
2105
|
+
modality,
|
|
2106
|
+
local: true,
|
|
2107
|
+
cost: { price: 0, unit: "free" },
|
|
2108
|
+
logo: getProviderLogo(logoProvider),
|
|
2109
|
+
status: "installed",
|
|
2110
|
+
localInfo: {
|
|
2111
|
+
sizeBytes: 0,
|
|
2112
|
+
runtime: "huggingface",
|
|
2113
|
+
diskPath: snapshotDir
|
|
2114
|
+
}
|
|
2115
|
+
});
|
|
2116
|
+
}
|
|
2117
|
+
} catch {
|
|
2118
|
+
}
|
|
2119
|
+
return models;
|
|
2120
|
+
}
|
|
2121
|
+
};
|
|
2122
|
+
|
|
2123
|
+
// src/providers/whisper-local.ts
|
|
2124
|
+
var import_node_child_process = require("child_process");
|
|
2125
|
+
var import_promises2 = require("fs/promises");
|
|
2126
|
+
var import_node_path2 = require("path");
|
|
2127
|
+
var import_node_os2 = require("os");
|
|
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
|
+
}
|
|
2148
|
+
function runPython(code, timeoutMs = 5e3) {
|
|
2149
|
+
return new Promise((resolve, reject) => {
|
|
2150
|
+
const proc = (0, import_node_child_process.execFile)("python3", ["-c", code], { timeout: timeoutMs }, (err, stdout) => {
|
|
2151
|
+
if (err) reject(err);
|
|
2152
|
+
else resolve(stdout.trim());
|
|
2153
|
+
});
|
|
2154
|
+
});
|
|
2155
|
+
}
|
|
2156
|
+
async function fileExists(path) {
|
|
2157
|
+
try {
|
|
2158
|
+
await (0, import_promises2.access)(path);
|
|
2159
|
+
return true;
|
|
2160
|
+
} catch {
|
|
2161
|
+
return false;
|
|
2162
|
+
}
|
|
2163
|
+
}
|
|
2164
|
+
var WhisperLocalProvider = class {
|
|
2165
|
+
id = "whisper-local";
|
|
2166
|
+
name = "Whisper (Local)";
|
|
2167
|
+
modalities = ["stt"];
|
|
2168
|
+
isLocal = true;
|
|
2169
|
+
runtime = null;
|
|
2170
|
+
async ping() {
|
|
2171
|
+
return await this.detectRuntime() !== null;
|
|
2172
|
+
}
|
|
2173
|
+
async detectRuntime() {
|
|
2174
|
+
if (this.runtime) return this.runtime;
|
|
2175
|
+
try {
|
|
2176
|
+
await runPython('import faster_whisper; print("ok")');
|
|
2177
|
+
this.runtime = "faster-whisper";
|
|
2178
|
+
return this.runtime;
|
|
2179
|
+
} catch {
|
|
2180
|
+
}
|
|
2181
|
+
try {
|
|
2182
|
+
await runPython("import whisper; print(whisper.__version__)");
|
|
2183
|
+
this.runtime = "whisper";
|
|
2184
|
+
return this.runtime;
|
|
2185
|
+
} catch {
|
|
2186
|
+
}
|
|
2187
|
+
return null;
|
|
2188
|
+
}
|
|
2189
|
+
async listModels(_modality) {
|
|
2190
|
+
if (_modality && _modality !== "stt") return [];
|
|
2191
|
+
const runtime = await this.detectRuntime();
|
|
2192
|
+
if (!runtime) return [];
|
|
2193
|
+
const descMap = await fetchWhisperDescriptions().catch(() => /* @__PURE__ */ new Map());
|
|
2194
|
+
const logo = getProviderLogo("openai");
|
|
2195
|
+
const models = [];
|
|
2196
|
+
for (const name of WHISPER_MODELS) {
|
|
2197
|
+
const installed = await this.isModelCached(name, runtime);
|
|
2198
|
+
models.push({
|
|
2199
|
+
id: `whisper-${name}`,
|
|
2200
|
+
provider: "whisper-local",
|
|
2201
|
+
name: `Whisper ${name}`,
|
|
2202
|
+
description: descMap.get(name),
|
|
2203
|
+
modality: "stt",
|
|
2204
|
+
local: true,
|
|
2205
|
+
cost: { price: 0, unit: "free" },
|
|
2206
|
+
logo,
|
|
2207
|
+
status: installed ? "installed" : "available",
|
|
2208
|
+
localInfo: {
|
|
2209
|
+
sizeBytes: 0,
|
|
2210
|
+
runtime
|
|
2211
|
+
}
|
|
2212
|
+
});
|
|
2213
|
+
}
|
|
2214
|
+
return models;
|
|
2215
|
+
}
|
|
2216
|
+
async isModelCached(name, runtime) {
|
|
2217
|
+
if (runtime === "whisper") {
|
|
2218
|
+
return fileExists((0, import_node_path2.join)((0, import_node_os2.homedir)(), ".cache", "whisper", `${name}.pt`));
|
|
2219
|
+
}
|
|
2220
|
+
const hfDir = (0, import_node_path2.join)((0, import_node_os2.homedir)(), ".cache", "huggingface", "hub", `models--Systran--faster-whisper-${name}`);
|
|
2221
|
+
return fileExists(hfDir);
|
|
2222
|
+
}
|
|
2223
|
+
async transcribe(options) {
|
|
2224
|
+
const runtime = await this.detectRuntime();
|
|
2225
|
+
if (!runtime) throw new Error("Whisper is not installed");
|
|
2226
|
+
const model = options.model?.replace("whisper-", "") ?? "base";
|
|
2227
|
+
const lang = options.language ? `--language ${options.language}` : "";
|
|
2228
|
+
const task = options.task ?? "transcribe";
|
|
2229
|
+
if (runtime === "faster-whisper") {
|
|
2230
|
+
const code = `
|
|
2231
|
+
import json, sys
|
|
2232
|
+
from faster_whisper import WhisperModel
|
|
2233
|
+
model = WhisperModel("${model}")
|
|
2234
|
+
segments, info = model.transcribe("${options.audio}", task="${task}"${options.language ? `, language="${options.language}"` : ""})
|
|
2235
|
+
segs = [{"start": s.start, "end": s.end, "text": s.text} for s in segments]
|
|
2236
|
+
print(json.dumps({"text": " ".join(s["text"] for s in segs), "language": info.language, "duration": info.duration, "segments": segs}))
|
|
2237
|
+
`;
|
|
2238
|
+
const output = await runPython(code, 12e4);
|
|
2239
|
+
return JSON.parse(output);
|
|
2240
|
+
} else {
|
|
2241
|
+
const code = `
|
|
2242
|
+
import json, whisper
|
|
2243
|
+
model = whisper.load_model("${model}")
|
|
2244
|
+
result = model.transcribe("${options.audio}", task="${task}"${options.language ? `, language="${options.language}"` : ""})
|
|
2245
|
+
segs = [{"start": s["start"], "end": s["end"], "text": s["text"]} for s in result.get("segments", [])]
|
|
2246
|
+
print(json.dumps({"text": result["text"], "language": result.get("language", ""), "duration": 0, "segments": segs}))
|
|
2247
|
+
`;
|
|
2248
|
+
const output = await runPython(code, 12e4);
|
|
2249
|
+
return JSON.parse(output);
|
|
2250
|
+
}
|
|
2251
|
+
}
|
|
2252
|
+
};
|
|
2253
|
+
|
|
2254
|
+
// src/providers/audiocraft.ts
|
|
2255
|
+
var import_node_child_process2 = require("child_process");
|
|
2256
|
+
var import_promises3 = require("fs/promises");
|
|
2257
|
+
var import_node_path3 = require("path");
|
|
2258
|
+
var import_node_os3 = require("os");
|
|
2259
|
+
var AUDIOCRAFT_MODELS = [
|
|
2260
|
+
{ id: "musicgen-small", name: "MusicGen Small" },
|
|
2261
|
+
{ id: "musicgen-medium", name: "MusicGen Medium" },
|
|
2262
|
+
{ id: "musicgen-large", name: "MusicGen Large" },
|
|
2263
|
+
{ id: "musicgen-melody", name: "MusicGen Melody" },
|
|
2264
|
+
{ id: "audiogen-medium", name: "AudioGen Medium" }
|
|
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
|
+
}
|
|
2282
|
+
function runPython2(code, timeoutMs = 5e3) {
|
|
2283
|
+
return new Promise((resolve, reject) => {
|
|
2284
|
+
(0, import_node_child_process2.execFile)("python3", ["-c", code], { timeout: timeoutMs }, (err, stdout) => {
|
|
2285
|
+
if (err) reject(err);
|
|
2286
|
+
else resolve(stdout.trim());
|
|
2287
|
+
});
|
|
2288
|
+
});
|
|
2289
|
+
}
|
|
2290
|
+
async function fileExists2(path) {
|
|
2291
|
+
try {
|
|
2292
|
+
await (0, import_promises3.access)(path);
|
|
2293
|
+
return true;
|
|
2294
|
+
} catch {
|
|
2295
|
+
return false;
|
|
2296
|
+
}
|
|
2297
|
+
}
|
|
2298
|
+
var AudioCraftProvider = class {
|
|
2299
|
+
id = "audiocraft";
|
|
2300
|
+
name = "AudioCraft (Local)";
|
|
2301
|
+
modalities = ["music"];
|
|
2302
|
+
isLocal = true;
|
|
2303
|
+
detected = null;
|
|
2304
|
+
async ping() {
|
|
2305
|
+
if (this.detected !== null) return this.detected;
|
|
2306
|
+
try {
|
|
2307
|
+
await runPython2('import audiocraft; print("ok")');
|
|
2308
|
+
this.detected = true;
|
|
2309
|
+
} catch {
|
|
2310
|
+
this.detected = false;
|
|
2311
|
+
}
|
|
2312
|
+
return this.detected;
|
|
2313
|
+
}
|
|
2314
|
+
async listModels(_modality) {
|
|
2315
|
+
if (_modality && _modality !== "music") return [];
|
|
2316
|
+
if (!await this.ping()) return [];
|
|
2317
|
+
const descMap = await fetchAudioCraftDescriptions().catch(() => /* @__PURE__ */ new Map());
|
|
2318
|
+
const logo = getProviderLogo("meta");
|
|
2319
|
+
const models = [];
|
|
2320
|
+
for (const m of AUDIOCRAFT_MODELS) {
|
|
2321
|
+
const hfDir = (0, import_node_path3.join)((0, import_node_os3.homedir)(), ".cache", "huggingface", "hub", `models--facebook--${m.id}`);
|
|
2322
|
+
const installed = await fileExists2(hfDir);
|
|
2323
|
+
models.push({
|
|
2324
|
+
id: m.id,
|
|
2325
|
+
provider: "audiocraft",
|
|
2326
|
+
name: m.name,
|
|
2327
|
+
description: descMap.get(m.id),
|
|
2328
|
+
modality: "music",
|
|
2329
|
+
local: true,
|
|
2330
|
+
cost: { price: 0, unit: "free" },
|
|
2331
|
+
logo,
|
|
2332
|
+
status: installed ? "installed" : "available",
|
|
2333
|
+
localInfo: {
|
|
2334
|
+
sizeBytes: 0,
|
|
2335
|
+
runtime: "audiocraft"
|
|
2336
|
+
}
|
|
2337
|
+
});
|
|
2338
|
+
}
|
|
2339
|
+
return models;
|
|
2340
|
+
}
|
|
2341
|
+
};
|
|
2342
|
+
|
|
2343
|
+
// src/providers/openai-compat.ts
|
|
2344
|
+
var FETCH_TIMEOUT_MS4 = 5e3;
|
|
2345
|
+
var KNOWN_LOCAL_SERVERS = [
|
|
2346
|
+
{ port: 8080, name: "llama.cpp / LocalAI", id: "llamacpp" },
|
|
2347
|
+
{ port: 1234, name: "LM Studio", id: "lmstudio" },
|
|
2348
|
+
{ port: 8e3, name: "vLLM", id: "vllm" },
|
|
2349
|
+
{ port: 5e3, name: "TabbyAPI", id: "tabbyapi" },
|
|
2350
|
+
{ port: 5001, name: "KoboldCpp", id: "koboldcpp" },
|
|
2351
|
+
{ port: 1337, name: "Jan", id: "jan" }
|
|
2352
|
+
];
|
|
2353
|
+
async function fetchJsonTimeout2(url, headers, timeoutMs = FETCH_TIMEOUT_MS4) {
|
|
2354
|
+
const controller = new AbortController();
|
|
2355
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
2356
|
+
try {
|
|
2357
|
+
const res = await fetch(url, { signal: controller.signal, headers });
|
|
2358
|
+
if (!res.ok) return null;
|
|
2359
|
+
return await res.json();
|
|
2360
|
+
} catch {
|
|
2361
|
+
return null;
|
|
2362
|
+
} finally {
|
|
2363
|
+
clearTimeout(timer);
|
|
2364
|
+
}
|
|
2365
|
+
}
|
|
2366
|
+
var OpenAICompatProvider = class {
|
|
2367
|
+
id;
|
|
2368
|
+
name;
|
|
2369
|
+
modalities = ["llm"];
|
|
2370
|
+
isLocal = true;
|
|
2371
|
+
baseUrl;
|
|
2372
|
+
headers;
|
|
2373
|
+
constructor(config) {
|
|
2374
|
+
this.baseUrl = config.baseUrl.replace(/\/+$/, "");
|
|
2375
|
+
this.id = config.id ?? `openai-compat-${new URL(config.baseUrl).port}`;
|
|
2376
|
+
this.name = config.name ?? `OpenAI-Compatible (${this.baseUrl})`;
|
|
2377
|
+
this.headers = { "Content-Type": "application/json" };
|
|
2378
|
+
if (config.apiKey) {
|
|
2379
|
+
this.headers["Authorization"] = `Bearer ${config.apiKey}`;
|
|
2380
|
+
}
|
|
2381
|
+
}
|
|
2382
|
+
async ping() {
|
|
2383
|
+
const data = await fetchJsonTimeout2(`${this.baseUrl}/v1/models`, this.headers, 2e3);
|
|
2384
|
+
return data !== null;
|
|
2385
|
+
}
|
|
2386
|
+
async listModels(_modality) {
|
|
2387
|
+
if (_modality && _modality !== "llm") return [];
|
|
2388
|
+
const data = await fetchJsonTimeout2(`${this.baseUrl}/v1/models`, this.headers);
|
|
2389
|
+
if (!data?.data || !Array.isArray(data.data)) return [];
|
|
2390
|
+
const logo = getProviderLogo("openai");
|
|
2391
|
+
return data.data.map((m) => ({
|
|
2392
|
+
id: m.id,
|
|
2393
|
+
provider: this.id,
|
|
2394
|
+
name: m.id,
|
|
2395
|
+
modality: "llm",
|
|
2396
|
+
local: true,
|
|
2397
|
+
cost: { price: 0, unit: "free" },
|
|
2398
|
+
logo,
|
|
2399
|
+
status: "running",
|
|
2400
|
+
localInfo: {
|
|
2401
|
+
sizeBytes: 0,
|
|
2402
|
+
runtime: this.id
|
|
2403
|
+
},
|
|
2404
|
+
capabilities: {
|
|
2405
|
+
supportsStreaming: true
|
|
2406
|
+
}
|
|
2407
|
+
}));
|
|
2408
|
+
}
|
|
2409
|
+
async chat(options) {
|
|
2410
|
+
const start = Date.now();
|
|
2411
|
+
const model = options.model ?? "default";
|
|
2412
|
+
const body = {
|
|
2413
|
+
model,
|
|
2414
|
+
messages: options.messages,
|
|
2415
|
+
stream: false
|
|
2416
|
+
};
|
|
2417
|
+
if (options.temperature !== void 0) body.temperature = options.temperature;
|
|
2418
|
+
if (options.maxTokens !== void 0) body.max_tokens = options.maxTokens;
|
|
2419
|
+
if (options.jsonMode) body.response_format = { type: "json_object" };
|
|
2420
|
+
const res = await fetch(`${this.baseUrl}/v1/chat/completions`, {
|
|
2421
|
+
method: "POST",
|
|
2422
|
+
headers: this.headers,
|
|
2423
|
+
body: JSON.stringify(body)
|
|
2424
|
+
});
|
|
2425
|
+
if (!res.ok) throw new Error(`OpenAI-compat chat failed: ${res.status} ${await res.text()}`);
|
|
2426
|
+
const data = await res.json();
|
|
2427
|
+
const choice = data.choices?.[0];
|
|
2428
|
+
return {
|
|
2429
|
+
content: choice?.message?.content ?? "",
|
|
2430
|
+
provider: this.id,
|
|
2431
|
+
model,
|
|
2432
|
+
modality: "llm",
|
|
2433
|
+
latencyMs: Date.now() - start,
|
|
2434
|
+
usage: {
|
|
2435
|
+
cost: 0,
|
|
2436
|
+
input: data.usage?.prompt_tokens ?? 0,
|
|
2437
|
+
output: data.usage?.completion_tokens ?? 0,
|
|
2438
|
+
unit: "tokens"
|
|
2439
|
+
}
|
|
2440
|
+
};
|
|
2441
|
+
}
|
|
2442
|
+
stream(options) {
|
|
2443
|
+
const self = this;
|
|
2444
|
+
const start = Date.now();
|
|
2445
|
+
let aborted = false;
|
|
2446
|
+
let resolveResult = null;
|
|
2447
|
+
let rejectResult = null;
|
|
2448
|
+
const resultPromise = new Promise((resolve, reject) => {
|
|
2449
|
+
resolveResult = resolve;
|
|
2450
|
+
rejectResult = reject;
|
|
2451
|
+
});
|
|
2452
|
+
const model = options.model ?? "default";
|
|
2453
|
+
const body = {
|
|
2454
|
+
model,
|
|
2455
|
+
messages: options.messages,
|
|
2456
|
+
stream: true
|
|
2457
|
+
};
|
|
2458
|
+
if (options.temperature !== void 0) body.temperature = options.temperature;
|
|
2459
|
+
if (options.maxTokens !== void 0) body.max_tokens = options.maxTokens;
|
|
2460
|
+
const asyncIterator = {
|
|
2461
|
+
async *[Symbol.asyncIterator]() {
|
|
2462
|
+
try {
|
|
2463
|
+
const res = await fetch(`${self.baseUrl}/v1/chat/completions`, {
|
|
2464
|
+
method: "POST",
|
|
2465
|
+
headers: self.headers,
|
|
2466
|
+
body: JSON.stringify(body)
|
|
2467
|
+
});
|
|
2468
|
+
if (!res.ok) throw new Error(`Stream failed: ${res.status} ${await res.text()}`);
|
|
2469
|
+
const reader = res.body.getReader();
|
|
2470
|
+
const decoder = new TextDecoder();
|
|
2471
|
+
let fullContent = "";
|
|
2472
|
+
let buffer = "";
|
|
2473
|
+
while (!aborted) {
|
|
2474
|
+
const { done, value } = await reader.read();
|
|
2475
|
+
if (done) break;
|
|
2476
|
+
buffer += decoder.decode(value, { stream: true });
|
|
2477
|
+
const lines = buffer.split("\n");
|
|
2478
|
+
buffer = lines.pop() ?? "";
|
|
2479
|
+
for (const line of lines) {
|
|
2480
|
+
if (!line.startsWith("data: ") || line === "data: [DONE]") continue;
|
|
2481
|
+
try {
|
|
2482
|
+
const chunk = JSON.parse(line.slice(6));
|
|
2483
|
+
const delta = chunk.choices?.[0]?.delta?.content;
|
|
2484
|
+
if (delta) {
|
|
2485
|
+
fullContent += delta;
|
|
2486
|
+
yield { type: "text_delta", delta };
|
|
2487
|
+
}
|
|
2488
|
+
} catch {
|
|
2489
|
+
}
|
|
2490
|
+
}
|
|
2491
|
+
}
|
|
2492
|
+
const result = {
|
|
2493
|
+
content: fullContent,
|
|
2494
|
+
provider: self.id,
|
|
2495
|
+
model,
|
|
2496
|
+
modality: "llm",
|
|
2497
|
+
latencyMs: Date.now() - start,
|
|
2498
|
+
usage: { cost: 0, unit: "tokens" }
|
|
2499
|
+
};
|
|
2500
|
+
resolveResult?.(result);
|
|
2501
|
+
yield { type: "done", result };
|
|
2502
|
+
} catch (err) {
|
|
2503
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
2504
|
+
rejectResult?.(error);
|
|
2505
|
+
yield { type: "error", error };
|
|
2506
|
+
}
|
|
2507
|
+
}
|
|
2508
|
+
};
|
|
2509
|
+
return {
|
|
2510
|
+
[Symbol.asyncIterator]: () => asyncIterator[Symbol.asyncIterator](),
|
|
2511
|
+
result: () => resultPromise,
|
|
2512
|
+
abort: () => {
|
|
2513
|
+
aborted = true;
|
|
2514
|
+
}
|
|
2515
|
+
};
|
|
2516
|
+
}
|
|
2517
|
+
};
|
|
2518
|
+
async function detectOpenAICompatServers() {
|
|
2519
|
+
const providers = [];
|
|
2520
|
+
const results = await Promise.allSettled(
|
|
2521
|
+
KNOWN_LOCAL_SERVERS.map(async (server) => {
|
|
2522
|
+
const baseUrl = `http://localhost:${server.port}`;
|
|
2523
|
+
const provider = new OpenAICompatProvider({
|
|
2524
|
+
baseUrl,
|
|
2525
|
+
name: server.name,
|
|
2526
|
+
id: server.id
|
|
2527
|
+
});
|
|
2528
|
+
const ok = await provider.ping();
|
|
2529
|
+
if (ok) return provider;
|
|
2530
|
+
return null;
|
|
2531
|
+
})
|
|
2532
|
+
);
|
|
2533
|
+
for (const result of results) {
|
|
2534
|
+
if (result.status === "fulfilled" && result.value) {
|
|
2535
|
+
providers.push(result.value);
|
|
2536
|
+
}
|
|
2537
|
+
}
|
|
2538
|
+
return providers;
|
|
2539
|
+
}
|
|
2540
|
+
|
|
1418
2541
|
// src/noosphere.ts
|
|
1419
2542
|
var Noosphere = class {
|
|
1420
2543
|
config;
|
|
@@ -1597,14 +2720,38 @@ var Noosphere = class {
|
|
|
1597
2720
|
if (!this.initialized) await this.init();
|
|
1598
2721
|
return this.registry.getModel(provider, modelId);
|
|
1599
2722
|
}
|
|
1600
|
-
async syncModels() {
|
|
2723
|
+
async syncModels(modality) {
|
|
1601
2724
|
if (!this.initialized) await this.init();
|
|
1602
|
-
return this.registry.syncAll();
|
|
2725
|
+
return this.registry.syncAll(modality);
|
|
1603
2726
|
}
|
|
1604
2727
|
// --- Tracking Methods ---
|
|
1605
2728
|
getUsage(options) {
|
|
1606
2729
|
return this.tracker.getSummary(options);
|
|
1607
2730
|
}
|
|
2731
|
+
// --- Local Model Management ---
|
|
2732
|
+
async installModel(name) {
|
|
2733
|
+
if (!this.initialized) await this.init();
|
|
2734
|
+
const provider = this.registry.getProvider("ollama");
|
|
2735
|
+
if (!provider) throw new NoosphereError("Ollama provider not available", { code: "PROVIDER_UNAVAILABLE", provider: "ollama", modality: "llm" });
|
|
2736
|
+
return provider.pullModel(name);
|
|
2737
|
+
}
|
|
2738
|
+
async uninstallModel(name) {
|
|
2739
|
+
if (!this.initialized) await this.init();
|
|
2740
|
+
const provider = this.registry.getProvider("ollama");
|
|
2741
|
+
if (!provider) throw new NoosphereError("Ollama provider not available", { code: "PROVIDER_UNAVAILABLE", provider: "ollama", modality: "llm" });
|
|
2742
|
+
await provider.deleteModel(name);
|
|
2743
|
+
}
|
|
2744
|
+
async getHardware() {
|
|
2745
|
+
if (!this.initialized) await this.init();
|
|
2746
|
+
const provider = this.registry.getProvider("ollama");
|
|
2747
|
+
if (!provider) return { ollama: false, runningModels: [] };
|
|
2748
|
+
try {
|
|
2749
|
+
const runningModels = await provider.getRunningModels();
|
|
2750
|
+
return { ollama: true, runningModels };
|
|
2751
|
+
} catch {
|
|
2752
|
+
return { ollama: false, runningModels: [] };
|
|
2753
|
+
}
|
|
2754
|
+
}
|
|
1608
2755
|
// --- Lifecycle ---
|
|
1609
2756
|
async dispose() {
|
|
1610
2757
|
for (const provider of this.registry.getAllProviders()) {
|
|
@@ -1655,10 +2802,21 @@ var Noosphere = class {
|
|
|
1655
2802
|
return false;
|
|
1656
2803
|
}
|
|
1657
2804
|
};
|
|
2805
|
+
const ollamaCfg = local["ollama"];
|
|
1658
2806
|
const comfyuiCfg = local["comfyui"];
|
|
1659
2807
|
const piperCfg = local["piper"];
|
|
1660
2808
|
const kokoroCfg = local["kokoro"];
|
|
1661
2809
|
await Promise.allSettled([
|
|
2810
|
+
// Ollama — auto-detect even without explicit config
|
|
2811
|
+
(async () => {
|
|
2812
|
+
const host = ollamaCfg?.host ?? "http://localhost";
|
|
2813
|
+
const port = ollamaCfg?.port ?? 11434;
|
|
2814
|
+
const provider = new OllamaProvider({ host, port });
|
|
2815
|
+
const ok = await provider.ping();
|
|
2816
|
+
if (ok) {
|
|
2817
|
+
this.registry.addProvider(provider);
|
|
2818
|
+
}
|
|
2819
|
+
})(),
|
|
1662
2820
|
// ComfyUI
|
|
1663
2821
|
(async () => {
|
|
1664
2822
|
if (comfyuiCfg?.enabled) {
|
|
@@ -1685,6 +2843,29 @@ var Noosphere = class {
|
|
|
1685
2843
|
this.registry.addProvider(new LocalTTSProvider({ id: "kokoro", name: "Kokoro TTS", host: kokoroCfg.host, port: kokoroCfg.port }));
|
|
1686
2844
|
}
|
|
1687
2845
|
}
|
|
2846
|
+
})(),
|
|
2847
|
+
// HuggingFace local model catalog
|
|
2848
|
+
(async () => {
|
|
2849
|
+
this.registry.addProvider(new HfLocalProvider());
|
|
2850
|
+
})(),
|
|
2851
|
+
// Whisper local STT
|
|
2852
|
+
(async () => {
|
|
2853
|
+
const whisper = new WhisperLocalProvider();
|
|
2854
|
+
const ok = await whisper.ping();
|
|
2855
|
+
if (ok) this.registry.addProvider(whisper);
|
|
2856
|
+
})(),
|
|
2857
|
+
// AudioCraft local music generation
|
|
2858
|
+
(async () => {
|
|
2859
|
+
const audiocraft = new AudioCraftProvider();
|
|
2860
|
+
const ok = await audiocraft.ping();
|
|
2861
|
+
if (ok) this.registry.addProvider(audiocraft);
|
|
2862
|
+
})(),
|
|
2863
|
+
// Auto-detect OpenAI-compatible servers
|
|
2864
|
+
(async () => {
|
|
2865
|
+
const servers = await detectOpenAICompatServers();
|
|
2866
|
+
for (const server of servers) {
|
|
2867
|
+
this.registry.addProvider(server);
|
|
2868
|
+
}
|
|
1688
2869
|
})()
|
|
1689
2870
|
]);
|
|
1690
2871
|
}
|
|
@@ -1777,10 +2958,16 @@ var Noosphere = class {
|
|
|
1777
2958
|
};
|
|
1778
2959
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1779
2960
|
0 && (module.exports = {
|
|
2961
|
+
AudioCraftProvider,
|
|
2962
|
+
HfLocalProvider,
|
|
1780
2963
|
Noosphere,
|
|
1781
2964
|
NoosphereError,
|
|
2965
|
+
OllamaProvider,
|
|
2966
|
+
OpenAICompatProvider,
|
|
1782
2967
|
PROVIDER_IDS,
|
|
1783
2968
|
PROVIDER_LOGOS,
|
|
2969
|
+
WhisperLocalProvider,
|
|
2970
|
+
detectOpenAICompatServers,
|
|
1784
2971
|
getAllProviderLogos,
|
|
1785
2972
|
getProviderLogo
|
|
1786
2973
|
});
|