@openspecui/server 3.11.2 → 3.11.3
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/dist/{api-djiWTpO6.mjs → api-fZbAN-Zx.mjs} +1 -1
- package/dist/index.mjs +1933 -265
- package/dist/src-Cc9NSywS.mjs +3 -0
- package/dist/src-awZ9aP1s.mjs +232 -0
- package/package.json +6 -5
- /package/dist/{lexer-DQCqS3nf-DjvpjrU3.mjs → lexer-DQCqS3nf-Dzyxf9fs.mjs} +0 -0
- /package/dist/{src-BHeS1bxo.mjs → src-5XpFsBo7.mjs} +0 -0
- /package/dist/{src--4tprY9A.mjs → src-BHCDKXul.mjs} +0 -0
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { a as resolveCt2ModelDownloadPlanFromRepositoryFiles } from "./src-BJ-K9Dp2.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { t as resolveGgufModelDownloadPlanFromRepositoryFiles } from "./src-awZ9aP1s.mjs";
|
|
3
|
+
import { BatchTranslateInputSchema, CliExecutor, CodeEditorThemeSchema, ConfigManager, CustomSoundHashSchema, DASHBOARD_METRIC_KEYS, DashboardConfigSchema, DocumentTranslationConfigSchema, GitConfigSchema, GlobalSettingsManager, LocalModelAssetStateSchema, LocalModelLifecycleFileStateSchema, LocalModelLifecycleGroupStateSchema, LocalModelProfileManifestSchema, MarkdownParser, NotificationPublishInputSchema, NotificationSettingsSchema, OPENSPECUI_HOOKS_VERSION, OpenSpecAdapter, OpenSpecUIGlobalSettingsSchema, OpenSpecWatcher, OpsxConfigSchema, OpsxKernel, PtyClientMessageSchema, ReactiveContext, ServiceTranslationEngineIdSchema, TRANSLATION_ENGINE_MANIFESTS, TerminalConfigSchema, TerminalControlParser, TerminalRendererEngineSchema, TranslationCacheReadInputSchema, TranslationCacheSettingsSchema, TranslationCacheWriteInputSchema, TranslationEngineIdSchema, TranslationEngineLifecycleStatusSchema, TranslationLocalCt2SettingsSchema, TranslationLocalLlamaSettingsSchema, TranslationLocalSettingsSchema, TranslationOpenAISettingsSchema, buildBackendHealthPayload, buildLocalDownloadPlanFromRepositoryFiles, buildRuntimePackageInstallCommand, checkLocalDirectionalModelLanguagePair, clearCache, createCleanCliEnv, createTranslationEngineLifecycleStatus, detectRuntimePackageManager, getAllTools, getAvailableTools, getConfiguredTools, getDefaultCliCommandString, getDetectedProjectTools, getManagedLocalTranslationEngineManifest, getOpsxEntityRootRelativePath, getToolInitStates, getTranslationEngineLifecycleMessage, getWatcherRuntimeStatus, inferFileMime, inferFilePreviewKind, initWatcherPool, isDirectionalManagedLocalTranslationEngineId, isManagedLocalTranslationEngineId, isTranslationEngineDependencyReady, isTranslationEngineRuntimeReady, isWatcherPoolInitialized, normalizeOpsxEntityPath, parseOpsxEntityMetadata, parseOpsxSchemaDetail, resolveRuntimePackageInstallStrategy, resolveTerminalShellDefaults, selectLocalDownloadGroup, shouldShowTranslationEngineInstallGate, sniffGlobalCli, subscribeWatcherRuntimeStatus, terminalNotificationEventToPublishInput } from "@openspecui/core";
|
|
3
4
|
import { basename, dirname, extname, join, matchesGlob, relative, resolve, sep } from "node:path";
|
|
4
5
|
import { access, copyFile, lstat, mkdir, open, readFile, readlink, realpath, rename, rm, stat, symlink, unlink, writeFile } from "node:fs/promises";
|
|
5
6
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
@@ -388,7 +389,7 @@ var ProjectHookRuntime = class {
|
|
|
388
389
|
}
|
|
389
390
|
async loadFresh() {
|
|
390
391
|
if (!await pathExists$1(this.hooksPath)) return {};
|
|
391
|
-
const { tsImport } = await import("./api-
|
|
392
|
+
const { tsImport } = await import("./api-fZbAN-Zx.mjs");
|
|
392
393
|
return normalizeHooksModule(await tsImport(`${pathToFileURL(this.hooksPath).href}?t=${Date.now()}`, { parentURL: pathToFileURL(this.hooksPath).href }));
|
|
393
394
|
}
|
|
394
395
|
};
|
|
@@ -763,35 +764,35 @@ function hasRetryableStatusInMessage(message) {
|
|
|
763
764
|
|
|
764
765
|
//#endregion
|
|
765
766
|
//#region src/ct2-model-catalog.ts
|
|
766
|
-
const DEFAULT_SEARCH_LIMIT$
|
|
767
|
-
const MAX_SEARCH_FETCH_LIMIT$
|
|
767
|
+
const DEFAULT_SEARCH_LIMIT$2 = 6;
|
|
768
|
+
const MAX_SEARCH_FETCH_LIMIT$2 = 12;
|
|
768
769
|
const DEFAULT_SMALL_VERIFY_MODEL_ID$1 = "ooeoeo/opus-mt-en-zh-ct2-float16";
|
|
769
|
-
const HUGGING_FACE_FETCH_RETRY_COUNT$
|
|
770
|
-
const HUGGING_FACE_FETCH_RETRY_DELAY_MS$
|
|
771
|
-
const HUGGING_FACE_FETCH_DISPATCHER$
|
|
770
|
+
const HUGGING_FACE_FETCH_RETRY_COUNT$2 = 2;
|
|
771
|
+
const HUGGING_FACE_FETCH_RETRY_DELAY_MS$2 = 750;
|
|
772
|
+
const HUGGING_FACE_FETCH_DISPATCHER$2 = createProxyAwareDispatcher();
|
|
772
773
|
async function searchCt2Models(input, options = {}) {
|
|
773
|
-
const list = await fetchHuggingFaceModelList$
|
|
774
|
+
const list = await fetchHuggingFaceModelList$2(input, options);
|
|
774
775
|
return {
|
|
775
|
-
items: rankCandidates$
|
|
776
|
-
const detail = await getHuggingFaceModelDetail$
|
|
777
|
-
return detail ? toTranslationModelCandidate$
|
|
778
|
-
})), input).slice(0, normalizeSearchLimit$
|
|
776
|
+
items: rankCandidates$2(await Promise.all(list.items.map(async (item) => {
|
|
777
|
+
const detail = await getHuggingFaceModelDetail$2(item.id, input, options).catch(() => null);
|
|
778
|
+
return detail ? toTranslationModelCandidate$2(detail, input) : toTranslationModelCandidate$2(item, input);
|
|
779
|
+
})), input).slice(0, normalizeSearchLimit$2(input.limit)),
|
|
779
780
|
nextCursor: list.nextCursor
|
|
780
781
|
};
|
|
781
782
|
}
|
|
782
783
|
async function searchCt2ModelsProgressively(input, options = {}) {
|
|
783
|
-
const list = await fetchHuggingFaceModelList$
|
|
784
|
-
const candidateShells = rankCandidates$
|
|
784
|
+
const list = await fetchHuggingFaceModelList$2(input, options);
|
|
785
|
+
const candidateShells = rankCandidates$2(list.items.map((item) => toTranslationModelCandidate$2(item, input)), input).slice(0, normalizeSearchLimit$2(input.limit));
|
|
785
786
|
const events = [{
|
|
786
787
|
requestId: input.requestId,
|
|
787
788
|
phase: "candidates",
|
|
788
789
|
items: candidateShells,
|
|
789
790
|
nextCursor: list.nextCursor
|
|
790
791
|
}];
|
|
791
|
-
const ranked = rankCandidates$
|
|
792
|
-
const detail = await getHuggingFaceModelDetail$
|
|
793
|
-
return detail ? toTranslationModelCandidate$
|
|
794
|
-
})), input).slice(0, normalizeSearchLimit$
|
|
792
|
+
const ranked = rankCandidates$2(await Promise.all(candidateShells.map(async (candidate) => {
|
|
793
|
+
const detail = await getHuggingFaceModelDetail$2(candidate.id, input, options).catch(() => null);
|
|
794
|
+
return detail ? toTranslationModelCandidate$2(detail, input) : candidate;
|
|
795
|
+
})), input).slice(0, normalizeSearchLimit$2(input.limit));
|
|
795
796
|
events.push({
|
|
796
797
|
requestId: input.requestId,
|
|
797
798
|
phase: "enriched",
|
|
@@ -806,9 +807,9 @@ async function searchCt2ModelsProgressively(input, options = {}) {
|
|
|
806
807
|
});
|
|
807
808
|
return events;
|
|
808
809
|
}
|
|
809
|
-
async function fetchHuggingFaceModelList$
|
|
810
|
-
const limit = normalizeSearchLimit$
|
|
811
|
-
const fetchLimit = Math.min(Math.max(limit * 2, limit), MAX_SEARCH_FETCH_LIMIT$
|
|
810
|
+
async function fetchHuggingFaceModelList$2(input, options) {
|
|
811
|
+
const limit = normalizeSearchLimit$2(input.limit);
|
|
812
|
+
const fetchLimit = Math.min(Math.max(limit * 2, limit), MAX_SEARCH_FETCH_LIMIT$2);
|
|
812
813
|
const params = new URLSearchParams({
|
|
813
814
|
pipeline_tag: "translation",
|
|
814
815
|
sort: "trendingScore",
|
|
@@ -818,67 +819,67 @@ async function fetchHuggingFaceModelList$1(input, options) {
|
|
|
818
819
|
if (input.query?.trim()) params.set("search", input.query.trim());
|
|
819
820
|
if (input.cursor?.trim()) params.set("cursor", input.cursor.trim());
|
|
820
821
|
const url = `${buildHuggingFaceApiBaseUrl(options.hfEndpoint)}/models?${params.toString()}`;
|
|
821
|
-
const response = await fetchHuggingFace$
|
|
822
|
+
const response = await fetchHuggingFace$2(url);
|
|
822
823
|
const responseBody = await response.text();
|
|
823
|
-
const fetchCacheStore = getFetchCacheStore$
|
|
824
|
+
const fetchCacheStore = getFetchCacheStore$2(options);
|
|
824
825
|
await fetchCacheStore.upsertProviderFetch({
|
|
825
826
|
url,
|
|
826
827
|
status: response.status,
|
|
827
828
|
ok: response.ok,
|
|
828
|
-
headers: headersToRecord$
|
|
829
|
+
headers: headersToRecord$3(response.headers),
|
|
829
830
|
bodyText: responseBody,
|
|
830
|
-
queryContext: buildQueryContext$
|
|
831
|
+
queryContext: buildQueryContext$2(input)
|
|
831
832
|
});
|
|
832
833
|
if (!response.ok) throw new Error(`Hugging Face model search failed with status ${response.status}.`);
|
|
833
|
-
const listJson = parseJson$
|
|
834
|
-
const rawItems = Array.isArray(listJson) ? listJson.filter(isRecord$
|
|
835
|
-
const items = rawItems.map(normalizeHfModelListItem$
|
|
834
|
+
const listJson = parseJson$2(responseBody);
|
|
835
|
+
const rawItems = Array.isArray(listJson) ? listJson.filter(isRecord$2) : [];
|
|
836
|
+
const items = rawItems.map(normalizeHfModelListItem$2).filter((item) => item !== null);
|
|
836
837
|
for (const raw of rawItems) {
|
|
837
|
-
const item = normalizeHfModelListItem$
|
|
838
|
+
const item = normalizeHfModelListItem$2(raw);
|
|
838
839
|
if (!item) continue;
|
|
839
840
|
await fetchCacheStore.upsertListItem({
|
|
840
841
|
modelId: item.id,
|
|
841
842
|
raw,
|
|
842
|
-
queryContext: buildQueryContext$
|
|
843
|
+
queryContext: buildQueryContext$2(input)
|
|
843
844
|
});
|
|
844
845
|
}
|
|
845
846
|
return {
|
|
846
847
|
items,
|
|
847
|
-
nextCursor: readNextCursor$
|
|
848
|
+
nextCursor: readNextCursor$2(response.headers.get("link"))
|
|
848
849
|
};
|
|
849
850
|
}
|
|
850
|
-
async function getHuggingFaceModelDetail$
|
|
851
|
+
async function getHuggingFaceModelDetail$2(modelId, input, options) {
|
|
851
852
|
const [namespace, repo] = modelId.split("/", 2);
|
|
852
853
|
const modelPath = namespace && repo ? `${encodeURIComponent(namespace)}/${encodeURIComponent(repo)}` : encodeURIComponent(modelId);
|
|
853
854
|
const url = `${buildHuggingFaceApiBaseUrl(options.hfEndpoint)}/models/${modelPath}?blobs=true`;
|
|
854
|
-
const response = await fetchHuggingFace$
|
|
855
|
+
const response = await fetchHuggingFace$2(url);
|
|
855
856
|
const responseBody = await response.text();
|
|
856
|
-
await getFetchCacheStore$
|
|
857
|
+
await getFetchCacheStore$2(options).upsertProviderFetch({
|
|
857
858
|
url,
|
|
858
859
|
status: response.status,
|
|
859
860
|
ok: response.ok,
|
|
860
|
-
headers: headersToRecord$
|
|
861
|
+
headers: headersToRecord$3(response.headers),
|
|
861
862
|
bodyText: responseBody,
|
|
862
|
-
queryContext: input ? buildQueryContext$
|
|
863
|
+
queryContext: input ? buildQueryContext$2(input) : void 0
|
|
863
864
|
});
|
|
864
865
|
if (!response.ok) throw new Error(`Hugging Face model detail failed with status ${response.status}.`);
|
|
865
|
-
const detailJson = parseJson$
|
|
866
|
-
const raw = isRecord$
|
|
867
|
-
await getFetchCacheStore$
|
|
866
|
+
const detailJson = parseJson$2(responseBody);
|
|
867
|
+
const raw = isRecord$2(detailJson) ? detailJson : {};
|
|
868
|
+
await getFetchCacheStore$2(options).upsertDetail({
|
|
868
869
|
modelId,
|
|
869
870
|
raw,
|
|
870
|
-
queryContext: input ? buildQueryContext$
|
|
871
|
+
queryContext: input ? buildQueryContext$2(input) : void 0
|
|
871
872
|
});
|
|
872
|
-
return normalizeHfModelDetail$
|
|
873
|
+
return normalizeHfModelDetail$2(detailJson, modelId);
|
|
873
874
|
}
|
|
874
|
-
function toTranslationModelCandidate$
|
|
875
|
-
const plan = isHfModelDetail$
|
|
875
|
+
function toTranslationModelCandidate$2(detail, input) {
|
|
876
|
+
const plan = isHfModelDetail$2(detail) ? resolveCt2ModelPlan(detail) : null;
|
|
876
877
|
const verified = plan !== null;
|
|
877
878
|
const estimatedTotalBytes = plan?.estimatedTotalBytes;
|
|
878
879
|
return {
|
|
879
880
|
id: detail.id,
|
|
880
881
|
label: detail.id,
|
|
881
|
-
summary: buildCandidateSummary$
|
|
882
|
+
summary: buildCandidateSummary$2(detail, verified, estimatedTotalBytes),
|
|
882
883
|
downloads: detail.downloads,
|
|
883
884
|
likes: detail.likes,
|
|
884
885
|
trendingScore: detail.trendingScore,
|
|
@@ -895,7 +896,7 @@ function toTranslationModelCandidate$1(detail, input) {
|
|
|
895
896
|
primaryBytes: estimatedTotalBytes
|
|
896
897
|
},
|
|
897
898
|
downloadGroups: plan?.groups,
|
|
898
|
-
languageMatch: buildLanguageMatch$
|
|
899
|
+
languageMatch: buildLanguageMatch$2(detail, input.sourceLanguage, input.targetLanguage)
|
|
899
900
|
};
|
|
900
901
|
}
|
|
901
902
|
function resolveCt2ModelPlan(detail) {
|
|
@@ -907,9 +908,9 @@ function resolveCt2ModelPlan(detail) {
|
|
|
907
908
|
}))
|
|
908
909
|
});
|
|
909
910
|
}
|
|
910
|
-
function buildCandidateSummary$
|
|
911
|
+
function buildCandidateSummary$2(detail, verified, estimatedTotalBytes) {
|
|
911
912
|
const parts = [verified ? "Verified CTranslate2 translation model." : "Translation model from Hugging Face."];
|
|
912
|
-
if (estimatedTotalBytes !== void 0) parts.push(`Estimated download ${formatBytes$
|
|
913
|
+
if (estimatedTotalBytes !== void 0) parts.push(`Estimated download ${formatBytes$5(estimatedTotalBytes)}.`);
|
|
913
914
|
if (detail.tags.includes("translation")) parts.push("Tagged for translation.");
|
|
914
915
|
return parts.join(" ");
|
|
915
916
|
}
|
|
@@ -923,11 +924,11 @@ function rankCandidate$1(candidate) {
|
|
|
923
924
|
const sizePenalty = candidate.size.estimatedTotalBytes === void 0 ? 0 : Math.log10(candidate.size.estimatedTotalBytes / (1024 * 1024) + 1) * 12;
|
|
924
925
|
return smallVerifyBoost + compatibilityBoost + trend + directionalBoost + downloadsBoost + likesBoost - sizePenalty;
|
|
925
926
|
}
|
|
926
|
-
function rankCandidates$
|
|
927
|
+
function rankCandidates$2(candidates, input) {
|
|
927
928
|
const verifiedCandidates = candidates.filter((candidate) => candidate.compatibility.localRuntimeVerified);
|
|
928
929
|
return [...input.query?.trim() ? candidates : verifiedCandidates].sort((left, right) => rankCandidate$1(right) - rankCandidate$1(left));
|
|
929
930
|
}
|
|
930
|
-
function buildLanguageMatch$
|
|
931
|
+
function buildLanguageMatch$2(detail, sourceLanguage, targetLanguage) {
|
|
931
932
|
const sourceTokens = buildLanguageTokens$1(sourceLanguage);
|
|
932
933
|
const targetTokens = buildLanguageTokens$1(targetLanguage);
|
|
933
934
|
const searchable = [detail.id, ...detail.tags].join(" ").toLowerCase();
|
|
@@ -952,8 +953,8 @@ function buildLanguageTokens$1(language) {
|
|
|
952
953
|
const primary = normalized.split(/[-_]/, 1)[0] ?? normalized;
|
|
953
954
|
return [...new Set([normalized, primary].filter(Boolean))];
|
|
954
955
|
}
|
|
955
|
-
function normalizeHfModelDetail$
|
|
956
|
-
const base = normalizeHfModelListItem$
|
|
956
|
+
function normalizeHfModelDetail$2(value, fallbackId) {
|
|
957
|
+
const base = normalizeHfModelListItem$2(value) ?? {
|
|
957
958
|
id: fallbackId,
|
|
958
959
|
tags: [],
|
|
959
960
|
downloads: 0,
|
|
@@ -965,7 +966,7 @@ function normalizeHfModelDetail$1(value, fallbackId) {
|
|
|
965
966
|
siblings: Array.isArray(record.siblings) ? record.siblings.map((entry) => normalizeSibling$1(entry)).filter((entry) => entry !== null) : []
|
|
966
967
|
};
|
|
967
968
|
}
|
|
968
|
-
function normalizeHfModelListItem$
|
|
969
|
+
function normalizeHfModelListItem$2(value) {
|
|
969
970
|
if (!value || typeof value !== "object") return null;
|
|
970
971
|
const record = value;
|
|
971
972
|
if (typeof record.id !== "string" || record.id.length === 0) return null;
|
|
@@ -988,44 +989,44 @@ function normalizeSibling$1(value) {
|
|
|
988
989
|
size: normalizeOptionalNumber$1(record.size)
|
|
989
990
|
};
|
|
990
991
|
}
|
|
991
|
-
function getFetchCacheStore$
|
|
992
|
+
function getFetchCacheStore$2(options) {
|
|
992
993
|
return options.fetchCacheStore ?? new LocalModelFetchCacheStore({ cachePath: getDefaultLocalCt2ModelFetchCachePath() });
|
|
993
994
|
}
|
|
994
|
-
async function fetchHuggingFace$
|
|
995
|
+
async function fetchHuggingFace$2(input) {
|
|
995
996
|
let lastError;
|
|
996
|
-
for (let attempt = 0; attempt <= HUGGING_FACE_FETCH_RETRY_COUNT$
|
|
997
|
+
for (let attempt = 0; attempt <= HUGGING_FACE_FETCH_RETRY_COUNT$2; attempt += 1) {
|
|
997
998
|
try {
|
|
998
|
-
const response = await fetch(input, { dispatcher: HUGGING_FACE_FETCH_DISPATCHER$
|
|
999
|
+
const response = await fetch(input, { dispatcher: HUGGING_FACE_FETCH_DISPATCHER$2 });
|
|
999
1000
|
if (response.ok || !isRetryableNetworkStatusCode(response.status)) return response;
|
|
1000
1001
|
lastError = /* @__PURE__ */ new Error(`Hugging Face request failed with status ${response.status}.`);
|
|
1001
|
-
if (attempt === HUGGING_FACE_FETCH_RETRY_COUNT$
|
|
1002
|
+
if (attempt === HUGGING_FACE_FETCH_RETRY_COUNT$2) return response;
|
|
1002
1003
|
await response.body?.cancel().catch(() => void 0);
|
|
1003
1004
|
} catch (error) {
|
|
1004
1005
|
lastError = error;
|
|
1005
|
-
if (!isRetryableNetworkError(error) || attempt === HUGGING_FACE_FETCH_RETRY_COUNT$
|
|
1006
|
+
if (!isRetryableNetworkError(error) || attempt === HUGGING_FACE_FETCH_RETRY_COUNT$2) throw error;
|
|
1006
1007
|
}
|
|
1007
|
-
await delay$
|
|
1008
|
+
await delay$6(HUGGING_FACE_FETCH_RETRY_DELAY_MS$2 * (attempt + 1));
|
|
1008
1009
|
}
|
|
1009
1010
|
throw lastError instanceof Error ? lastError : /* @__PURE__ */ new Error("Hugging Face request failed.");
|
|
1010
1011
|
}
|
|
1011
|
-
function parseJson$
|
|
1012
|
+
function parseJson$2(text) {
|
|
1012
1013
|
try {
|
|
1013
1014
|
return JSON.parse(text);
|
|
1014
1015
|
} catch {
|
|
1015
1016
|
return null;
|
|
1016
1017
|
}
|
|
1017
1018
|
}
|
|
1018
|
-
function headersToRecord$
|
|
1019
|
+
function headersToRecord$3(headers) {
|
|
1019
1020
|
return Object.fromEntries(headers.entries());
|
|
1020
1021
|
}
|
|
1021
|
-
function buildQueryContext$
|
|
1022
|
+
function buildQueryContext$2(input) {
|
|
1022
1023
|
return {
|
|
1023
1024
|
...input.query?.trim() ? { query: input.query.trim() } : {},
|
|
1024
1025
|
...input.sourceLanguage?.trim() ? { sourceLanguage: input.sourceLanguage.trim() } : {},
|
|
1025
1026
|
...input.targetLanguage?.trim() ? { targetLanguage: input.targetLanguage.trim() } : {}
|
|
1026
1027
|
};
|
|
1027
1028
|
}
|
|
1028
|
-
function readNextCursor$
|
|
1029
|
+
function readNextCursor$2(linkHeader) {
|
|
1029
1030
|
if (!linkHeader) return void 0;
|
|
1030
1031
|
const match = /[?&]cursor=([^&>]+).*rel="next"/.exec(linkHeader);
|
|
1031
1032
|
if (!match) return void 0;
|
|
@@ -1035,10 +1036,10 @@ function readNextCursor$1(linkHeader) {
|
|
|
1035
1036
|
return match[1];
|
|
1036
1037
|
}
|
|
1037
1038
|
}
|
|
1038
|
-
function normalizeSearchLimit$
|
|
1039
|
-
return Math.min(Math.max(limit ?? DEFAULT_SEARCH_LIMIT$
|
|
1039
|
+
function normalizeSearchLimit$2(limit) {
|
|
1040
|
+
return Math.min(Math.max(limit ?? DEFAULT_SEARCH_LIMIT$2, 1), 20);
|
|
1040
1041
|
}
|
|
1041
|
-
function formatBytes$
|
|
1042
|
+
function formatBytes$5(value) {
|
|
1042
1043
|
if (!Number.isFinite(value) || value <= 0) return "0 B";
|
|
1043
1044
|
const units = [
|
|
1044
1045
|
"B",
|
|
@@ -1061,16 +1062,16 @@ function normalizeNonNegativeNumber$1(value) {
|
|
|
1061
1062
|
function normalizeOptionalNumber$1(value) {
|
|
1062
1063
|
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
|
|
1063
1064
|
}
|
|
1064
|
-
function isHfModelDetail$
|
|
1065
|
+
function isHfModelDetail$2(value) {
|
|
1065
1066
|
return "siblings" in value;
|
|
1066
1067
|
}
|
|
1067
1068
|
function isString$1(value) {
|
|
1068
1069
|
return typeof value === "string";
|
|
1069
1070
|
}
|
|
1070
|
-
function isRecord$
|
|
1071
|
+
function isRecord$2(value) {
|
|
1071
1072
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1072
1073
|
}
|
|
1073
|
-
function delay$
|
|
1074
|
+
function delay$6(ms) {
|
|
1074
1075
|
return new Promise((resolve$1) => {
|
|
1075
1076
|
setTimeout(resolve$1, ms);
|
|
1076
1077
|
});
|
|
@@ -1228,7 +1229,7 @@ async function readHuggingFaceRepositorySnapshot(input) {
|
|
|
1228
1229
|
} catch (error) {
|
|
1229
1230
|
lastError = error;
|
|
1230
1231
|
}
|
|
1231
|
-
if (attempt < 2) await delay$
|
|
1232
|
+
if (attempt < 2) await delay$5(300 * (attempt + 1));
|
|
1232
1233
|
}
|
|
1233
1234
|
const cachedFiles = await readCachedHuggingFaceRepositoryFiles(input);
|
|
1234
1235
|
if (cachedFiles.files.length > 0 && cachedFiles.commitHash) return {
|
|
@@ -1293,7 +1294,7 @@ function createProviderFetchCache(fetchCacheStore) {
|
|
|
1293
1294
|
url,
|
|
1294
1295
|
status: response.status,
|
|
1295
1296
|
ok: response.ok,
|
|
1296
|
-
headers: headersToRecord$
|
|
1297
|
+
headers: headersToRecord$2(response.headers),
|
|
1297
1298
|
bodyText: await response.clone().text()
|
|
1298
1299
|
});
|
|
1299
1300
|
return response;
|
|
@@ -1304,18 +1305,18 @@ function normalizeRequestUrl(input) {
|
|
|
1304
1305
|
if (input instanceof URL) return input.href;
|
|
1305
1306
|
return input.url;
|
|
1306
1307
|
}
|
|
1307
|
-
function headersToRecord$
|
|
1308
|
+
function headersToRecord$2(headers) {
|
|
1308
1309
|
return Object.fromEntries(headers.entries());
|
|
1309
1310
|
}
|
|
1310
|
-
function delay$
|
|
1311
|
+
function delay$5(ms) {
|
|
1311
1312
|
return new Promise((resolve$1) => setTimeout(resolve$1, ms));
|
|
1312
1313
|
}
|
|
1313
1314
|
|
|
1314
1315
|
//#endregion
|
|
1315
1316
|
//#region src/ct2-model-asset-service.ts
|
|
1316
|
-
const DEFAULT_NETWORK_RETRY_LIMIT$
|
|
1317
|
-
const DEFAULT_NETWORK_RETRY_DELAY_MS$
|
|
1318
|
-
const DEFAULT_NETWORK_RETRY_DELAY_MAX_MS$
|
|
1317
|
+
const DEFAULT_NETWORK_RETRY_LIMIT$2 = Number.POSITIVE_INFINITY;
|
|
1318
|
+
const DEFAULT_NETWORK_RETRY_DELAY_MS$2 = 500;
|
|
1319
|
+
const DEFAULT_NETWORK_RETRY_DELAY_MAX_MS$2 = 5e3;
|
|
1319
1320
|
var Ct2ModelAssetService = class {
|
|
1320
1321
|
now;
|
|
1321
1322
|
store;
|
|
@@ -1333,9 +1334,9 @@ var Ct2ModelAssetService = class {
|
|
|
1333
1334
|
this.now = options.now ?? Date.now;
|
|
1334
1335
|
this.cacheDir = options.cacheDir ?? getDefaultLocalCt2ModelCacheDir();
|
|
1335
1336
|
this.networkRetryPolicy = {
|
|
1336
|
-
limit: options.networkRetryPolicy?.limit ?? DEFAULT_NETWORK_RETRY_LIMIT$
|
|
1337
|
-
delayMs: options.networkRetryPolicy?.delayMs ?? DEFAULT_NETWORK_RETRY_DELAY_MS$
|
|
1338
|
-
maxDelayMs: options.networkRetryPolicy?.maxDelayMs ?? DEFAULT_NETWORK_RETRY_DELAY_MAX_MS$
|
|
1337
|
+
limit: options.networkRetryPolicy?.limit ?? DEFAULT_NETWORK_RETRY_LIMIT$2,
|
|
1338
|
+
delayMs: options.networkRetryPolicy?.delayMs ?? DEFAULT_NETWORK_RETRY_DELAY_MS$2,
|
|
1339
|
+
maxDelayMs: options.networkRetryPolicy?.maxDelayMs ?? DEFAULT_NETWORK_RETRY_DELAY_MAX_MS$2
|
|
1339
1340
|
};
|
|
1340
1341
|
this.store = new LocalModelAssetStore({ indexPath: options.indexPath ?? getDefaultLocalCt2ModelIndexPath() });
|
|
1341
1342
|
this.profileManifestStore = new LocalModelProfileManifestStore({ manifestPath: options.profileManifestPath ?? getDefaultLocalCt2ModelProfileManifestPath() });
|
|
@@ -1358,10 +1359,10 @@ var Ct2ModelAssetService = class {
|
|
|
1358
1359
|
const localMap = await this.store.readMap();
|
|
1359
1360
|
const items = await Promise.all([...localMap.values()].map(async (state) => {
|
|
1360
1361
|
const asset = await this.refreshCachedState(state);
|
|
1361
|
-
return toCatalogItem$
|
|
1362
|
+
return toCatalogItem$2({
|
|
1362
1363
|
id: state.modelId,
|
|
1363
1364
|
label: state.modelId,
|
|
1364
|
-
summary: state.plan?.estimatedTotalBytes !== void 0 ? `Previously selected CT2 model. Estimated download ${formatBytes$
|
|
1365
|
+
summary: state.plan?.estimatedTotalBytes !== void 0 ? `Previously selected CT2 model. Estimated download ${formatBytes$4(state.plan.estimatedTotalBytes)}.` : "Previously selected CT2 model.",
|
|
1365
1366
|
downloads: 0,
|
|
1366
1367
|
likes: 0,
|
|
1367
1368
|
tags: ["local-ct2"],
|
|
@@ -1382,7 +1383,7 @@ var Ct2ModelAssetService = class {
|
|
|
1382
1383
|
}
|
|
1383
1384
|
}, asset);
|
|
1384
1385
|
}));
|
|
1385
|
-
items.sort(compareCatalogItems$
|
|
1386
|
+
items.sort(compareCatalogItems$2);
|
|
1386
1387
|
return { items };
|
|
1387
1388
|
}
|
|
1388
1389
|
async searchRemoteCatalog(input) {
|
|
@@ -1392,7 +1393,7 @@ var Ct2ModelAssetService = class {
|
|
|
1392
1393
|
this.readSelectedModel()
|
|
1393
1394
|
]);
|
|
1394
1395
|
const items = await this.decorateCatalogItems(remote.items, localMap, selectedModel);
|
|
1395
|
-
items.sort(compareCatalogItems$
|
|
1396
|
+
items.sort(compareCatalogItems$2);
|
|
1396
1397
|
return {
|
|
1397
1398
|
items,
|
|
1398
1399
|
nextCursor: remote.nextCursor
|
|
@@ -1464,7 +1465,7 @@ var Ct2ModelAssetService = class {
|
|
|
1464
1465
|
if (!requestedGroupId) return { success: true };
|
|
1465
1466
|
const current = await this.readSelectedModelState(modelId, requestedGroupId);
|
|
1466
1467
|
const effectiveGroupId = current.plan?.selectedGroupId ?? current.selectedGroupId ?? requestedGroupId;
|
|
1467
|
-
const sessionKey = buildSessionKey$
|
|
1468
|
+
const sessionKey = buildSessionKey$2(modelId, effectiveGroupId);
|
|
1468
1469
|
const session = this.sessions.get(sessionKey);
|
|
1469
1470
|
if (session) {
|
|
1470
1471
|
session.abortController.abort();
|
|
@@ -1511,7 +1512,7 @@ var Ct2ModelAssetService = class {
|
|
|
1511
1512
|
}
|
|
1512
1513
|
const current = await this.readSelectedModelState(modelId, requestedGroupId);
|
|
1513
1514
|
const effectiveGroupId = current.plan?.selectedGroupId ?? current.selectedGroupId ?? requestedGroupId;
|
|
1514
|
-
const sessionKey = buildSessionKey$
|
|
1515
|
+
const sessionKey = buildSessionKey$2(modelId, effectiveGroupId);
|
|
1515
1516
|
this.sessions.get(sessionKey)?.abortController.abort();
|
|
1516
1517
|
this.sessions.delete(sessionKey);
|
|
1517
1518
|
await this.store.upsert(LocalModelAssetStateSchema.parse({
|
|
@@ -1544,8 +1545,8 @@ var Ct2ModelAssetService = class {
|
|
|
1544
1545
|
const persistedManifest = await this.profileManifestStore.read(modelId);
|
|
1545
1546
|
const nextGroupsState = { ...current.groupsState };
|
|
1546
1547
|
delete nextGroupsState[effectiveGroupId];
|
|
1547
|
-
const nextManifest = persistedManifest ? removeManifestGroup$
|
|
1548
|
-
const nextPlan = removePlanGroup$
|
|
1548
|
+
const nextManifest = persistedManifest ? removeManifestGroup$2(persistedManifest, effectiveGroupId) : removeManifestGroup$2(current.profileManifest, effectiveGroupId);
|
|
1549
|
+
const nextPlan = removePlanGroup$2(current.plan, effectiveGroupId);
|
|
1549
1550
|
const nextSelectedGroupId = current.selectedGroupId === effectiveGroupId ? void 0 : current.selectedGroupId;
|
|
1550
1551
|
const nextState = await this.refreshCachedState(LocalModelAssetStateSchema.parse({
|
|
1551
1552
|
...current,
|
|
@@ -1659,7 +1660,7 @@ var Ct2ModelAssetService = class {
|
|
|
1659
1660
|
const remoteItems = await Promise.all(candidates.map(async (candidate) => {
|
|
1660
1661
|
seen.add(candidate.id);
|
|
1661
1662
|
const localState = localMap.get(candidate.id);
|
|
1662
|
-
return toCatalogItem$
|
|
1663
|
+
return toCatalogItem$2(candidate, localState ? await this.refreshCachedState(localState) : LocalModelAssetStateSchema.parse({
|
|
1663
1664
|
modelId: candidate.id,
|
|
1664
1665
|
status: "not-downloaded",
|
|
1665
1666
|
selected: candidate.id === selectedModel,
|
|
@@ -1668,10 +1669,10 @@ var Ct2ModelAssetService = class {
|
|
|
1668
1669
|
}));
|
|
1669
1670
|
const localOnlyItems = options.includeLocalOnly === false ? [] : await Promise.all([...localMap.values()].filter((state) => !seen.has(state.modelId)).map(async (state) => {
|
|
1670
1671
|
const asset = await this.refreshCachedState(state);
|
|
1671
|
-
return toCatalogItem$
|
|
1672
|
+
return toCatalogItem$2({
|
|
1672
1673
|
id: state.modelId,
|
|
1673
1674
|
label: state.modelId,
|
|
1674
|
-
summary: state.plan?.estimatedTotalBytes !== void 0 ? `Previously selected CT2 model. Estimated download ${formatBytes$
|
|
1675
|
+
summary: state.plan?.estimatedTotalBytes !== void 0 ? `Previously selected CT2 model. Estimated download ${formatBytes$4(state.plan.estimatedTotalBytes)}.` : "Previously selected CT2 model.",
|
|
1675
1676
|
downloads: 0,
|
|
1676
1677
|
likes: 0,
|
|
1677
1678
|
tags: ["local-ct2"],
|
|
@@ -1701,12 +1702,12 @@ var Ct2ModelAssetService = class {
|
|
|
1701
1702
|
this.profileManifestStore.read(state.modelId)
|
|
1702
1703
|
]);
|
|
1703
1704
|
const selected = state.selected || state.modelId === selectedModel;
|
|
1704
|
-
const manifest = state.profileManifest ?? persistedManifest ?? createSyntheticManifestFromPlan({
|
|
1705
|
+
const manifest = state.profileManifest ?? persistedManifest ?? createSyntheticManifestFromPlan$1({
|
|
1705
1706
|
cacheDir: this.cacheDir,
|
|
1706
1707
|
modelId: state.modelId,
|
|
1707
1708
|
plan: state.plan
|
|
1708
1709
|
});
|
|
1709
|
-
const selectedGroupIdForProjection = resolveManifestGroupId$
|
|
1710
|
+
const selectedGroupIdForProjection = resolveManifestGroupId$2(manifest, selectedGroupId ?? persistedSelectedGroupId ?? state.selectedGroupId ?? state.plan?.selectedGroupId) ?? selectFirstManifestGroupId$2(manifest) ?? state.plan?.selectedGroupId;
|
|
1710
1711
|
const groupsState = manifest ? options.revalidateDisk ? await this.reconcileGroupsFromDisk({
|
|
1711
1712
|
manifest,
|
|
1712
1713
|
groupsState: state.groupsState
|
|
@@ -1714,13 +1715,13 @@ var Ct2ModelAssetService = class {
|
|
|
1714
1715
|
manifest,
|
|
1715
1716
|
groupsState: state.groupsState
|
|
1716
1717
|
}) : state.groupsState;
|
|
1717
|
-
const plan = manifest ? buildPlanFromManifest$
|
|
1718
|
+
const plan = manifest ? buildPlanFromManifest$2({
|
|
1718
1719
|
modelId: state.modelId,
|
|
1719
1720
|
manifest,
|
|
1720
1721
|
groupsState,
|
|
1721
1722
|
selectedGroupId: selectedGroupIdForProjection
|
|
1722
1723
|
}) : state.plan ?? void 0;
|
|
1723
|
-
const selectedPlanGroup = selectPlanGroup(plan, selectedGroupIdForProjection);
|
|
1724
|
+
const selectedPlanGroup = selectPlanGroup$1(plan, selectedGroupIdForProjection);
|
|
1724
1725
|
const selectedGroupState = selectedPlanGroup && groupsState[selectedPlanGroup.id] ? groupsState[selectedPlanGroup.id] : void 0;
|
|
1725
1726
|
return LocalModelAssetStateSchema.parse({
|
|
1726
1727
|
...state,
|
|
@@ -1750,12 +1751,12 @@ var Ct2ModelAssetService = class {
|
|
|
1750
1751
|
const manifestGroup = input.manifest.groups[groupId];
|
|
1751
1752
|
if (!manifestGroup) continue;
|
|
1752
1753
|
const current = nextGroupsState[groupId];
|
|
1753
|
-
const files = reconcileGroupFilesFromSnapshot$
|
|
1754
|
+
const files = reconcileGroupFilesFromSnapshot$2({
|
|
1754
1755
|
manifestGroup,
|
|
1755
1756
|
currentFiles: current?.files ?? [],
|
|
1756
1757
|
currentStatus: current?.status ?? "not-downloaded"
|
|
1757
1758
|
});
|
|
1758
|
-
const bytesDownloaded = sumDownloadedBytes$
|
|
1759
|
+
const bytesDownloaded = sumDownloadedBytes$2(files);
|
|
1759
1760
|
const totalBytes = manifestGroup.estimatedTotalBytes;
|
|
1760
1761
|
const status = current?.status ?? "not-downloaded";
|
|
1761
1762
|
nextGroupsState[groupId] = LocalModelLifecycleGroupStateSchema.parse({
|
|
@@ -1782,26 +1783,26 @@ var Ct2ModelAssetService = class {
|
|
|
1782
1783
|
const manifestGroup = input.manifest.groups[groupId];
|
|
1783
1784
|
if (!manifestGroup) continue;
|
|
1784
1785
|
const current = nextGroupsState[groupId];
|
|
1785
|
-
if (isActiveDownloadStatus$
|
|
1786
|
+
if (isActiveDownloadStatus$2(current?.status ?? "not-downloaded")) {
|
|
1786
1787
|
nextGroupsState[groupId] = LocalModelLifecycleGroupStateSchema.parse({
|
|
1787
1788
|
...current,
|
|
1788
1789
|
groupId,
|
|
1789
1790
|
baseGroupId: manifestGroup.baseGroupId,
|
|
1790
1791
|
rootDir: manifestGroup.rootDir,
|
|
1791
1792
|
totalBytes: manifestGroup.estimatedTotalBytes,
|
|
1792
|
-
files: reconcileGroupFiles$
|
|
1793
|
+
files: reconcileGroupFiles$2({
|
|
1793
1794
|
manifestGroup,
|
|
1794
1795
|
currentFiles: current?.files ?? []
|
|
1795
1796
|
})
|
|
1796
1797
|
});
|
|
1797
1798
|
continue;
|
|
1798
1799
|
}
|
|
1799
|
-
const files = await reconcileGroupFilesFromDisk$
|
|
1800
|
+
const files = await reconcileGroupFilesFromDisk$2({
|
|
1800
1801
|
rootDir: manifestGroup.rootDir,
|
|
1801
1802
|
manifestGroup,
|
|
1802
1803
|
currentFiles: current?.files ?? []
|
|
1803
1804
|
});
|
|
1804
|
-
const bytesDownloaded = sumDownloadedBytes$
|
|
1805
|
+
const bytesDownloaded = sumDownloadedBytes$2(files);
|
|
1805
1806
|
const totalBytes = manifestGroup.estimatedTotalBytes;
|
|
1806
1807
|
const allComplete = files.length > 0 && files.every((file) => file.sizeBytes !== void 0 && (file.downloadedBytes ?? 0) >= file.sizeBytes && file.status === "downloaded");
|
|
1807
1808
|
const hasPartial = files.some((file) => (file.downloadedBytes ?? 0) > 0);
|
|
@@ -1828,12 +1829,12 @@ var Ct2ModelAssetService = class {
|
|
|
1828
1829
|
const effectiveGroupId = groupId ?? await this.readSelectedGroupId();
|
|
1829
1830
|
if (!effectiveGroupId) throw new Error("No CT2 model artifact group is selected.");
|
|
1830
1831
|
const manifest = await this.ensureProfileManifest(modelId);
|
|
1831
|
-
const resolvedGroupId = resolveManifestGroupId$
|
|
1832
|
+
const resolvedGroupId = resolveManifestGroupId$2(manifest, effectiveGroupId);
|
|
1832
1833
|
if (!resolvedGroupId) throw new Error("No concrete CT2 model download plan is available.");
|
|
1833
|
-
const sessionKey = buildSessionKey$
|
|
1834
|
+
const sessionKey = buildSessionKey$2(modelId, resolvedGroupId);
|
|
1834
1835
|
const existing = this.sessions.get(sessionKey);
|
|
1835
1836
|
if (existing) return { sessionId: existing.sessionId };
|
|
1836
|
-
const sessionId = `local-ct2-model-${sanitizeId$
|
|
1837
|
+
const sessionId = `local-ct2-model-${sanitizeId$2(modelId)}-${sanitizeId$2(resolvedGroupId)}-${this.now()}`;
|
|
1837
1838
|
const abortController = new AbortController();
|
|
1838
1839
|
this.sessions.set(sessionKey, {
|
|
1839
1840
|
modelId,
|
|
@@ -1849,12 +1850,12 @@ var Ct2ModelAssetService = class {
|
|
|
1849
1850
|
}
|
|
1850
1851
|
const totalBytes = manifestGroup.estimatedTotalBytes;
|
|
1851
1852
|
const currentGroup = current.groupsState[resolvedGroupId];
|
|
1852
|
-
const resumedFiles = await reconcileGroupFilesFromDisk$
|
|
1853
|
+
const resumedFiles = await reconcileGroupFilesFromDisk$2({
|
|
1853
1854
|
rootDir: manifestGroup.rootDir,
|
|
1854
1855
|
manifestGroup,
|
|
1855
1856
|
currentFiles: currentGroup?.files ?? []
|
|
1856
1857
|
});
|
|
1857
|
-
const resumedBytesDownloaded = sumDownloadedBytes$
|
|
1858
|
+
const resumedBytesDownloaded = sumDownloadedBytes$2(resumedFiles);
|
|
1858
1859
|
const nextState = LocalModelAssetStateSchema.parse({
|
|
1859
1860
|
...current,
|
|
1860
1861
|
modelId,
|
|
@@ -1927,7 +1928,7 @@ var Ct2ModelAssetService = class {
|
|
|
1927
1928
|
if (!basePlan?.groups?.length) throw new Error(`No recognizable CT2 model artifacts were found for ${modelId}.`);
|
|
1928
1929
|
const groupsEntries = basePlan.groups.flatMap((group) => {
|
|
1929
1930
|
if (!group.selectable || group.estimatedTotalBytes === void 0) return [];
|
|
1930
|
-
const groupId = buildVersionedGroupId$
|
|
1931
|
+
const groupId = buildVersionedGroupId$2(group.id, snapshot.shortCommitHash);
|
|
1931
1932
|
const rootDir = getLocalCt2ModelArtifactGroupRoot(this.cacheDir, modelId, groupId);
|
|
1932
1933
|
return [[groupId, {
|
|
1933
1934
|
id: groupId,
|
|
@@ -1971,15 +1972,15 @@ var Ct2ModelAssetService = class {
|
|
|
1971
1972
|
const files = manifestGroup.files;
|
|
1972
1973
|
const totalBytes = manifestGroup.estimatedTotalBytes;
|
|
1973
1974
|
const currentGroup = (await this.readSelectedModelState(modelId, groupId)).groupsState[groupId];
|
|
1974
|
-
const downloadedFiles = await reconcileGroupFilesFromDisk$
|
|
1975
|
+
const downloadedFiles = await reconcileGroupFilesFromDisk$2({
|
|
1975
1976
|
rootDir: manifestGroup.rootDir,
|
|
1976
1977
|
manifestGroup,
|
|
1977
1978
|
currentFiles: currentGroup?.files ?? []
|
|
1978
1979
|
});
|
|
1979
|
-
let bytesDownloaded = sumDownloadedBytes$
|
|
1980
|
+
let bytesDownloaded = sumDownloadedBytes$2(downloadedFiles);
|
|
1980
1981
|
if (files.length === 0 || totalBytes === void 0) throw new Error("No concrete CT2 model download files were selected.");
|
|
1981
1982
|
for (const [fileIndex, file] of files.entries()) {
|
|
1982
|
-
throwIfAborted$
|
|
1983
|
+
throwIfAborted$2(signal);
|
|
1983
1984
|
const previousFileBytes = downloadedFiles[fileIndex]?.downloadedBytes ?? 0;
|
|
1984
1985
|
if (file.sizeBytes !== void 0 && previousFileBytes >= file.sizeBytes) continue;
|
|
1985
1986
|
downloadedFiles[fileIndex] = {
|
|
@@ -1998,14 +1999,14 @@ var Ct2ModelAssetService = class {
|
|
|
1998
1999
|
bytesDownloaded,
|
|
1999
2000
|
files: downloadedFiles
|
|
2000
2001
|
});
|
|
2001
|
-
await downloadUrlFileWithProgress({
|
|
2002
|
+
await downloadUrlFileWithProgress$1({
|
|
2002
2003
|
url: file.sourceUrl ?? `${manifest.endpoint}/${modelId}/resolve/${manifestGroup.commitHash}/${file.path}`,
|
|
2003
2004
|
targetPath: join(manifestGroup.rootDir, file.path),
|
|
2004
2005
|
expectedSizeBytes: file.sizeBytes,
|
|
2005
2006
|
retryPolicy: this.networkRetryPolicy,
|
|
2006
2007
|
signal,
|
|
2007
2008
|
onProgress: async (fileBytesDownloaded) => {
|
|
2008
|
-
throwIfAborted$
|
|
2009
|
+
throwIfAborted$2(signal);
|
|
2009
2010
|
const boundedFileBytes = file.sizeBytes ? Math.min(file.sizeBytes, fileBytesDownloaded) : fileBytesDownloaded;
|
|
2010
2011
|
downloadedFiles[fileIndex] = {
|
|
2011
2012
|
path: file.path,
|
|
@@ -2029,14 +2030,14 @@ var Ct2ModelAssetService = class {
|
|
|
2029
2030
|
modelId,
|
|
2030
2031
|
groupId,
|
|
2031
2032
|
sessionId,
|
|
2032
|
-
message: `Connection interrupted while downloading ${file.path}. Retrying automatically in ${formatDuration$
|
|
2033
|
+
message: `Connection interrupted while downloading ${file.path}. Retrying automatically in ${formatDuration$2(retryDelayMs)}.`,
|
|
2033
2034
|
totalBytes,
|
|
2034
2035
|
bytesDownloaded: bytesDownloaded - previousFileBytes + (downloadedFiles[fileIndex]?.downloadedBytes ?? 0),
|
|
2035
2036
|
files: downloadedFiles
|
|
2036
2037
|
});
|
|
2037
2038
|
}
|
|
2038
2039
|
});
|
|
2039
|
-
throwIfAborted$
|
|
2040
|
+
throwIfAborted$2(signal);
|
|
2040
2041
|
const nextDownloadedBytes = file.sizeBytes ?? 0;
|
|
2041
2042
|
bytesDownloaded = bytesDownloaded - previousFileBytes + nextDownloadedBytes;
|
|
2042
2043
|
downloadedFiles[fileIndex] = {
|
|
@@ -2105,7 +2106,7 @@ var Ct2ModelAssetService = class {
|
|
|
2105
2106
|
}
|
|
2106
2107
|
async finishDownload(modelId, groupId, sessionId, success, message) {
|
|
2107
2108
|
if (!this.isActiveSession(modelId, groupId, sessionId)) return;
|
|
2108
|
-
const sessionKey = buildSessionKey$
|
|
2109
|
+
const sessionKey = buildSessionKey$2(modelId, groupId);
|
|
2109
2110
|
const current = await this.readSelectedModelState(modelId, groupId);
|
|
2110
2111
|
const currentGroup = current.groupsState[groupId];
|
|
2111
2112
|
const totalBytes = currentGroup?.totalBytes ?? current.totalBytes;
|
|
@@ -2169,25 +2170,28 @@ var Ct2ModelAssetService = class {
|
|
|
2169
2170
|
return (await this.options.globalSettingsManager.readSettings()).translationEngines.localCt2.hfEndpoint;
|
|
2170
2171
|
}
|
|
2171
2172
|
isActiveSession(modelId, groupId, sessionId) {
|
|
2172
|
-
return this.sessions.get(buildSessionKey$
|
|
2173
|
+
return this.sessions.get(buildSessionKey$2(modelId, groupId))?.sessionId === sessionId;
|
|
2173
2174
|
}
|
|
2174
2175
|
emitLog(log) {
|
|
2175
2176
|
this.logs.set(log.modelId, log);
|
|
2176
2177
|
for (const listener of this.listeners) listener(log);
|
|
2177
2178
|
}
|
|
2178
2179
|
};
|
|
2179
|
-
function toCatalogItem$
|
|
2180
|
+
function toCatalogItem$2(candidate, asset) {
|
|
2180
2181
|
const downloadGroups = asset.plan?.groups ?? candidate.downloadGroups;
|
|
2181
2182
|
const hasSelectableGroup = downloadGroups?.some((group) => group.selectable) ?? false;
|
|
2183
|
+
const local = asset.status === "downloaded" || asset.status === "paused" || asset.status === "downloading" || (asset.progress ?? 0) > 0;
|
|
2182
2184
|
return {
|
|
2183
2185
|
...candidate,
|
|
2184
2186
|
downloadGroups,
|
|
2185
2187
|
asset,
|
|
2186
2188
|
selectable: hasSelectableGroup || (candidate.size.estimatedTotalBytes ?? 0) > 0,
|
|
2187
|
-
local
|
|
2189
|
+
local,
|
|
2190
|
+
primarySource: local ? "local" : "network",
|
|
2191
|
+
sources: [local ? "local" : "network"]
|
|
2188
2192
|
};
|
|
2189
2193
|
}
|
|
2190
|
-
function compareCatalogItems$
|
|
2194
|
+
function compareCatalogItems$2(left, right) {
|
|
2191
2195
|
if (left.local !== right.local) return left.local ? -1 : 1;
|
|
2192
2196
|
if (left.asset.selected !== right.asset.selected) return left.asset.selected ? -1 : 1;
|
|
2193
2197
|
const rightProgress = right.asset.progress ?? 0;
|
|
@@ -2195,7 +2199,7 @@ function compareCatalogItems$1(left, right) {
|
|
|
2195
2199
|
if (left.local && right.local && leftProgress !== rightProgress) return rightProgress - leftProgress;
|
|
2196
2200
|
return right.downloads - left.downloads;
|
|
2197
2201
|
}
|
|
2198
|
-
function createSyntheticManifestFromPlan(input) {
|
|
2202
|
+
function createSyntheticManifestFromPlan$1(input) {
|
|
2199
2203
|
const groupEntries = (input.plan?.groups ?? []).flatMap((group) => {
|
|
2200
2204
|
if (!group.commitHash || !group.shortCommitHash || group.estimatedTotalBytes === void 0 || !group.selectable) return [];
|
|
2201
2205
|
return [[group.id, {
|
|
@@ -2232,15 +2236,15 @@ function createSyntheticManifestFromPlan(input) {
|
|
|
2232
2236
|
groupOrder: groupEntries.map(([groupId]) => groupId)
|
|
2233
2237
|
});
|
|
2234
2238
|
}
|
|
2235
|
-
function buildPlanFromManifest$
|
|
2236
|
-
const selectedGroupId = input.selectedGroupId && input.manifest.groups[input.selectedGroupId]?.selectable ? input.selectedGroupId : selectFirstManifestGroupId$
|
|
2239
|
+
function buildPlanFromManifest$2(input) {
|
|
2240
|
+
const selectedGroupId = input.selectedGroupId && input.manifest.groups[input.selectedGroupId]?.selectable ? input.selectedGroupId : selectFirstManifestGroupId$2(input.manifest);
|
|
2237
2241
|
const groups = input.manifest.groupOrder.flatMap((groupId) => {
|
|
2238
2242
|
const manifestGroup = input.manifest.groups[groupId];
|
|
2239
2243
|
if (!manifestGroup) return [];
|
|
2240
2244
|
const groupState = input.groupsState[groupId];
|
|
2241
2245
|
return [{
|
|
2242
2246
|
id: manifestGroup.id,
|
|
2243
|
-
label: formatManifestGroupChipLabel$
|
|
2247
|
+
label: formatManifestGroupChipLabel$2(input.manifest, manifestGroup),
|
|
2244
2248
|
description: manifestGroup.description,
|
|
2245
2249
|
profile: manifestGroup.profile,
|
|
2246
2250
|
dtype: manifestGroup.dtype,
|
|
@@ -2273,11 +2277,11 @@ function buildPlanFromManifest$1(input) {
|
|
|
2273
2277
|
groups
|
|
2274
2278
|
};
|
|
2275
2279
|
}
|
|
2276
|
-
function formatManifestGroupChipLabel$
|
|
2280
|
+
function formatManifestGroupChipLabel$2(manifest, group) {
|
|
2277
2281
|
if (group.commitHash === manifest.commitHash) return group.label;
|
|
2278
2282
|
return `${group.label} · ${group.shortCommitHash}`;
|
|
2279
2283
|
}
|
|
2280
|
-
function resolveManifestGroupId$
|
|
2284
|
+
function resolveManifestGroupId$2(manifest, requestedGroupId) {
|
|
2281
2285
|
if (!manifest || !requestedGroupId) return requestedGroupId;
|
|
2282
2286
|
if (manifest.groups[requestedGroupId]?.selectable) return requestedGroupId;
|
|
2283
2287
|
return manifest.groupOrder.find((groupId) => {
|
|
@@ -2285,15 +2289,15 @@ function resolveManifestGroupId$1(manifest, requestedGroupId) {
|
|
|
2285
2289
|
return group?.selectable && group.baseGroupId === requestedGroupId;
|
|
2286
2290
|
});
|
|
2287
2291
|
}
|
|
2288
|
-
function selectFirstManifestGroupId$
|
|
2292
|
+
function selectFirstManifestGroupId$2(manifest) {
|
|
2289
2293
|
return manifest?.groupOrder.find((groupId) => manifest.groups[groupId]?.selectable);
|
|
2290
2294
|
}
|
|
2291
|
-
function selectPlanGroup(plan, selectedGroupId) {
|
|
2295
|
+
function selectPlanGroup$1(plan, selectedGroupId) {
|
|
2292
2296
|
if (!plan) return null;
|
|
2293
2297
|
if (selectedGroupId) return plan.groups?.find((group) => group.id === selectedGroupId) ?? null;
|
|
2294
2298
|
return plan.groups?.find((group) => group.selected) ?? plan.groups?.[0] ?? null;
|
|
2295
2299
|
}
|
|
2296
|
-
function removeManifestGroup$
|
|
2300
|
+
function removeManifestGroup$2(manifest, groupId) {
|
|
2297
2301
|
if (!manifest) return void 0;
|
|
2298
2302
|
const groups = { ...manifest.groups };
|
|
2299
2303
|
delete groups[groupId];
|
|
@@ -2305,7 +2309,7 @@ function removeManifestGroup$1(manifest, groupId) {
|
|
|
2305
2309
|
groupOrder
|
|
2306
2310
|
});
|
|
2307
2311
|
}
|
|
2308
|
-
function removePlanGroup$
|
|
2312
|
+
function removePlanGroup$2(plan, groupId) {
|
|
2309
2313
|
if (!plan) return void 0;
|
|
2310
2314
|
const groups = plan.groups?.filter((group) => group.id !== groupId);
|
|
2311
2315
|
if (!groups?.length) return void 0;
|
|
@@ -2321,7 +2325,7 @@ function removePlanGroup$1(plan, groupId) {
|
|
|
2321
2325
|
}))
|
|
2322
2326
|
};
|
|
2323
2327
|
}
|
|
2324
|
-
function reconcileGroupFiles$
|
|
2328
|
+
function reconcileGroupFiles$2(input) {
|
|
2325
2329
|
const currentFileByPath = new Map(input.currentFiles.map((file) => [file.path, file]));
|
|
2326
2330
|
return input.manifestGroup.files.map((file) => {
|
|
2327
2331
|
const current = currentFileByPath.get(file.path);
|
|
@@ -2338,7 +2342,7 @@ function reconcileGroupFiles$1(input) {
|
|
|
2338
2342
|
});
|
|
2339
2343
|
});
|
|
2340
2344
|
}
|
|
2341
|
-
function reconcileGroupFilesFromSnapshot$
|
|
2345
|
+
function reconcileGroupFilesFromSnapshot$2(input) {
|
|
2342
2346
|
const currentFileByPath = new Map(input.currentFiles.map((file) => [file.path, file]));
|
|
2343
2347
|
return input.manifestGroup.files.map((file) => {
|
|
2344
2348
|
const current = currentFileByPath.get(file.path);
|
|
@@ -2355,7 +2359,7 @@ function reconcileGroupFilesFromSnapshot$1(input) {
|
|
|
2355
2359
|
});
|
|
2356
2360
|
});
|
|
2357
2361
|
}
|
|
2358
|
-
async function reconcileGroupFilesFromDisk$
|
|
2362
|
+
async function reconcileGroupFilesFromDisk$2(input) {
|
|
2359
2363
|
const currentFileByPath = new Map(input.currentFiles.map((file) => [file.path, file]));
|
|
2360
2364
|
return Promise.all(input.manifestGroup.files.map(async (file) => {
|
|
2361
2365
|
const current = currentFileByPath.get(file.path);
|
|
@@ -2373,26 +2377,26 @@ async function reconcileGroupFilesFromDisk$1(input) {
|
|
|
2373
2377
|
});
|
|
2374
2378
|
}));
|
|
2375
2379
|
}
|
|
2376
|
-
function isActiveDownloadStatus$
|
|
2380
|
+
function isActiveDownloadStatus$2(status) {
|
|
2377
2381
|
return status === "queued" || status === "downloading" || status === "deleting";
|
|
2378
2382
|
}
|
|
2379
|
-
function sumDownloadedBytes$
|
|
2383
|
+
function sumDownloadedBytes$2(files) {
|
|
2380
2384
|
return files.reduce((total, file) => {
|
|
2381
2385
|
const downloadedBytes = file.downloadedBytes ?? 0;
|
|
2382
2386
|
if (file.sizeBytes === void 0) return total + downloadedBytes;
|
|
2383
2387
|
return total + Math.min(downloadedBytes, file.sizeBytes);
|
|
2384
2388
|
}, 0);
|
|
2385
2389
|
}
|
|
2386
|
-
function buildSessionKey$
|
|
2390
|
+
function buildSessionKey$2(modelId, groupId) {
|
|
2387
2391
|
return `${modelId}::${groupId}`;
|
|
2388
2392
|
}
|
|
2389
|
-
function buildVersionedGroupId$
|
|
2390
|
-
return `${sanitizeId$
|
|
2393
|
+
function buildVersionedGroupId$2(baseGroupId, shortCommitHash) {
|
|
2394
|
+
return `${sanitizeId$2(baseGroupId)}-${sanitizeId$2(shortCommitHash)}`;
|
|
2391
2395
|
}
|
|
2392
|
-
function sanitizeId$
|
|
2396
|
+
function sanitizeId$2(value) {
|
|
2393
2397
|
return value.replace(/[^a-zA-Z0-9_-]+/g, "-");
|
|
2394
2398
|
}
|
|
2395
|
-
function formatBytes$
|
|
2399
|
+
function formatBytes$4(value) {
|
|
2396
2400
|
if (!Number.isFinite(value) || value <= 0) return "0 B";
|
|
2397
2401
|
const units = [
|
|
2398
2402
|
"B",
|
|
@@ -2409,11 +2413,11 @@ function formatBytes$2(value) {
|
|
|
2409
2413
|
const digits = size >= 100 || unitIndex === 0 ? 0 : 1;
|
|
2410
2414
|
return `${size.toFixed(digits)} ${units[unitIndex]}`;
|
|
2411
2415
|
}
|
|
2412
|
-
function formatDuration$
|
|
2416
|
+
function formatDuration$2(ms) {
|
|
2413
2417
|
if (ms < 1e3) return `${ms} ms`;
|
|
2414
2418
|
return `${(ms / 1e3).toFixed(ms >= 1e4 ? 0 : 1)} s`;
|
|
2415
2419
|
}
|
|
2416
|
-
function throwIfAborted$
|
|
2420
|
+
function throwIfAborted$2(signal) {
|
|
2417
2421
|
if (signal.aborted) throw new Error("CT2 model download aborted.");
|
|
2418
2422
|
}
|
|
2419
2423
|
async function readPathSize$1(path) {
|
|
@@ -2423,7 +2427,7 @@ async function readPathSize$1(path) {
|
|
|
2423
2427
|
return null;
|
|
2424
2428
|
}
|
|
2425
2429
|
}
|
|
2426
|
-
async function downloadUrlFileWithProgress(input) {
|
|
2430
|
+
async function downloadUrlFileWithProgress$1(input) {
|
|
2427
2431
|
if (input.expectedSizeBytes !== void 0) {
|
|
2428
2432
|
const existingTargetSize = await readPathSize$1(input.targetPath);
|
|
2429
2433
|
if (existingTargetSize !== null && existingTargetSize >= input.expectedSizeBytes) {
|
|
@@ -2433,7 +2437,7 @@ async function downloadUrlFileWithProgress(input) {
|
|
|
2433
2437
|
}
|
|
2434
2438
|
let lastError;
|
|
2435
2439
|
for (let attempt = 0; attempt <= input.retryPolicy.limit; attempt += 1) try {
|
|
2436
|
-
throwIfAborted$
|
|
2440
|
+
throwIfAborted$2(input.signal);
|
|
2437
2441
|
await streamDownloadAttempt(input);
|
|
2438
2442
|
return;
|
|
2439
2443
|
} catch (error) {
|
|
@@ -2441,7 +2445,7 @@ async function downloadUrlFileWithProgress(input) {
|
|
|
2441
2445
|
if (!isRetryableDownloadError$1(error) || attempt === input.retryPolicy.limit) throw error;
|
|
2442
2446
|
const retryDelayMs = Math.min(input.retryPolicy.maxDelayMs, input.retryPolicy.delayMs * (attempt + 1));
|
|
2443
2447
|
await input.onRetry?.({ retryDelayMs });
|
|
2444
|
-
await delay$
|
|
2448
|
+
await delay$4(retryDelayMs, input.signal);
|
|
2445
2449
|
}
|
|
2446
2450
|
throw lastError instanceof Error ? lastError : /* @__PURE__ */ new Error(`Cannot download ${input.url}.`);
|
|
2447
2451
|
}
|
|
@@ -2476,7 +2480,7 @@ async function streamDownloadAttempt(input) {
|
|
|
2476
2480
|
try {
|
|
2477
2481
|
const reader = body.getReader();
|
|
2478
2482
|
while (true) {
|
|
2479
|
-
throwIfAborted$
|
|
2483
|
+
throwIfAborted$2(input.signal);
|
|
2480
2484
|
const { done, value } = await reader.read();
|
|
2481
2485
|
if (done) break;
|
|
2482
2486
|
if (!value) continue;
|
|
@@ -2503,7 +2507,7 @@ function isRetryableDownloadError$1(error) {
|
|
|
2503
2507
|
if (!(error instanceof Error)) return false;
|
|
2504
2508
|
return /status 408|status 409|status 425|status 429|status 500|status 502|status 503|status 504/u.test(error.message);
|
|
2505
2509
|
}
|
|
2506
|
-
function delay$
|
|
2510
|
+
function delay$4(ms, signal) {
|
|
2507
2511
|
return new Promise((resolve$1, reject) => {
|
|
2508
2512
|
const timeout = setTimeout(() => {
|
|
2509
2513
|
signal?.removeEventListener("abort", onAbort);
|
|
@@ -3577,119 +3581,1622 @@ const SESSION_PREVIEW_KINDS = new Set([
|
|
|
3577
3581
|
function isSessionPreviewKind(previewKind) {
|
|
3578
3582
|
return SESSION_PREVIEW_KINDS.has(previewKind);
|
|
3579
3583
|
}
|
|
3580
|
-
function toHash(input) {
|
|
3581
|
-
return createHash("sha256").update(input).digest("hex");
|
|
3584
|
+
function toHash(input) {
|
|
3585
|
+
return createHash("sha256").update(input).digest("hex");
|
|
3586
|
+
}
|
|
3587
|
+
function stripLeadingSlash(path) {
|
|
3588
|
+
return path.replace(/^\/+/, "");
|
|
3589
|
+
}
|
|
3590
|
+
function inferPreviewAssetContentType(path) {
|
|
3591
|
+
switch (extname(path).toLowerCase()) {
|
|
3592
|
+
case ".html": return "text/html";
|
|
3593
|
+
case ".js":
|
|
3594
|
+
case ".mjs": return "application/javascript";
|
|
3595
|
+
case ".css": return "text/css";
|
|
3596
|
+
case ".json": return "application/json";
|
|
3597
|
+
case ".svg": return "image/svg+xml";
|
|
3598
|
+
case ".png": return "image/png";
|
|
3599
|
+
case ".jpg":
|
|
3600
|
+
case ".jpeg": return "image/jpeg";
|
|
3601
|
+
case ".woff": return "font/woff";
|
|
3602
|
+
case ".woff2": return "font/woff2";
|
|
3603
|
+
default: return inferFileMime(path) ?? "application/octet-stream";
|
|
3604
|
+
}
|
|
3605
|
+
}
|
|
3606
|
+
function isRewritablePreviewAsset(path) {
|
|
3607
|
+
const extension = extname(path).toLowerCase();
|
|
3608
|
+
return extension === ".html" || extension === ".js" || extension === ".mjs" || extension === ".css";
|
|
3609
|
+
}
|
|
3610
|
+
function rewritePreviewAssetPaths(content, hash) {
|
|
3611
|
+
const sessionAssetPrefix = `/api/file-preview/${hash}/assets/`;
|
|
3612
|
+
return content.replaceAll("/assets/", sessionAssetPrefix);
|
|
3613
|
+
}
|
|
3614
|
+
var FilePreviewService = class {
|
|
3615
|
+
sessions = /* @__PURE__ */ new Map();
|
|
3616
|
+
constructor(projectDir, previewAssetsDir) {
|
|
3617
|
+
this.projectDir = projectDir;
|
|
3618
|
+
this.previewAssetsDir = previewAssetsDir;
|
|
3619
|
+
}
|
|
3620
|
+
prepareEntityFilePreview(input) {
|
|
3621
|
+
const resolved = resolveEntityEntryPath({
|
|
3622
|
+
projectDir: this.projectDir,
|
|
3623
|
+
stage: input.stage,
|
|
3624
|
+
changeId: input.changeId,
|
|
3625
|
+
path: input.path
|
|
3626
|
+
});
|
|
3627
|
+
if (!statSync(resolved.absolutePath, { throwIfNoEntry: false })?.isFile()) throw new Error("Preview target file not found.");
|
|
3628
|
+
const mime = inferFileMime(resolved.relativePath);
|
|
3629
|
+
if (!mime) throw new Error("Preview target mime is unknown.");
|
|
3630
|
+
const previewKind = inferFilePreviewKind(resolved.relativePath, mime);
|
|
3631
|
+
if (!isSessionPreviewKind(previewKind)) throw new Error("Preview route is not supported for this file type.");
|
|
3632
|
+
const directoryPath = resolve(resolved.absolutePath, "..");
|
|
3633
|
+
const hash = toHash(`${directoryPath}:${mime}`);
|
|
3634
|
+
const entryFileName = previewKind === "html" ? null : PREVIEW_ENTRY_FILE_BY_KIND[previewKind];
|
|
3635
|
+
const fileName = basename(resolved.absolutePath);
|
|
3636
|
+
this.sessions.set(hash, {
|
|
3637
|
+
hash,
|
|
3638
|
+
directoryPath,
|
|
3639
|
+
mime,
|
|
3640
|
+
previewKind,
|
|
3641
|
+
entryFileName
|
|
3642
|
+
});
|
|
3643
|
+
const htmlPathname = `/api/file-preview/${hash}/${fileName}`;
|
|
3644
|
+
const resourcePathname = previewKind === "html" ? null : `/api/file-preview/${hash}/resource/${fileName}`;
|
|
3645
|
+
const entryPathname = previewKind === "html" ? htmlPathname : `/api/file-preview/${hash}/${entryFileName}`;
|
|
3646
|
+
return {
|
|
3647
|
+
hash,
|
|
3648
|
+
mime,
|
|
3649
|
+
previewKind,
|
|
3650
|
+
relativePath: resolved.relativePath,
|
|
3651
|
+
resourcePathname,
|
|
3652
|
+
entryPathname,
|
|
3653
|
+
urlPath: previewKind === "html" ? htmlPathname : `${entryPathname}?file=${encodeURIComponent(fileName)}`
|
|
3654
|
+
};
|
|
3655
|
+
}
|
|
3656
|
+
readPreviewRequest(hash, requestPath) {
|
|
3657
|
+
const session = this.sessions.get(hash);
|
|
3658
|
+
if (!session) return null;
|
|
3659
|
+
const normalized = stripLeadingSlash(requestPath);
|
|
3660
|
+
if (session.previewKind === "html") {
|
|
3661
|
+
const absolutePath$1 = resolve(session.directoryPath, normalized);
|
|
3662
|
+
if (!absolutePath$1.startsWith(session.directoryPath + "/")) return null;
|
|
3663
|
+
if (!existsSync(absolutePath$1) || !statSync(absolutePath$1).isFile()) return null;
|
|
3664
|
+
return {
|
|
3665
|
+
content: readFileSync(absolutePath$1),
|
|
3666
|
+
contentType: inferFileMime(absolutePath$1) ?? "application/octet-stream"
|
|
3667
|
+
};
|
|
3668
|
+
}
|
|
3669
|
+
if (normalized.startsWith("resource/")) {
|
|
3670
|
+
const resourcePath = normalized.slice(9);
|
|
3671
|
+
const absolutePath$1 = resolve(session.directoryPath, resourcePath);
|
|
3672
|
+
if (!absolutePath$1.startsWith(session.directoryPath + "/")) return null;
|
|
3673
|
+
if (!existsSync(absolutePath$1) || !statSync(absolutePath$1).isFile()) return null;
|
|
3674
|
+
return {
|
|
3675
|
+
content: readFileSync(absolutePath$1),
|
|
3676
|
+
contentType: inferFileMime(absolutePath$1) ?? "application/octet-stream"
|
|
3677
|
+
};
|
|
3678
|
+
}
|
|
3679
|
+
const assetName = normalized || session.entryFileName;
|
|
3680
|
+
if (!assetName) return null;
|
|
3681
|
+
const absolutePath = resolve(this.previewAssetsDir, assetName);
|
|
3682
|
+
if (!absolutePath.startsWith(resolve(this.previewAssetsDir) + "/")) return null;
|
|
3683
|
+
if (!existsSync(absolutePath) || !statSync(absolutePath).isFile()) return null;
|
|
3684
|
+
if (isRewritablePreviewAsset(assetName)) {
|
|
3685
|
+
const rewritten = rewritePreviewAssetPaths(readFileSync(absolutePath, "utf8"), hash);
|
|
3686
|
+
return {
|
|
3687
|
+
content: Buffer.from(rewritten, "utf8"),
|
|
3688
|
+
contentType: inferPreviewAssetContentType(absolutePath)
|
|
3689
|
+
};
|
|
3690
|
+
}
|
|
3691
|
+
return {
|
|
3692
|
+
content: readFileSync(absolutePath),
|
|
3693
|
+
contentType: inferPreviewAssetContentType(absolutePath)
|
|
3694
|
+
};
|
|
3695
|
+
}
|
|
3696
|
+
};
|
|
3697
|
+
|
|
3698
|
+
//#endregion
|
|
3699
|
+
//#region src/local-llama-model-cache-path.ts
|
|
3700
|
+
function getDefaultLocalLlamaModelCacheRoot() {
|
|
3701
|
+
return join(getOpenSpecUICacheDir(), "translation-engines", "local-llama");
|
|
3702
|
+
}
|
|
3703
|
+
function getDefaultLocalLlamaModelCacheDir() {
|
|
3704
|
+
return join(getDefaultLocalLlamaModelCacheRoot(), "hf-cache");
|
|
3705
|
+
}
|
|
3706
|
+
function getDefaultLocalLlamaModelIndexPath() {
|
|
3707
|
+
return join(getDefaultLocalLlamaModelCacheRoot(), "models.json");
|
|
3708
|
+
}
|
|
3709
|
+
function getDefaultLocalLlamaModelProfileManifestPath() {
|
|
3710
|
+
return join(getDefaultLocalLlamaModelCacheRoot(), "profile-manifests.json");
|
|
3711
|
+
}
|
|
3712
|
+
function getDefaultLocalLlamaModelFetchCachePath() {
|
|
3713
|
+
return join(getDefaultLocalLlamaModelCacheRoot(), "fetch-cache.json");
|
|
3714
|
+
}
|
|
3715
|
+
function getLocalLlamaModelArtifactRoot(cacheDir, modelId) {
|
|
3716
|
+
return join(cacheDir, "artifacts", sanitizeLocalModelPathSegment(modelId));
|
|
3717
|
+
}
|
|
3718
|
+
function getLocalLlamaModelArtifactGroupRoot(cacheDir, modelId, groupId) {
|
|
3719
|
+
return join(getLocalLlamaModelArtifactRoot(cacheDir, modelId), sanitizeLocalModelPathSegment(groupId));
|
|
3720
|
+
}
|
|
3721
|
+
|
|
3722
|
+
//#endregion
|
|
3723
|
+
//#region src/llama-model-catalog.ts
|
|
3724
|
+
const DEFAULT_SEARCH_LIMIT$1 = 6;
|
|
3725
|
+
const MAX_SEARCH_FETCH_LIMIT$1 = 12;
|
|
3726
|
+
const DEFAULT_RECOMMENDED_MODEL_IDS = ["bartowski/Qwen2.5-0.5B-Instruct-GGUF"];
|
|
3727
|
+
const HUGGING_FACE_FETCH_RETRY_COUNT$1 = 2;
|
|
3728
|
+
const HUGGING_FACE_FETCH_RETRY_DELAY_MS$1 = 750;
|
|
3729
|
+
const HUGGING_FACE_FETCH_DISPATCHER$1 = createProxyAwareDispatcher();
|
|
3730
|
+
async function searchLlamaModels(input, options = {}) {
|
|
3731
|
+
if (!input.query?.trim()) return { items: await readRecommendedCandidates(options) };
|
|
3732
|
+
const list = await fetchHuggingFaceModelList$1(input, options);
|
|
3733
|
+
return {
|
|
3734
|
+
items: rankCandidates$1(await Promise.all(list.items.map(async (item) => {
|
|
3735
|
+
const detail = await getHuggingFaceModelDetail$1(item.id, input, options).catch(() => null);
|
|
3736
|
+
return detail ? toTranslationModelCandidate$1(detail, input) : toTranslationModelCandidate$1(item, input);
|
|
3737
|
+
})), input).slice(0, normalizeSearchLimit$1(input.limit)),
|
|
3738
|
+
nextCursor: list.nextCursor
|
|
3739
|
+
};
|
|
3740
|
+
}
|
|
3741
|
+
async function searchLlamaModelsProgressively(input, options = {}) {
|
|
3742
|
+
if (!input.query?.trim()) {
|
|
3743
|
+
const recommended = await readRecommendedCandidates(options);
|
|
3744
|
+
return [
|
|
3745
|
+
{
|
|
3746
|
+
requestId: input.requestId,
|
|
3747
|
+
phase: "candidates",
|
|
3748
|
+
items: recommended
|
|
3749
|
+
},
|
|
3750
|
+
{
|
|
3751
|
+
requestId: input.requestId,
|
|
3752
|
+
phase: "enriched",
|
|
3753
|
+
items: recommended
|
|
3754
|
+
},
|
|
3755
|
+
{
|
|
3756
|
+
requestId: input.requestId,
|
|
3757
|
+
phase: "complete",
|
|
3758
|
+
items: recommended
|
|
3759
|
+
}
|
|
3760
|
+
];
|
|
3761
|
+
}
|
|
3762
|
+
const list = await fetchHuggingFaceModelList$1(input, options);
|
|
3763
|
+
const candidateShells = rankCandidates$1(list.items.map((item) => toTranslationModelCandidate$1(item, input)), input).slice(0, normalizeSearchLimit$1(input.limit));
|
|
3764
|
+
const events = [{
|
|
3765
|
+
requestId: input.requestId,
|
|
3766
|
+
phase: "candidates",
|
|
3767
|
+
items: candidateShells,
|
|
3768
|
+
nextCursor: list.nextCursor
|
|
3769
|
+
}];
|
|
3770
|
+
const ranked = rankCandidates$1(await Promise.all(candidateShells.map(async (candidate) => {
|
|
3771
|
+
const detail = await getHuggingFaceModelDetail$1(candidate.id, input, options).catch(() => null);
|
|
3772
|
+
return detail ? toTranslationModelCandidate$1(detail, input) : candidate;
|
|
3773
|
+
})), input).slice(0, normalizeSearchLimit$1(input.limit));
|
|
3774
|
+
events.push({
|
|
3775
|
+
requestId: input.requestId,
|
|
3776
|
+
phase: "enriched",
|
|
3777
|
+
items: ranked,
|
|
3778
|
+
nextCursor: list.nextCursor
|
|
3779
|
+
});
|
|
3780
|
+
events.push({
|
|
3781
|
+
requestId: input.requestId,
|
|
3782
|
+
phase: "complete",
|
|
3783
|
+
items: ranked,
|
|
3784
|
+
nextCursor: list.nextCursor
|
|
3785
|
+
});
|
|
3786
|
+
return events;
|
|
3787
|
+
}
|
|
3788
|
+
async function readRecommendedCandidates(options) {
|
|
3789
|
+
return rankCandidates$1((await Promise.all(DEFAULT_RECOMMENDED_MODEL_IDS.map((modelId) => getHuggingFaceModelDetail$1(modelId, void 0, options).catch(() => null)))).filter((detail) => detail !== null).map((detail) => toTranslationModelCandidate$1(detail, {})), {}).slice(0, DEFAULT_SEARCH_LIMIT$1);
|
|
3790
|
+
}
|
|
3791
|
+
async function fetchHuggingFaceModelList$1(input, options) {
|
|
3792
|
+
const limit = normalizeSearchLimit$1(input.limit);
|
|
3793
|
+
const fetchLimit = Math.min(Math.max(limit * 2, limit), MAX_SEARCH_FETCH_LIMIT$1);
|
|
3794
|
+
const params = new URLSearchParams({
|
|
3795
|
+
sort: "trendingScore",
|
|
3796
|
+
direction: "-1",
|
|
3797
|
+
limit: String(fetchLimit)
|
|
3798
|
+
});
|
|
3799
|
+
if (input.query?.trim()) params.set("search", input.query.trim());
|
|
3800
|
+
if (input.cursor?.trim()) params.set("cursor", input.cursor.trim());
|
|
3801
|
+
const url = `${buildHuggingFaceApiBaseUrl(options.hfEndpoint)}/models?${params.toString()}`;
|
|
3802
|
+
const response = await fetchHuggingFace$1(url);
|
|
3803
|
+
const responseBody = await response.text();
|
|
3804
|
+
const fetchCacheStore = getFetchCacheStore$1(options);
|
|
3805
|
+
await fetchCacheStore.upsertProviderFetch({
|
|
3806
|
+
url,
|
|
3807
|
+
status: response.status,
|
|
3808
|
+
ok: response.ok,
|
|
3809
|
+
headers: headersToRecord$1(response.headers),
|
|
3810
|
+
bodyText: responseBody,
|
|
3811
|
+
queryContext: buildQueryContext$1(input)
|
|
3812
|
+
});
|
|
3813
|
+
if (!response.ok) throw new Error(`Hugging Face model search failed with status ${response.status}.`);
|
|
3814
|
+
const listJson = parseJson$1(responseBody);
|
|
3815
|
+
const rawItems = Array.isArray(listJson) ? listJson.filter(isRecord$1) : [];
|
|
3816
|
+
const items = rawItems.map(normalizeHfModelListItem$1).filter((item) => item !== null);
|
|
3817
|
+
for (const raw of rawItems) {
|
|
3818
|
+
const item = normalizeHfModelListItem$1(raw);
|
|
3819
|
+
if (!item) continue;
|
|
3820
|
+
await fetchCacheStore.upsertListItem({
|
|
3821
|
+
modelId: item.id,
|
|
3822
|
+
raw,
|
|
3823
|
+
queryContext: buildQueryContext$1(input)
|
|
3824
|
+
});
|
|
3825
|
+
}
|
|
3826
|
+
return {
|
|
3827
|
+
items,
|
|
3828
|
+
nextCursor: readNextCursor$1(response.headers.get("link"))
|
|
3829
|
+
};
|
|
3830
|
+
}
|
|
3831
|
+
async function getHuggingFaceModelDetail$1(modelId, input, options) {
|
|
3832
|
+
const [namespace, repo] = modelId.split("/", 2);
|
|
3833
|
+
const modelPath = namespace && repo ? `${encodeURIComponent(namespace)}/${encodeURIComponent(repo)}` : encodeURIComponent(modelId);
|
|
3834
|
+
const url = `${buildHuggingFaceApiBaseUrl(options.hfEndpoint)}/models/${modelPath}?blobs=true`;
|
|
3835
|
+
const response = await fetchHuggingFace$1(url);
|
|
3836
|
+
const responseBody = await response.text();
|
|
3837
|
+
await getFetchCacheStore$1(options).upsertProviderFetch({
|
|
3838
|
+
url,
|
|
3839
|
+
status: response.status,
|
|
3840
|
+
ok: response.ok,
|
|
3841
|
+
headers: headersToRecord$1(response.headers),
|
|
3842
|
+
bodyText: responseBody,
|
|
3843
|
+
queryContext: input ? buildQueryContext$1(input) : void 0
|
|
3844
|
+
});
|
|
3845
|
+
if (!response.ok) throw new Error(`Hugging Face model detail failed with status ${response.status}.`);
|
|
3846
|
+
const detailJson = parseJson$1(responseBody);
|
|
3847
|
+
const raw = isRecord$1(detailJson) ? detailJson : {};
|
|
3848
|
+
await getFetchCacheStore$1(options).upsertDetail({
|
|
3849
|
+
modelId,
|
|
3850
|
+
raw,
|
|
3851
|
+
queryContext: input ? buildQueryContext$1(input) : void 0
|
|
3852
|
+
});
|
|
3853
|
+
return normalizeHfModelDetail$1(detailJson, modelId);
|
|
3854
|
+
}
|
|
3855
|
+
function toTranslationModelCandidate$1(detail, input) {
|
|
3856
|
+
const plan = isHfModelDetail$1(detail) ? resolveGgufModelDownloadPlanFromRepositoryFiles({
|
|
3857
|
+
modelId: detail.id,
|
|
3858
|
+
files: detail.siblings.map((entry) => ({
|
|
3859
|
+
path: entry.rfilename,
|
|
3860
|
+
sizeBytes: entry.size
|
|
3861
|
+
}))
|
|
3862
|
+
}) : null;
|
|
3863
|
+
const estimatedTotalBytes = plan?.estimatedTotalBytes;
|
|
3864
|
+
const verified = plan !== null;
|
|
3865
|
+
return {
|
|
3866
|
+
id: detail.id,
|
|
3867
|
+
label: detail.id,
|
|
3868
|
+
summary: buildCandidateSummary$1(detail, verified, estimatedTotalBytes),
|
|
3869
|
+
downloads: detail.downloads,
|
|
3870
|
+
likes: detail.likes,
|
|
3871
|
+
trendingScore: detail.trendingScore,
|
|
3872
|
+
lastModified: detail.lastModified,
|
|
3873
|
+
pipelineTag: detail.pipeline_tag,
|
|
3874
|
+
tags: detail.tags,
|
|
3875
|
+
compatibility: {
|
|
3876
|
+
transformersJs: false,
|
|
3877
|
+
onnx: false,
|
|
3878
|
+
localRuntimeVerified: verified
|
|
3879
|
+
},
|
|
3880
|
+
size: {
|
|
3881
|
+
estimatedTotalBytes,
|
|
3882
|
+
primaryBytes: estimatedTotalBytes
|
|
3883
|
+
},
|
|
3884
|
+
downloadGroups: plan?.groups,
|
|
3885
|
+
languageMatch: buildLanguageMatch$1(detail, input.query)
|
|
3886
|
+
};
|
|
3887
|
+
}
|
|
3888
|
+
function buildCandidateSummary$1(detail, verified, estimatedTotalBytes) {
|
|
3889
|
+
const parts = [verified ? "Verified GGUF runtime model." : "Model from Hugging Face."];
|
|
3890
|
+
if (hasGgufSignal(detail)) parts.push("GGUF artifact detected.");
|
|
3891
|
+
if (estimatedTotalBytes !== void 0) parts.push(`Estimated download ${formatBytes$3(estimatedTotalBytes)}.`);
|
|
3892
|
+
return parts.join(" ");
|
|
3893
|
+
}
|
|
3894
|
+
function buildLanguageMatch$1(detail, query) {
|
|
3895
|
+
const normalizedQuery = query?.trim().toLowerCase() ?? "";
|
|
3896
|
+
const haystack = `${detail.id} ${detail.tags.join(" ")}`.toLowerCase();
|
|
3897
|
+
const queryMatched = normalizedQuery.length > 0 && haystack.includes(normalizedQuery);
|
|
3898
|
+
return {
|
|
3899
|
+
sourceMatched: queryMatched,
|
|
3900
|
+
targetMatched: queryMatched,
|
|
3901
|
+
directionalScore: queryMatched ? 1 : 0
|
|
3902
|
+
};
|
|
3903
|
+
}
|
|
3904
|
+
function rankCandidates$1(candidates, input) {
|
|
3905
|
+
const verifiedCandidates = candidates.filter((candidate) => candidate.compatibility.localRuntimeVerified);
|
|
3906
|
+
return [...input.query?.trim() ? candidates : verifiedCandidates.length > 0 ? verifiedCandidates : candidates].sort((left, right) => scoreCandidate(right, input) - scoreCandidate(left, input));
|
|
3907
|
+
}
|
|
3908
|
+
function scoreCandidate(candidate, input) {
|
|
3909
|
+
const normalizedQuery = input.query?.trim().toLowerCase() ?? "";
|
|
3910
|
+
const queryMatchBoost = normalizedQuery.length > 0 && candidate.id.toLowerCase().includes(normalizedQuery) ? 18 : 0;
|
|
3911
|
+
const verifiedBoost = candidate.compatibility.localRuntimeVerified ? 36 : 0;
|
|
3912
|
+
const ggufBoost = candidate.tags.some((tag) => tag.toLowerCase() === "gguf") ? 12 : 0;
|
|
3913
|
+
const recommendedBoost = DEFAULT_RECOMMENDED_MODEL_IDS.includes(candidate.id) ? 24 : 0;
|
|
3914
|
+
const signalBoost = candidate.tags.some((tag) => /translation|multilingual|mt/iu.test(tag)) ? 8 : candidate.tags.some((tag) => /conversational|chat/iu.test(tag)) ? 3 : 0;
|
|
3915
|
+
return verifiedBoost + ggufBoost + recommendedBoost + signalBoost + queryMatchBoost + Math.min(candidate.downloads / 1e4, 12) + Math.min(candidate.likes / 200, 8) + Math.min(candidate.trendingScore ?? 0, 20);
|
|
3916
|
+
}
|
|
3917
|
+
function hasGgufSignal(detail) {
|
|
3918
|
+
if (detail.tags.some((tag) => tag.toLowerCase() === "gguf")) return true;
|
|
3919
|
+
return isHfModelDetail$1(detail) ? detail.siblings.some((entry) => entry.rfilename.toLowerCase().endsWith(".gguf")) : false;
|
|
3920
|
+
}
|
|
3921
|
+
function normalizeSearchLimit$1(limit) {
|
|
3922
|
+
return Math.min(Math.max(limit ?? DEFAULT_SEARCH_LIMIT$1, 1), DEFAULT_SEARCH_LIMIT$1);
|
|
3923
|
+
}
|
|
3924
|
+
function buildQueryContext$1(input) {
|
|
3925
|
+
return {
|
|
3926
|
+
...input.query?.trim() ? { query: input.query.trim() } : {},
|
|
3927
|
+
...input.sourceLanguage?.trim() ? { sourceLanguage: input.sourceLanguage.trim() } : {},
|
|
3928
|
+
...input.targetLanguage?.trim() ? { targetLanguage: input.targetLanguage.trim() } : {}
|
|
3929
|
+
};
|
|
3930
|
+
}
|
|
3931
|
+
function parseJson$1(text) {
|
|
3932
|
+
try {
|
|
3933
|
+
return JSON.parse(text);
|
|
3934
|
+
} catch {
|
|
3935
|
+
return null;
|
|
3936
|
+
}
|
|
3937
|
+
}
|
|
3938
|
+
function headersToRecord$1(headers) {
|
|
3939
|
+
return Object.fromEntries(headers.entries());
|
|
3940
|
+
}
|
|
3941
|
+
async function fetchHuggingFace$1(input) {
|
|
3942
|
+
let lastError;
|
|
3943
|
+
for (let attempt = 0; attempt <= HUGGING_FACE_FETCH_RETRY_COUNT$1; attempt += 1) {
|
|
3944
|
+
try {
|
|
3945
|
+
const response = await fetchWithDispatcher$1(input);
|
|
3946
|
+
if (response.ok || !isRetryableNetworkStatusCode(response.status)) return response;
|
|
3947
|
+
lastError = /* @__PURE__ */ new Error(`Hugging Face request failed with status ${response.status}.`);
|
|
3948
|
+
if (attempt === HUGGING_FACE_FETCH_RETRY_COUNT$1) return response;
|
|
3949
|
+
await response.body?.cancel().catch(() => void 0);
|
|
3950
|
+
} catch (error) {
|
|
3951
|
+
lastError = error;
|
|
3952
|
+
if (!isRetryableNetworkError(error) || attempt === HUGGING_FACE_FETCH_RETRY_COUNT$1) throw error;
|
|
3953
|
+
}
|
|
3954
|
+
await delay$3(HUGGING_FACE_FETCH_RETRY_DELAY_MS$1 * (attempt + 1));
|
|
3955
|
+
}
|
|
3956
|
+
throw lastError instanceof Error ? lastError : /* @__PURE__ */ new Error("Hugging Face request failed.");
|
|
3957
|
+
}
|
|
3958
|
+
async function fetchWithDispatcher$1(input) {
|
|
3959
|
+
return fetch(input, {
|
|
3960
|
+
dispatcher: HUGGING_FACE_FETCH_DISPATCHER$1,
|
|
3961
|
+
headers: { Accept: "application/json" }
|
|
3962
|
+
});
|
|
3963
|
+
}
|
|
3964
|
+
function normalizeHfModelListItem$1(value) {
|
|
3965
|
+
const id = typeof value.id === "string" ? value.id : null;
|
|
3966
|
+
if (!id) return null;
|
|
3967
|
+
return {
|
|
3968
|
+
id,
|
|
3969
|
+
pipeline_tag: typeof value.pipeline_tag === "string" ? value.pipeline_tag : void 0,
|
|
3970
|
+
tags: Array.isArray(value.tags) ? value.tags.filter((tag) => typeof tag === "string") : [],
|
|
3971
|
+
downloads: typeof value.downloads === "number" ? value.downloads : 0,
|
|
3972
|
+
likes: typeof value.likes === "number" ? value.likes : 0,
|
|
3973
|
+
trendingScore: typeof value.trendingScore === "number" ? value.trendingScore : void 0,
|
|
3974
|
+
lastModified: typeof value.lastModified === "string" ? value.lastModified : void 0
|
|
3975
|
+
};
|
|
3976
|
+
}
|
|
3977
|
+
function normalizeHfModelDetail$1(value, modelId) {
|
|
3978
|
+
const record = isRecord$1(value) ? value : {};
|
|
3979
|
+
return {
|
|
3980
|
+
...normalizeHfModelListItem$1({
|
|
3981
|
+
id: modelId,
|
|
3982
|
+
...record
|
|
3983
|
+
}) ?? {
|
|
3984
|
+
id: modelId,
|
|
3985
|
+
tags: [],
|
|
3986
|
+
downloads: 0,
|
|
3987
|
+
likes: 0
|
|
3988
|
+
},
|
|
3989
|
+
siblings: Array.isArray(record.siblings) ? record.siblings.filter(isRecord$1).map((entry) => ({
|
|
3990
|
+
rfilename: typeof entry.rfilename === "string" ? entry.rfilename : "",
|
|
3991
|
+
size: typeof entry.size === "number" ? entry.size : void 0
|
|
3992
|
+
})).filter((entry) => entry.rfilename.length > 0) : []
|
|
3993
|
+
};
|
|
3994
|
+
}
|
|
3995
|
+
function isRecord$1(value) {
|
|
3996
|
+
return typeof value === "object" && value !== null;
|
|
3997
|
+
}
|
|
3998
|
+
function isHfModelDetail$1(value) {
|
|
3999
|
+
return Array.isArray(value.siblings);
|
|
4000
|
+
}
|
|
4001
|
+
function readNextCursor$1(linkHeader) {
|
|
4002
|
+
if (!linkHeader) return void 0;
|
|
4003
|
+
const nextMatch = linkHeader.match(/<[^>]*[?&]cursor=([^&>]+)[^>]*>;\s*rel="next"/iu);
|
|
4004
|
+
return nextMatch?.[1] ? decodeURIComponent(nextMatch[1]) : void 0;
|
|
4005
|
+
}
|
|
4006
|
+
function getFetchCacheStore$1(options) {
|
|
4007
|
+
return options.fetchCacheStore ?? new LocalModelFetchCacheStore({ cachePath: getDefaultLocalLlamaModelFetchCachePath() });
|
|
4008
|
+
}
|
|
4009
|
+
function formatBytes$3(value) {
|
|
4010
|
+
if (value < 1024) return `${value} B`;
|
|
4011
|
+
const units = [
|
|
4012
|
+
"KB",
|
|
4013
|
+
"MB",
|
|
4014
|
+
"GB",
|
|
4015
|
+
"TB"
|
|
4016
|
+
];
|
|
4017
|
+
let size = value / 1024;
|
|
4018
|
+
let unitIndex = 0;
|
|
4019
|
+
while (size >= 1024 && unitIndex < units.length - 1) {
|
|
4020
|
+
size /= 1024;
|
|
4021
|
+
unitIndex += 1;
|
|
4022
|
+
}
|
|
4023
|
+
const digits = size >= 100 ? 0 : size >= 10 ? 1 : 2;
|
|
4024
|
+
return `${size.toFixed(digits)} ${units[unitIndex]}`;
|
|
4025
|
+
}
|
|
4026
|
+
async function delay$3(ms) {
|
|
4027
|
+
await new Promise((resolve$1) => setTimeout(resolve$1, ms));
|
|
4028
|
+
}
|
|
4029
|
+
|
|
4030
|
+
//#endregion
|
|
4031
|
+
//#region src/llama-model-asset-service.ts
|
|
4032
|
+
const DEFAULT_NETWORK_RETRY_LIMIT$1 = Number.POSITIVE_INFINITY;
|
|
4033
|
+
const DEFAULT_NETWORK_RETRY_DELAY_MS$1 = 500;
|
|
4034
|
+
const DEFAULT_NETWORK_RETRY_DELAY_MAX_MS$1 = 5e3;
|
|
4035
|
+
var LlamaModelAssetService = class {
|
|
4036
|
+
now;
|
|
4037
|
+
store;
|
|
4038
|
+
profileManifestStore;
|
|
4039
|
+
cacheDir;
|
|
4040
|
+
fetchCacheStore;
|
|
4041
|
+
networkRetryPolicy;
|
|
4042
|
+
listeners = /* @__PURE__ */ new Set();
|
|
4043
|
+
sessions = /* @__PURE__ */ new Map();
|
|
4044
|
+
sessionTasks = /* @__PURE__ */ new Map();
|
|
4045
|
+
logs = /* @__PURE__ */ new Map();
|
|
4046
|
+
constructor(options) {
|
|
4047
|
+
this.options = options;
|
|
4048
|
+
ensureProxyAwareFetchDispatcher();
|
|
4049
|
+
this.now = options.now ?? Date.now;
|
|
4050
|
+
this.cacheDir = options.cacheDir ?? getDefaultLocalLlamaModelCacheDir();
|
|
4051
|
+
this.networkRetryPolicy = {
|
|
4052
|
+
limit: options.networkRetryPolicy?.limit ?? DEFAULT_NETWORK_RETRY_LIMIT$1,
|
|
4053
|
+
delayMs: options.networkRetryPolicy?.delayMs ?? DEFAULT_NETWORK_RETRY_DELAY_MS$1,
|
|
4054
|
+
maxDelayMs: options.networkRetryPolicy?.maxDelayMs ?? DEFAULT_NETWORK_RETRY_DELAY_MAX_MS$1
|
|
4055
|
+
};
|
|
4056
|
+
this.store = new LocalModelAssetStore({ indexPath: options.indexPath ?? getDefaultLocalLlamaModelIndexPath() });
|
|
4057
|
+
this.profileManifestStore = new LocalModelProfileManifestStore({ manifestPath: options.profileManifestPath ?? getDefaultLocalLlamaModelProfileManifestPath() });
|
|
4058
|
+
this.fetchCacheStore = new LocalModelFetchCacheStore({
|
|
4059
|
+
cachePath: options.fetchCachePath ?? getDefaultLocalLlamaModelFetchCachePath(),
|
|
4060
|
+
now: this.now
|
|
4061
|
+
});
|
|
4062
|
+
}
|
|
4063
|
+
subscribeLogs() {
|
|
4064
|
+
return observable((emit) => {
|
|
4065
|
+
for (const log of this.logs.values()) emit.next(log);
|
|
4066
|
+
const listener = (log) => emit.next(log);
|
|
4067
|
+
this.listeners.add(listener);
|
|
4068
|
+
return () => {
|
|
4069
|
+
this.listeners.delete(listener);
|
|
4070
|
+
};
|
|
4071
|
+
});
|
|
4072
|
+
}
|
|
4073
|
+
async listLocalCatalog() {
|
|
4074
|
+
const localMap = await this.store.readMap();
|
|
4075
|
+
const items = await Promise.all([...localMap.values()].map(async (state) => {
|
|
4076
|
+
const asset = await this.refreshCachedState(state);
|
|
4077
|
+
return toCatalogItem$1({
|
|
4078
|
+
id: state.modelId,
|
|
4079
|
+
label: state.modelId,
|
|
4080
|
+
summary: state.plan?.estimatedTotalBytes !== void 0 ? `Previously selected llama GGUF model. Estimated download ${formatBytes$2(state.plan.estimatedTotalBytes)}.` : "Previously selected llama GGUF model.",
|
|
4081
|
+
downloads: 0,
|
|
4082
|
+
likes: 0,
|
|
4083
|
+
tags: ["local-llama"],
|
|
4084
|
+
compatibility: {
|
|
4085
|
+
transformersJs: false,
|
|
4086
|
+
onnx: false,
|
|
4087
|
+
localRuntimeVerified: true
|
|
4088
|
+
},
|
|
4089
|
+
size: {
|
|
4090
|
+
estimatedTotalBytes: state.plan?.estimatedTotalBytes,
|
|
4091
|
+
primaryBytes: state.plan?.estimatedTotalBytes
|
|
4092
|
+
},
|
|
4093
|
+
downloadGroups: state.plan?.groups,
|
|
4094
|
+
languageMatch: {
|
|
4095
|
+
sourceMatched: false,
|
|
4096
|
+
targetMatched: false,
|
|
4097
|
+
directionalScore: 0
|
|
4098
|
+
}
|
|
4099
|
+
}, asset);
|
|
4100
|
+
}));
|
|
4101
|
+
items.sort(compareCatalogItems$1);
|
|
4102
|
+
return { items };
|
|
4103
|
+
}
|
|
4104
|
+
async searchRemoteCatalog(input) {
|
|
4105
|
+
const [remote, localMap, selectedModel] = await Promise.all([
|
|
4106
|
+
this.searchRemote(input),
|
|
4107
|
+
this.store.readMap(),
|
|
4108
|
+
this.readSelectedModel()
|
|
4109
|
+
]);
|
|
4110
|
+
const items = await this.decorateCatalogItems(remote.items, localMap, selectedModel);
|
|
4111
|
+
items.sort(compareCatalogItems$1);
|
|
4112
|
+
return {
|
|
4113
|
+
items,
|
|
4114
|
+
nextCursor: remote.nextCursor
|
|
4115
|
+
};
|
|
4116
|
+
}
|
|
4117
|
+
subscribeRemoteCatalog(input) {
|
|
4118
|
+
return observable((emit) => {
|
|
4119
|
+
let active = true;
|
|
4120
|
+
(async () => {
|
|
4121
|
+
try {
|
|
4122
|
+
const events = await searchLlamaModelsProgressively({
|
|
4123
|
+
query: input.query,
|
|
4124
|
+
sourceLanguage: input.sourceLanguage,
|
|
4125
|
+
targetLanguage: input.targetLanguage,
|
|
4126
|
+
limit: input.limit,
|
|
4127
|
+
cursor: input.cursor,
|
|
4128
|
+
requestId: input.requestId
|
|
4129
|
+
}, {
|
|
4130
|
+
fetchCacheStore: this.fetchCacheStore,
|
|
4131
|
+
hfEndpoint: await this.readHuggingFaceEndpoint()
|
|
4132
|
+
});
|
|
4133
|
+
for (const event of events) {
|
|
4134
|
+
if (!active) return;
|
|
4135
|
+
const localMap = await this.store.readMap();
|
|
4136
|
+
const selectedModel = await this.readSelectedModel();
|
|
4137
|
+
const items = event.items ? await this.decorateCatalogItems(event.items, localMap, selectedModel, { includeLocalOnly: false }) : void 0;
|
|
4138
|
+
emit.next({
|
|
4139
|
+
requestId: event.requestId,
|
|
4140
|
+
phase: event.phase,
|
|
4141
|
+
items,
|
|
4142
|
+
nextCursor: event.nextCursor,
|
|
4143
|
+
message: event.message
|
|
4144
|
+
});
|
|
4145
|
+
}
|
|
4146
|
+
} catch (error) {
|
|
4147
|
+
if (!active) return;
|
|
4148
|
+
emit.next({
|
|
4149
|
+
requestId: input.requestId,
|
|
4150
|
+
phase: "error",
|
|
4151
|
+
message: error instanceof Error ? error.message : "Unable to search remote llama models."
|
|
4152
|
+
});
|
|
4153
|
+
}
|
|
4154
|
+
})();
|
|
4155
|
+
return () => {
|
|
4156
|
+
active = false;
|
|
4157
|
+
};
|
|
4158
|
+
});
|
|
4159
|
+
}
|
|
4160
|
+
async readSelectedModelState(modelId, selectedGroupId) {
|
|
4161
|
+
const state = (await this.store.readMap()).get(modelId);
|
|
4162
|
+
if (state) return this.refreshCachedState(state, selectedGroupId);
|
|
4163
|
+
const selected = modelId === await this.readSelectedModel();
|
|
4164
|
+
return this.refreshCachedState(LocalModelAssetStateSchema.parse({
|
|
4165
|
+
modelId,
|
|
4166
|
+
status: "not-downloaded",
|
|
4167
|
+
selected,
|
|
4168
|
+
selectedGroupId,
|
|
4169
|
+
updatedAt: this.now()
|
|
4170
|
+
}), selectedGroupId);
|
|
4171
|
+
}
|
|
4172
|
+
async startDownload(modelId, groupId) {
|
|
4173
|
+
return this.runDownload(modelId, "Downloading llama GGUF model", groupId);
|
|
4174
|
+
}
|
|
4175
|
+
async resumeDownload(modelId, groupId) {
|
|
4176
|
+
return this.runDownload(modelId, "Resuming llama GGUF model download", groupId);
|
|
4177
|
+
}
|
|
4178
|
+
async pauseDownload(modelId, groupId) {
|
|
4179
|
+
const requestedGroupId = groupId ?? await this.readSelectedGroupId();
|
|
4180
|
+
if (!requestedGroupId) return { success: true };
|
|
4181
|
+
const current = await this.readSelectedModelState(modelId, requestedGroupId);
|
|
4182
|
+
const effectiveGroupId = current.plan?.selectedGroupId ?? current.selectedGroupId ?? requestedGroupId;
|
|
4183
|
+
const sessionKey = buildSessionKey$1(modelId, effectiveGroupId);
|
|
4184
|
+
const session = this.sessions.get(sessionKey);
|
|
4185
|
+
if (session) {
|
|
4186
|
+
session.abortController.abort();
|
|
4187
|
+
this.sessions.delete(sessionKey);
|
|
4188
|
+
}
|
|
4189
|
+
const nextState = LocalModelAssetStateSchema.parse({
|
|
4190
|
+
...current,
|
|
4191
|
+
groupsState: {
|
|
4192
|
+
...current.groupsState,
|
|
4193
|
+
[effectiveGroupId]: LocalModelLifecycleGroupStateSchema.parse({
|
|
4194
|
+
...current.groupsState[effectiveGroupId],
|
|
4195
|
+
groupId: effectiveGroupId,
|
|
4196
|
+
status: "paused",
|
|
4197
|
+
resumable: true,
|
|
4198
|
+
updatedAt: this.now()
|
|
4199
|
+
})
|
|
4200
|
+
},
|
|
4201
|
+
updatedAt: this.now()
|
|
4202
|
+
});
|
|
4203
|
+
const projected = await this.refreshCachedState(nextState, effectiveGroupId, { revalidateDisk: true });
|
|
4204
|
+
await this.store.upsert(projected);
|
|
4205
|
+
this.emitLog({
|
|
4206
|
+
engineId: "local-llama",
|
|
4207
|
+
modelId,
|
|
4208
|
+
selectedGroupId: effectiveGroupId,
|
|
4209
|
+
groupId: effectiveGroupId,
|
|
4210
|
+
status: "paused",
|
|
4211
|
+
message: "Llama GGUF download paused.",
|
|
4212
|
+
progress: projected.progress,
|
|
4213
|
+
bytesDownloaded: projected.bytesDownloaded,
|
|
4214
|
+
totalBytes: projected.totalBytes,
|
|
4215
|
+
resumable: true,
|
|
4216
|
+
files: projected.files,
|
|
4217
|
+
updatedAt: this.now()
|
|
4218
|
+
});
|
|
4219
|
+
return { success: true };
|
|
4220
|
+
}
|
|
4221
|
+
async deleteModel(modelId, groupId) {
|
|
4222
|
+
const requestedGroupId = groupId ?? await this.readSelectedGroupId();
|
|
4223
|
+
if (!requestedGroupId) {
|
|
4224
|
+
await this.store.remove(modelId);
|
|
4225
|
+
await this.profileManifestStore.remove(modelId);
|
|
4226
|
+
return { success: true };
|
|
4227
|
+
}
|
|
4228
|
+
const current = await this.readSelectedModelState(modelId, requestedGroupId);
|
|
4229
|
+
const effectiveGroupId = current.plan?.selectedGroupId ?? current.selectedGroupId ?? requestedGroupId;
|
|
4230
|
+
const sessionKey = buildSessionKey$1(modelId, effectiveGroupId);
|
|
4231
|
+
this.sessions.get(sessionKey)?.abortController.abort();
|
|
4232
|
+
this.sessions.delete(sessionKey);
|
|
4233
|
+
await this.store.upsert(LocalModelAssetStateSchema.parse({
|
|
4234
|
+
...current,
|
|
4235
|
+
groupsState: {
|
|
4236
|
+
...current.groupsState,
|
|
4237
|
+
[effectiveGroupId]: LocalModelLifecycleGroupStateSchema.parse({
|
|
4238
|
+
...current.groupsState[effectiveGroupId],
|
|
4239
|
+
groupId: effectiveGroupId,
|
|
4240
|
+
status: "deleting",
|
|
4241
|
+
updatedAt: this.now()
|
|
4242
|
+
})
|
|
4243
|
+
},
|
|
4244
|
+
updatedAt: this.now()
|
|
4245
|
+
}));
|
|
4246
|
+
this.emitLog({
|
|
4247
|
+
engineId: "local-llama",
|
|
4248
|
+
modelId,
|
|
4249
|
+
selectedGroupId: effectiveGroupId,
|
|
4250
|
+
groupId: effectiveGroupId,
|
|
4251
|
+
status: "deleting",
|
|
4252
|
+
message: "Deleting llama GGUF files.",
|
|
4253
|
+
files: current.files,
|
|
4254
|
+
updatedAt: this.now()
|
|
4255
|
+
});
|
|
4256
|
+
await rm(getLocalLlamaModelArtifactGroupRoot(this.cacheDir, modelId, effectiveGroupId), {
|
|
4257
|
+
recursive: true,
|
|
4258
|
+
force: true
|
|
4259
|
+
});
|
|
4260
|
+
const persistedManifest = await this.profileManifestStore.read(modelId);
|
|
4261
|
+
const nextGroupsState = { ...current.groupsState };
|
|
4262
|
+
delete nextGroupsState[effectiveGroupId];
|
|
4263
|
+
const nextManifest = persistedManifest ? removeManifestGroup$1(persistedManifest, effectiveGroupId) : removeManifestGroup$1(current.profileManifest, effectiveGroupId);
|
|
4264
|
+
const nextPlan = removePlanGroup$1(current.plan, effectiveGroupId);
|
|
4265
|
+
const nextSelectedGroupId = current.selectedGroupId === effectiveGroupId ? void 0 : current.selectedGroupId;
|
|
4266
|
+
const nextState = await this.refreshCachedState(LocalModelAssetStateSchema.parse({
|
|
4267
|
+
...current,
|
|
4268
|
+
selectedGroupId: nextSelectedGroupId,
|
|
4269
|
+
profileManifest: nextManifest,
|
|
4270
|
+
groupsState: nextGroupsState,
|
|
4271
|
+
plan: nextPlan,
|
|
4272
|
+
updatedAt: this.now()
|
|
4273
|
+
}), nextSelectedGroupId, { revalidateDisk: true });
|
|
4274
|
+
if (nextState.profileManifest) await this.profileManifestStore.upsert(nextState.profileManifest);
|
|
4275
|
+
else await this.profileManifestStore.remove(modelId);
|
|
4276
|
+
if (nextState.profileManifest || nextState.plan?.groups?.length) await this.store.upsert(nextState);
|
|
4277
|
+
else await this.store.remove(modelId);
|
|
4278
|
+
this.emitLog({
|
|
4279
|
+
engineId: "local-llama",
|
|
4280
|
+
modelId,
|
|
4281
|
+
selectedGroupId: effectiveGroupId,
|
|
4282
|
+
groupId: effectiveGroupId,
|
|
4283
|
+
status: "not-downloaded",
|
|
4284
|
+
message: "Llama GGUF files were removed.",
|
|
4285
|
+
progress: 0,
|
|
4286
|
+
bytesDownloaded: 0,
|
|
4287
|
+
totalBytes: 0,
|
|
4288
|
+
files: [],
|
|
4289
|
+
updatedAt: this.now()
|
|
4290
|
+
});
|
|
4291
|
+
return { success: true };
|
|
4292
|
+
}
|
|
4293
|
+
async refreshArtifacts(modelId) {
|
|
4294
|
+
const targetModelId = modelId ?? await this.readSelectedModel();
|
|
4295
|
+
const loadingState = LocalModelAssetStateSchema.parse({
|
|
4296
|
+
...await this.readSelectedModelState(targetModelId),
|
|
4297
|
+
profileLoad: {
|
|
4298
|
+
status: "loading",
|
|
4299
|
+
message: "Loading llama GGUF artifacts.",
|
|
4300
|
+
updatedAt: this.now()
|
|
4301
|
+
},
|
|
4302
|
+
updatedAt: this.now()
|
|
4303
|
+
});
|
|
4304
|
+
await this.store.upsert(loadingState);
|
|
4305
|
+
try {
|
|
4306
|
+
const manifest = await this.createProfileManifest(targetModelId);
|
|
4307
|
+
await this.profileManifestStore.upsert(manifest);
|
|
4308
|
+
const current = await this.readSelectedModelState(targetModelId);
|
|
4309
|
+
const nextState = await this.refreshCachedState(LocalModelAssetStateSchema.parse({
|
|
4310
|
+
...current,
|
|
4311
|
+
profileManifest: manifest,
|
|
4312
|
+
profileLoad: {
|
|
4313
|
+
status: "ready",
|
|
4314
|
+
message: "Llama GGUF artifacts are ready.",
|
|
4315
|
+
updatedAt: this.now()
|
|
4316
|
+
},
|
|
4317
|
+
updatedAt: this.now()
|
|
4318
|
+
}), void 0, { revalidateDisk: true });
|
|
4319
|
+
await this.store.upsert(nextState);
|
|
4320
|
+
return nextState;
|
|
4321
|
+
} catch (error) {
|
|
4322
|
+
const message = error instanceof Error ? error.message : "Unable to load llama GGUF artifacts.";
|
|
4323
|
+
const failedState = LocalModelAssetStateSchema.parse({
|
|
4324
|
+
...await this.readSelectedModelState(targetModelId),
|
|
4325
|
+
profileLoad: {
|
|
4326
|
+
status: "error",
|
|
4327
|
+
error: message,
|
|
4328
|
+
updatedAt: this.now()
|
|
4329
|
+
},
|
|
4330
|
+
updatedAt: this.now()
|
|
4331
|
+
});
|
|
4332
|
+
await this.store.upsert(failedState);
|
|
4333
|
+
throw error;
|
|
4334
|
+
}
|
|
4335
|
+
}
|
|
4336
|
+
async markSelectedModel(modelId) {
|
|
4337
|
+
const nextStates = (await this.store.readAll()).map((state) => LocalModelAssetStateSchema.parse({
|
|
4338
|
+
...state,
|
|
4339
|
+
selected: state.modelId === modelId
|
|
4340
|
+
}));
|
|
4341
|
+
if (!nextStates.some((state) => state.modelId === modelId)) nextStates.push(LocalModelAssetStateSchema.parse({
|
|
4342
|
+
modelId,
|
|
4343
|
+
status: "not-downloaded",
|
|
4344
|
+
selected: true,
|
|
4345
|
+
updatedAt: this.now()
|
|
4346
|
+
}));
|
|
4347
|
+
await this.store.writeAll(nextStates);
|
|
4348
|
+
try {
|
|
4349
|
+
return await this.refreshArtifacts(modelId);
|
|
4350
|
+
} catch {
|
|
4351
|
+
return this.readSelectedModelState(modelId);
|
|
4352
|
+
}
|
|
4353
|
+
}
|
|
4354
|
+
async waitForModelTask(modelId) {
|
|
4355
|
+
await Promise.all([...this.sessionTasks.entries()].filter(([sessionKey]) => sessionKey.startsWith(`${modelId}:`)).map(([, task]) => task));
|
|
4356
|
+
}
|
|
4357
|
+
async close() {
|
|
4358
|
+
for (const session of this.sessions.values()) session.abortController.abort();
|
|
4359
|
+
await Promise.allSettled(this.sessionTasks.values());
|
|
4360
|
+
}
|
|
4361
|
+
async searchRemote(input) {
|
|
4362
|
+
return searchLlamaModels({
|
|
4363
|
+
query: input.query,
|
|
4364
|
+
sourceLanguage: input.sourceLanguage,
|
|
4365
|
+
targetLanguage: input.targetLanguage,
|
|
4366
|
+
limit: input.limit,
|
|
4367
|
+
cursor: input.cursor
|
|
4368
|
+
}, {
|
|
4369
|
+
fetchCacheStore: this.fetchCacheStore,
|
|
4370
|
+
hfEndpoint: await this.readHuggingFaceEndpoint()
|
|
4371
|
+
});
|
|
4372
|
+
}
|
|
4373
|
+
async decorateCatalogItems(candidates, localMap, selectedModel, options = {}) {
|
|
4374
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4375
|
+
const remoteItems = await Promise.all(candidates.map(async (candidate) => {
|
|
4376
|
+
seen.add(candidate.id);
|
|
4377
|
+
const localState = localMap.get(candidate.id);
|
|
4378
|
+
return toCatalogItem$1(candidate, localState ? await this.refreshCachedState(localState) : LocalModelAssetStateSchema.parse({
|
|
4379
|
+
modelId: candidate.id,
|
|
4380
|
+
status: "not-downloaded",
|
|
4381
|
+
selected: candidate.id === selectedModel,
|
|
4382
|
+
updatedAt: this.now()
|
|
4383
|
+
}));
|
|
4384
|
+
}));
|
|
4385
|
+
const localOnlyItems = options.includeLocalOnly === false ? [] : await Promise.all([...localMap.values()].filter((state) => !seen.has(state.modelId)).map(async (state) => {
|
|
4386
|
+
const asset = await this.refreshCachedState(state);
|
|
4387
|
+
return toCatalogItem$1({
|
|
4388
|
+
id: state.modelId,
|
|
4389
|
+
label: state.modelId,
|
|
4390
|
+
summary: state.plan?.estimatedTotalBytes !== void 0 ? `Previously selected llama GGUF model. Estimated download ${formatBytes$2(state.plan.estimatedTotalBytes)}.` : "Previously selected llama GGUF model.",
|
|
4391
|
+
downloads: 0,
|
|
4392
|
+
likes: 0,
|
|
4393
|
+
tags: ["local-llama"],
|
|
4394
|
+
compatibility: {
|
|
4395
|
+
transformersJs: false,
|
|
4396
|
+
onnx: false,
|
|
4397
|
+
localRuntimeVerified: true
|
|
4398
|
+
},
|
|
4399
|
+
size: {
|
|
4400
|
+
estimatedTotalBytes: state.plan?.estimatedTotalBytes,
|
|
4401
|
+
primaryBytes: state.plan?.estimatedTotalBytes
|
|
4402
|
+
},
|
|
4403
|
+
downloadGroups: state.plan?.groups,
|
|
4404
|
+
languageMatch: {
|
|
4405
|
+
sourceMatched: false,
|
|
4406
|
+
targetMatched: false,
|
|
4407
|
+
directionalScore: 0
|
|
4408
|
+
}
|
|
4409
|
+
}, asset);
|
|
4410
|
+
}));
|
|
4411
|
+
return [...remoteItems, ...localOnlyItems];
|
|
4412
|
+
}
|
|
4413
|
+
async refreshCachedState(state, selectedGroupId, options = {}) {
|
|
4414
|
+
const [selectedModel, persistedSelectedGroupId, persistedManifest] = await Promise.all([
|
|
4415
|
+
this.readSelectedModel(),
|
|
4416
|
+
this.readSelectedGroupId(),
|
|
4417
|
+
this.profileManifestStore.read(state.modelId)
|
|
4418
|
+
]);
|
|
4419
|
+
const selected = state.selected || state.modelId === selectedModel;
|
|
4420
|
+
const manifest = state.profileManifest ?? persistedManifest ?? createSyntheticManifestFromPlan({
|
|
4421
|
+
cacheDir: this.cacheDir,
|
|
4422
|
+
modelId: state.modelId,
|
|
4423
|
+
plan: state.plan
|
|
4424
|
+
});
|
|
4425
|
+
const selectedGroupIdForProjection = resolveManifestGroupId$1(manifest, selectedGroupId ?? persistedSelectedGroupId ?? state.selectedGroupId ?? state.plan?.selectedGroupId) ?? selectFirstManifestGroupId$1(manifest) ?? state.plan?.selectedGroupId;
|
|
4426
|
+
const groupsState = manifest ? options.revalidateDisk ? await this.reconcileGroupsFromDisk({
|
|
4427
|
+
manifest,
|
|
4428
|
+
groupsState: state.groupsState
|
|
4429
|
+
}) : this.reconcileGroupsFromSnapshot({
|
|
4430
|
+
manifest,
|
|
4431
|
+
groupsState: state.groupsState
|
|
4432
|
+
}) : state.groupsState;
|
|
4433
|
+
const plan = manifest ? buildPlanFromManifest$1({
|
|
4434
|
+
modelId: state.modelId,
|
|
4435
|
+
manifest,
|
|
4436
|
+
groupsState,
|
|
4437
|
+
selectedGroupId: selectedGroupIdForProjection
|
|
4438
|
+
}) : state.plan ?? void 0;
|
|
4439
|
+
const selectedPlanGroup = selectPlanGroup(plan, selectedGroupIdForProjection);
|
|
4440
|
+
const selectedGroupState = selectedPlanGroup && groupsState[selectedPlanGroup.id] ? groupsState[selectedPlanGroup.id] : void 0;
|
|
4441
|
+
return LocalModelAssetStateSchema.parse({
|
|
4442
|
+
...state,
|
|
4443
|
+
selected,
|
|
4444
|
+
selectedGroupId: selectedGroupIdForProjection,
|
|
4445
|
+
profileManifest: manifest,
|
|
4446
|
+
groupsState,
|
|
4447
|
+
plan,
|
|
4448
|
+
status: selectedGroupState?.status ?? state.status,
|
|
4449
|
+
progress: selectedGroupState?.progress,
|
|
4450
|
+
totalBytes: selectedGroupState?.totalBytes ?? selectedPlanGroup?.estimatedTotalBytes,
|
|
4451
|
+
bytesDownloaded: selectedGroupState?.bytesDownloaded,
|
|
4452
|
+
error: selectedGroupState?.error,
|
|
4453
|
+
resumable: selectedGroupState?.resumable ?? false,
|
|
4454
|
+
files: selectedPlanGroup?.files.map((file) => ({
|
|
4455
|
+
path: file.path,
|
|
4456
|
+
sizeBytes: file.sizeBytes,
|
|
4457
|
+
downloadedBytes: selectedGroupState?.files.find((entry) => entry.path === file.path)?.downloadedBytes
|
|
4458
|
+
})) ?? state.files,
|
|
4459
|
+
updatedAt: this.now(),
|
|
4460
|
+
installedAt: selectedGroupState?.installedAt ?? state.installedAt
|
|
4461
|
+
});
|
|
4462
|
+
}
|
|
4463
|
+
reconcileGroupsFromSnapshot(input) {
|
|
4464
|
+
const nextGroupsState = { ...input.groupsState };
|
|
4465
|
+
for (const groupId of input.manifest.groupOrder) {
|
|
4466
|
+
const manifestGroup = input.manifest.groups[groupId];
|
|
4467
|
+
if (!manifestGroup) continue;
|
|
4468
|
+
const current = nextGroupsState[groupId];
|
|
4469
|
+
const files = reconcileGroupFilesFromSnapshot$1({
|
|
4470
|
+
manifestGroup,
|
|
4471
|
+
currentFiles: current?.files ?? [],
|
|
4472
|
+
currentStatus: current?.status ?? "not-downloaded"
|
|
4473
|
+
});
|
|
4474
|
+
const bytesDownloaded = sumDownloadedBytes$1(files);
|
|
4475
|
+
const totalBytes = manifestGroup.estimatedTotalBytes;
|
|
4476
|
+
const status = current?.status ?? "not-downloaded";
|
|
4477
|
+
nextGroupsState[groupId] = LocalModelLifecycleGroupStateSchema.parse({
|
|
4478
|
+
...current,
|
|
4479
|
+
groupId,
|
|
4480
|
+
baseGroupId: manifestGroup.baseGroupId,
|
|
4481
|
+
status,
|
|
4482
|
+
rootDir: manifestGroup.rootDir,
|
|
4483
|
+
bytesDownloaded,
|
|
4484
|
+
totalBytes,
|
|
4485
|
+
progress: totalBytes && totalBytes > 0 ? Math.max(0, Math.min(1, bytesDownloaded / totalBytes)) : current?.progress,
|
|
4486
|
+
resumable: current?.resumable ?? (status === "paused" || status === "error" || status === "downloading"),
|
|
4487
|
+
error: current?.error,
|
|
4488
|
+
installedAt: current?.installedAt,
|
|
4489
|
+
updatedAt: current?.updatedAt ?? this.now(),
|
|
4490
|
+
files
|
|
4491
|
+
});
|
|
4492
|
+
}
|
|
4493
|
+
return nextGroupsState;
|
|
4494
|
+
}
|
|
4495
|
+
async reconcileGroupsFromDisk(input) {
|
|
4496
|
+
const nextGroupsState = { ...input.groupsState };
|
|
4497
|
+
for (const groupId of input.manifest.groupOrder) {
|
|
4498
|
+
const manifestGroup = input.manifest.groups[groupId];
|
|
4499
|
+
if (!manifestGroup) continue;
|
|
4500
|
+
const current = nextGroupsState[groupId];
|
|
4501
|
+
if (isActiveDownloadStatus$1(current?.status ?? "not-downloaded")) {
|
|
4502
|
+
nextGroupsState[groupId] = LocalModelLifecycleGroupStateSchema.parse({
|
|
4503
|
+
...current,
|
|
4504
|
+
groupId,
|
|
4505
|
+
baseGroupId: manifestGroup.baseGroupId,
|
|
4506
|
+
rootDir: manifestGroup.rootDir,
|
|
4507
|
+
totalBytes: manifestGroup.estimatedTotalBytes,
|
|
4508
|
+
files: reconcileGroupFiles$1({
|
|
4509
|
+
manifestGroup,
|
|
4510
|
+
currentFiles: current?.files ?? []
|
|
4511
|
+
})
|
|
4512
|
+
});
|
|
4513
|
+
continue;
|
|
4514
|
+
}
|
|
4515
|
+
const files = await reconcileGroupFilesFromDisk$1({
|
|
4516
|
+
rootDir: manifestGroup.rootDir,
|
|
4517
|
+
manifestGroup,
|
|
4518
|
+
currentFiles: current?.files ?? []
|
|
4519
|
+
});
|
|
4520
|
+
const bytesDownloaded = sumDownloadedBytes$1(files);
|
|
4521
|
+
const totalBytes = manifestGroup.estimatedTotalBytes;
|
|
4522
|
+
const allComplete = files.length > 0 && files.every((file) => file.sizeBytes !== void 0 && (file.downloadedBytes ?? 0) >= file.sizeBytes && file.status === "downloaded");
|
|
4523
|
+
const hasPartial = files.some((file) => (file.downloadedBytes ?? 0) > 0);
|
|
4524
|
+
const status = allComplete ? "downloaded" : current?.status === "error" ? "error" : current?.status === "paused" ? "paused" : hasPartial ? "paused" : "not-downloaded";
|
|
4525
|
+
nextGroupsState[groupId] = LocalModelLifecycleGroupStateSchema.parse({
|
|
4526
|
+
...current,
|
|
4527
|
+
groupId,
|
|
4528
|
+
baseGroupId: manifestGroup.baseGroupId,
|
|
4529
|
+
status,
|
|
4530
|
+
rootDir: manifestGroup.rootDir,
|
|
4531
|
+
bytesDownloaded,
|
|
4532
|
+
totalBytes,
|
|
4533
|
+
progress: totalBytes && totalBytes > 0 ? Math.max(0, Math.min(1, bytesDownloaded / totalBytes)) : void 0,
|
|
4534
|
+
resumable: status === "paused" || status === "error",
|
|
4535
|
+
error: status === "error" ? current?.error : void 0,
|
|
4536
|
+
installedAt: status === "downloaded" ? current?.installedAt ?? this.now() : current?.installedAt,
|
|
4537
|
+
updatedAt: this.now(),
|
|
4538
|
+
files
|
|
4539
|
+
});
|
|
4540
|
+
}
|
|
4541
|
+
return nextGroupsState;
|
|
4542
|
+
}
|
|
4543
|
+
async runDownload(modelId, messagePrefix, groupId) {
|
|
4544
|
+
const effectiveGroupId = groupId ?? await this.readSelectedGroupId();
|
|
4545
|
+
if (!effectiveGroupId) throw new Error("No llama GGUF artifact group is selected.");
|
|
4546
|
+
const manifest = await this.ensureProfileManifest(modelId);
|
|
4547
|
+
const resolvedGroupId = resolveManifestGroupId$1(manifest, effectiveGroupId);
|
|
4548
|
+
if (!resolvedGroupId) throw new Error("No concrete llama GGUF download plan is available.");
|
|
4549
|
+
const sessionKey = buildSessionKey$1(modelId, resolvedGroupId);
|
|
4550
|
+
const existing = this.sessions.get(sessionKey);
|
|
4551
|
+
if (existing) return { sessionId: existing.sessionId };
|
|
4552
|
+
const sessionId = `local-llama-model-${sanitizeId$1(modelId)}-${sanitizeId$1(resolvedGroupId)}-${this.now()}`;
|
|
4553
|
+
const abortController = new AbortController();
|
|
4554
|
+
this.sessions.set(sessionKey, {
|
|
4555
|
+
modelId,
|
|
4556
|
+
sessionId,
|
|
4557
|
+
abortController,
|
|
4558
|
+
groupId: resolvedGroupId
|
|
4559
|
+
});
|
|
4560
|
+
const current = await this.readSelectedModelState(modelId, resolvedGroupId);
|
|
4561
|
+
const manifestGroup = manifest.groups[resolvedGroupId];
|
|
4562
|
+
if (!manifestGroup || manifestGroup.files.length === 0 || manifestGroup.estimatedTotalBytes === void 0) {
|
|
4563
|
+
this.sessions.delete(sessionKey);
|
|
4564
|
+
throw new Error("No concrete llama GGUF download plan is available.");
|
|
4565
|
+
}
|
|
4566
|
+
const totalBytes = manifestGroup.estimatedTotalBytes;
|
|
4567
|
+
const currentGroup = current.groupsState[resolvedGroupId];
|
|
4568
|
+
const resumedFiles = await reconcileGroupFilesFromDisk$1({
|
|
4569
|
+
rootDir: manifestGroup.rootDir,
|
|
4570
|
+
manifestGroup,
|
|
4571
|
+
currentFiles: currentGroup?.files ?? []
|
|
4572
|
+
});
|
|
4573
|
+
const resumedBytesDownloaded = sumDownloadedBytes$1(resumedFiles);
|
|
4574
|
+
const nextState = LocalModelAssetStateSchema.parse({
|
|
4575
|
+
...current,
|
|
4576
|
+
modelId,
|
|
4577
|
+
selected: true,
|
|
4578
|
+
profileManifest: manifest,
|
|
4579
|
+
groupsState: {
|
|
4580
|
+
...current.groupsState,
|
|
4581
|
+
[resolvedGroupId]: LocalModelLifecycleGroupStateSchema.parse({
|
|
4582
|
+
...currentGroup,
|
|
4583
|
+
groupId: resolvedGroupId,
|
|
4584
|
+
baseGroupId: manifestGroup.baseGroupId,
|
|
4585
|
+
status: "downloading",
|
|
4586
|
+
rootDir: manifestGroup.rootDir,
|
|
4587
|
+
bytesDownloaded: resumedBytesDownloaded,
|
|
4588
|
+
progress: totalBytes > 0 ? resumedBytesDownloaded / totalBytes : currentGroup?.progress,
|
|
4589
|
+
totalBytes,
|
|
4590
|
+
resumable: true,
|
|
4591
|
+
files: resumedFiles,
|
|
4592
|
+
updatedAt: this.now()
|
|
4593
|
+
})
|
|
4594
|
+
},
|
|
4595
|
+
updatedAt: this.now()
|
|
4596
|
+
});
|
|
4597
|
+
const projected = await this.refreshCachedState(nextState, resolvedGroupId, { revalidateDisk: true });
|
|
4598
|
+
await this.store.upsert(projected);
|
|
4599
|
+
this.emitLog({
|
|
4600
|
+
engineId: "local-llama",
|
|
4601
|
+
modelId,
|
|
4602
|
+
selectedGroupId: resolvedGroupId,
|
|
4603
|
+
groupId: resolvedGroupId,
|
|
4604
|
+
status: "downloading",
|
|
4605
|
+
message: `${messagePrefix} ${modelId}.`,
|
|
4606
|
+
progress: projected.progress,
|
|
4607
|
+
bytesDownloaded: projected.bytesDownloaded,
|
|
4608
|
+
totalBytes,
|
|
4609
|
+
sessionId,
|
|
4610
|
+
resumable: true,
|
|
4611
|
+
files: projected.files,
|
|
4612
|
+
updatedAt: this.now()
|
|
4613
|
+
});
|
|
4614
|
+
const task = this.performDownload(modelId, resolvedGroupId, sessionId, abortController.signal).catch((error) => this.finishDownload(modelId, resolvedGroupId, sessionId, false, error instanceof Error ? error.message : String(error))).finally(() => {
|
|
4615
|
+
if (this.sessionTasks.get(sessionKey) === task) this.sessionTasks.delete(sessionKey);
|
|
4616
|
+
});
|
|
4617
|
+
this.sessionTasks.set(sessionKey, task);
|
|
4618
|
+
return { sessionId };
|
|
4619
|
+
}
|
|
4620
|
+
async ensureProfileManifest(modelId) {
|
|
4621
|
+
const persistedState = (await this.store.readMap()).get(modelId);
|
|
4622
|
+
if (persistedState?.profileManifest) return persistedState.profileManifest;
|
|
4623
|
+
const existing = await this.profileManifestStore.read(modelId);
|
|
4624
|
+
if (existing) return existing;
|
|
4625
|
+
const manifest = await this.createProfileManifest(modelId);
|
|
4626
|
+
await this.profileManifestStore.upsert(manifest);
|
|
4627
|
+
return manifest;
|
|
4628
|
+
}
|
|
4629
|
+
async createProfileManifest(modelId) {
|
|
4630
|
+
const hfEndpoint = await this.readHuggingFaceEndpoint();
|
|
4631
|
+
const snapshot = await readLocalModelRepositorySnapshot({
|
|
4632
|
+
modelId,
|
|
4633
|
+
hfEndpoint,
|
|
4634
|
+
fetchCacheStore: this.fetchCacheStore
|
|
4635
|
+
});
|
|
4636
|
+
const basePlan = resolveGgufModelDownloadPlanFromRepositoryFiles({
|
|
4637
|
+
modelId,
|
|
4638
|
+
files: snapshot.files.map((file) => ({
|
|
4639
|
+
...file,
|
|
4640
|
+
revision: snapshot.commitHash
|
|
4641
|
+
}))
|
|
4642
|
+
});
|
|
4643
|
+
if (!basePlan?.groups?.length) throw new Error(`No recognizable GGUF artifacts were found for ${modelId}.`);
|
|
4644
|
+
const groupsEntries = basePlan.groups.flatMap((group) => {
|
|
4645
|
+
if (!group.selectable || group.estimatedTotalBytes === void 0) return [];
|
|
4646
|
+
const groupId = buildVersionedGroupId$1(group.id, snapshot.shortCommitHash);
|
|
4647
|
+
const rootDir = getLocalLlamaModelArtifactGroupRoot(this.cacheDir, modelId, groupId);
|
|
4648
|
+
return [[groupId, {
|
|
4649
|
+
id: groupId,
|
|
4650
|
+
baseGroupId: group.id,
|
|
4651
|
+
label: group.label,
|
|
4652
|
+
displayLabel: group.label,
|
|
4653
|
+
description: group.description,
|
|
4654
|
+
profile: group.profile,
|
|
4655
|
+
dtype: group.dtype,
|
|
4656
|
+
commitHash: snapshot.commitHash,
|
|
4657
|
+
shortCommitHash: snapshot.shortCommitHash,
|
|
4658
|
+
rootDir,
|
|
4659
|
+
estimatedTotalBytes: group.estimatedTotalBytes,
|
|
4660
|
+
selectable: group.selectable,
|
|
4661
|
+
files: group.files.map((file) => ({
|
|
4662
|
+
...file,
|
|
4663
|
+
revision: snapshot.commitHash,
|
|
4664
|
+
sourceUrl: file.sourceUrl ?? `${normalizeHuggingFaceEndpoint(hfEndpoint)}/${modelId}/resolve/${snapshot.commitHash}/${file.path}`
|
|
4665
|
+
}))
|
|
4666
|
+
}]];
|
|
4667
|
+
});
|
|
4668
|
+
if (groupsEntries.length === 0) throw new Error(`No selectable GGUF artifacts were found for ${modelId}.`);
|
|
4669
|
+
return LocalModelProfileManifestSchema.parse({
|
|
4670
|
+
modelId,
|
|
4671
|
+
source: "huggingface",
|
|
4672
|
+
endpoint: normalizeHuggingFaceEndpoint(hfEndpoint),
|
|
4673
|
+
revision: snapshot.revision,
|
|
4674
|
+
commitHash: snapshot.commitHash,
|
|
4675
|
+
shortCommitHash: snapshot.shortCommitHash,
|
|
4676
|
+
fetchedAt: this.now(),
|
|
4677
|
+
updatedAt: this.now(),
|
|
4678
|
+
raw: snapshot.raw,
|
|
4679
|
+
groups: Object.fromEntries(groupsEntries),
|
|
4680
|
+
groupOrder: groupsEntries.map(([groupId]) => groupId)
|
|
4681
|
+
});
|
|
4682
|
+
}
|
|
4683
|
+
async performDownload(modelId, groupId, sessionId, signal) {
|
|
4684
|
+
const manifest = await this.ensureProfileManifest(modelId);
|
|
4685
|
+
const manifestGroup = manifest.groups[groupId];
|
|
4686
|
+
if (!manifestGroup) throw new Error(`Unknown llama GGUF artifact group: ${groupId}.`);
|
|
4687
|
+
const files = manifestGroup.files;
|
|
4688
|
+
const totalBytes = manifestGroup.estimatedTotalBytes;
|
|
4689
|
+
const currentGroup = (await this.readSelectedModelState(modelId, groupId)).groupsState[groupId];
|
|
4690
|
+
const downloadedFiles = await reconcileGroupFilesFromDisk$1({
|
|
4691
|
+
rootDir: manifestGroup.rootDir,
|
|
4692
|
+
manifestGroup,
|
|
4693
|
+
currentFiles: currentGroup?.files ?? []
|
|
4694
|
+
});
|
|
4695
|
+
let bytesDownloaded = sumDownloadedBytes$1(downloadedFiles);
|
|
4696
|
+
if (files.length === 0 || totalBytes === void 0) throw new Error("No concrete llama GGUF download files were selected.");
|
|
4697
|
+
for (const [fileIndex, file] of files.entries()) {
|
|
4698
|
+
throwIfAborted$1(signal);
|
|
4699
|
+
const previousFileBytes = downloadedFiles[fileIndex]?.downloadedBytes ?? 0;
|
|
4700
|
+
if (file.sizeBytes !== void 0 && previousFileBytes >= file.sizeBytes) continue;
|
|
4701
|
+
downloadedFiles[fileIndex] = {
|
|
4702
|
+
path: file.path,
|
|
4703
|
+
sizeBytes: file.sizeBytes,
|
|
4704
|
+
downloadedBytes: previousFileBytes,
|
|
4705
|
+
required: file.required,
|
|
4706
|
+
status: previousFileBytes > 0 ? "paused" : "not-downloaded"
|
|
4707
|
+
};
|
|
4708
|
+
await this.emitDownloadProgress({
|
|
4709
|
+
modelId,
|
|
4710
|
+
groupId,
|
|
4711
|
+
sessionId,
|
|
4712
|
+
message: `Downloading ${file.path}.`,
|
|
4713
|
+
totalBytes,
|
|
4714
|
+
bytesDownloaded,
|
|
4715
|
+
files: downloadedFiles
|
|
4716
|
+
});
|
|
4717
|
+
await downloadUrlFileWithProgress({
|
|
4718
|
+
url: file.sourceUrl ?? `${manifest.endpoint}/${modelId}/resolve/${manifestGroup.commitHash}/${file.path}`,
|
|
4719
|
+
targetPath: join(manifestGroup.rootDir, file.path),
|
|
4720
|
+
expectedSizeBytes: file.sizeBytes,
|
|
4721
|
+
retryPolicy: this.networkRetryPolicy,
|
|
4722
|
+
signal,
|
|
4723
|
+
onProgress: async (fileBytesDownloaded) => {
|
|
4724
|
+
throwIfAborted$1(signal);
|
|
4725
|
+
const boundedFileBytes = file.sizeBytes ? Math.min(file.sizeBytes, fileBytesDownloaded) : fileBytesDownloaded;
|
|
4726
|
+
downloadedFiles[fileIndex] = {
|
|
4727
|
+
path: file.path,
|
|
4728
|
+
sizeBytes: file.sizeBytes,
|
|
4729
|
+
downloadedBytes: boundedFileBytes,
|
|
4730
|
+
required: file.required,
|
|
4731
|
+
status: boundedFileBytes >= (file.sizeBytes ?? Number.POSITIVE_INFINITY) ? "downloaded" : "downloading"
|
|
4732
|
+
};
|
|
4733
|
+
await this.emitDownloadProgress({
|
|
4734
|
+
modelId,
|
|
4735
|
+
groupId,
|
|
4736
|
+
sessionId,
|
|
4737
|
+
message: `Downloading ${file.path}.`,
|
|
4738
|
+
totalBytes,
|
|
4739
|
+
bytesDownloaded: bytesDownloaded - previousFileBytes + boundedFileBytes,
|
|
4740
|
+
files: downloadedFiles
|
|
4741
|
+
});
|
|
4742
|
+
},
|
|
4743
|
+
onRetry: async ({ retryDelayMs }) => {
|
|
4744
|
+
await this.emitDownloadProgress({
|
|
4745
|
+
modelId,
|
|
4746
|
+
groupId,
|
|
4747
|
+
sessionId,
|
|
4748
|
+
message: `Connection interrupted while downloading ${file.path}. Retrying automatically in ${formatDuration$1(retryDelayMs)}.`,
|
|
4749
|
+
totalBytes,
|
|
4750
|
+
bytesDownloaded: bytesDownloaded - previousFileBytes + (downloadedFiles[fileIndex]?.downloadedBytes ?? 0),
|
|
4751
|
+
files: downloadedFiles
|
|
4752
|
+
});
|
|
4753
|
+
}
|
|
4754
|
+
});
|
|
4755
|
+
throwIfAborted$1(signal);
|
|
4756
|
+
const nextDownloadedBytes = file.sizeBytes ?? 0;
|
|
4757
|
+
bytesDownloaded = bytesDownloaded - previousFileBytes + nextDownloadedBytes;
|
|
4758
|
+
downloadedFiles[fileIndex] = {
|
|
4759
|
+
path: file.path,
|
|
4760
|
+
sizeBytes: file.sizeBytes,
|
|
4761
|
+
downloadedBytes: file.sizeBytes,
|
|
4762
|
+
required: file.required,
|
|
4763
|
+
status: "downloaded"
|
|
4764
|
+
};
|
|
4765
|
+
await this.emitDownloadProgress({
|
|
4766
|
+
modelId,
|
|
4767
|
+
groupId,
|
|
4768
|
+
sessionId,
|
|
4769
|
+
message: `Downloaded ${file.path}.`,
|
|
4770
|
+
totalBytes,
|
|
4771
|
+
bytesDownloaded,
|
|
4772
|
+
files: downloadedFiles
|
|
4773
|
+
});
|
|
4774
|
+
}
|
|
4775
|
+
await this.finishDownload(modelId, groupId, sessionId, true, `Llama GGUF model ${modelId} is ready.`);
|
|
4776
|
+
}
|
|
4777
|
+
async emitDownloadProgress(input) {
|
|
4778
|
+
if (!this.isActiveSession(input.modelId, input.groupId, input.sessionId)) return;
|
|
4779
|
+
const progress = input.totalBytes && input.totalBytes > 0 ? Math.max(0, Math.min(1, input.bytesDownloaded / input.totalBytes)) : void 0;
|
|
4780
|
+
const current = await this.readSelectedModelState(input.modelId, input.groupId);
|
|
4781
|
+
const currentGroup = current.groupsState[input.groupId];
|
|
4782
|
+
const nextState = LocalModelAssetStateSchema.parse({
|
|
4783
|
+
...current,
|
|
4784
|
+
groupsState: {
|
|
4785
|
+
...current.groupsState,
|
|
4786
|
+
[input.groupId]: LocalModelLifecycleGroupStateSchema.parse({
|
|
4787
|
+
...currentGroup,
|
|
4788
|
+
groupId: input.groupId,
|
|
4789
|
+
status: "downloading",
|
|
4790
|
+
bytesDownloaded: input.bytesDownloaded,
|
|
4791
|
+
totalBytes: input.totalBytes,
|
|
4792
|
+
progress,
|
|
4793
|
+
resumable: true,
|
|
4794
|
+
files: input.files,
|
|
4795
|
+
updatedAt: this.now()
|
|
4796
|
+
})
|
|
4797
|
+
},
|
|
4798
|
+
updatedAt: this.now()
|
|
4799
|
+
});
|
|
4800
|
+
const projected = await this.refreshCachedState(nextState, input.groupId, { revalidateDisk: true });
|
|
4801
|
+
await this.store.upsert(projected);
|
|
4802
|
+
this.emitLog({
|
|
4803
|
+
engineId: "local-llama",
|
|
4804
|
+
modelId: input.modelId,
|
|
4805
|
+
selectedGroupId: input.groupId,
|
|
4806
|
+
groupId: input.groupId,
|
|
4807
|
+
status: "downloading",
|
|
4808
|
+
message: input.message,
|
|
4809
|
+
progress,
|
|
4810
|
+
bytesDownloaded: input.bytesDownloaded,
|
|
4811
|
+
totalBytes: input.totalBytes,
|
|
4812
|
+
files: input.files.map((file) => ({
|
|
4813
|
+
path: file.path,
|
|
4814
|
+
sizeBytes: file.sizeBytes,
|
|
4815
|
+
downloadedBytes: file.downloadedBytes
|
|
4816
|
+
})),
|
|
4817
|
+
sessionId: input.sessionId,
|
|
4818
|
+
resumable: true,
|
|
4819
|
+
updatedAt: this.now()
|
|
4820
|
+
});
|
|
4821
|
+
}
|
|
4822
|
+
async finishDownload(modelId, groupId, sessionId, success, message) {
|
|
4823
|
+
if (!this.isActiveSession(modelId, groupId, sessionId)) return;
|
|
4824
|
+
const sessionKey = buildSessionKey$1(modelId, groupId);
|
|
4825
|
+
const current = await this.readSelectedModelState(modelId, groupId);
|
|
4826
|
+
const currentGroup = current.groupsState[groupId];
|
|
4827
|
+
const totalBytes = currentGroup?.totalBytes ?? current.totalBytes;
|
|
4828
|
+
const files = success ? current.files.map((file) => LocalModelLifecycleFileStateSchema.parse({
|
|
4829
|
+
...file,
|
|
4830
|
+
required: true,
|
|
4831
|
+
downloadedBytes: file.sizeBytes,
|
|
4832
|
+
status: "downloaded",
|
|
4833
|
+
updatedAt: this.now()
|
|
4834
|
+
})) : (currentGroup?.files ?? []).map((file) => LocalModelLifecycleFileStateSchema.parse({
|
|
4835
|
+
...file,
|
|
4836
|
+
status: file.status === "downloaded" ? "downloaded" : "paused",
|
|
4837
|
+
updatedAt: this.now()
|
|
4838
|
+
}));
|
|
4839
|
+
const nextState = LocalModelAssetStateSchema.parse({
|
|
4840
|
+
...current,
|
|
4841
|
+
groupsState: {
|
|
4842
|
+
...current.groupsState,
|
|
4843
|
+
[groupId]: LocalModelLifecycleGroupStateSchema.parse({
|
|
4844
|
+
...currentGroup,
|
|
4845
|
+
groupId,
|
|
4846
|
+
status: success ? "downloaded" : "error",
|
|
4847
|
+
progress: success ? 1 : current.progress,
|
|
4848
|
+
bytesDownloaded: success ? totalBytes : current.bytesDownloaded,
|
|
4849
|
+
totalBytes,
|
|
4850
|
+
installedAt: success ? this.now() : currentGroup?.installedAt,
|
|
4851
|
+
updatedAt: this.now(),
|
|
4852
|
+
error: success ? void 0 : message,
|
|
4853
|
+
resumable: !success,
|
|
4854
|
+
files
|
|
4855
|
+
})
|
|
4856
|
+
},
|
|
4857
|
+
updatedAt: this.now()
|
|
4858
|
+
});
|
|
4859
|
+
const projected = await this.refreshCachedState(nextState, groupId, { revalidateDisk: true });
|
|
4860
|
+
await this.store.upsert(projected);
|
|
4861
|
+
this.sessions.delete(sessionKey);
|
|
4862
|
+
this.emitLog({
|
|
4863
|
+
engineId: "local-llama",
|
|
4864
|
+
modelId,
|
|
4865
|
+
selectedGroupId: groupId,
|
|
4866
|
+
groupId,
|
|
4867
|
+
status: projected.status,
|
|
4868
|
+
message,
|
|
4869
|
+
progress: projected.progress,
|
|
4870
|
+
bytesDownloaded: projected.bytesDownloaded,
|
|
4871
|
+
totalBytes: projected.totalBytes,
|
|
4872
|
+
sessionId,
|
|
4873
|
+
resumable: projected.resumable,
|
|
4874
|
+
files: projected.files,
|
|
4875
|
+
updatedAt: this.now()
|
|
4876
|
+
});
|
|
4877
|
+
}
|
|
4878
|
+
async readSelectedModel() {
|
|
4879
|
+
return (await this.options.globalSettingsManager.readSettings()).translationEngines.localLlama.model;
|
|
4880
|
+
}
|
|
4881
|
+
async readSelectedGroupId() {
|
|
4882
|
+
return (await this.options.globalSettingsManager.readSettings()).translationEngines.localLlama.selectedGroupId;
|
|
4883
|
+
}
|
|
4884
|
+
async readHuggingFaceEndpoint() {
|
|
4885
|
+
return (await this.options.globalSettingsManager.readSettings()).translationEngines.localLlama.hfEndpoint;
|
|
4886
|
+
}
|
|
4887
|
+
isActiveSession(modelId, groupId, sessionId) {
|
|
4888
|
+
return this.sessions.get(buildSessionKey$1(modelId, groupId))?.sessionId === sessionId;
|
|
4889
|
+
}
|
|
4890
|
+
emitLog(log) {
|
|
4891
|
+
this.logs.set(log.modelId, log);
|
|
4892
|
+
for (const listener of this.listeners) listener(log);
|
|
4893
|
+
}
|
|
4894
|
+
};
|
|
4895
|
+
function toCatalogItem$1(candidate, asset) {
|
|
4896
|
+
const downloadGroups = asset.plan?.groups ?? candidate.downloadGroups;
|
|
4897
|
+
const hasSelectableGroup = downloadGroups?.some((group) => group.selectable) ?? false;
|
|
4898
|
+
const local = asset.status === "downloaded" || asset.status === "paused" || asset.status === "downloading" || (asset.progress ?? 0) > 0;
|
|
4899
|
+
return {
|
|
4900
|
+
...candidate,
|
|
4901
|
+
downloadGroups,
|
|
4902
|
+
asset,
|
|
4903
|
+
selectable: hasSelectableGroup || (candidate.size.estimatedTotalBytes ?? 0) > 0,
|
|
4904
|
+
local,
|
|
4905
|
+
primarySource: local ? "local" : candidate.compatibility.localRuntimeVerified ? "recommended" : "network",
|
|
4906
|
+
sources: [local ? "local" : candidate.compatibility.localRuntimeVerified ? "recommended" : "network"]
|
|
4907
|
+
};
|
|
4908
|
+
}
|
|
4909
|
+
function compareCatalogItems$1(left, right) {
|
|
4910
|
+
if (left.local !== right.local) return left.local ? -1 : 1;
|
|
4911
|
+
if (left.asset.selected !== right.asset.selected) return left.asset.selected ? -1 : 1;
|
|
4912
|
+
const rightProgress = right.asset.progress ?? 0;
|
|
4913
|
+
const leftProgress = left.asset.progress ?? 0;
|
|
4914
|
+
if (left.local && right.local && leftProgress !== rightProgress) return rightProgress - leftProgress;
|
|
4915
|
+
return right.downloads - left.downloads;
|
|
4916
|
+
}
|
|
4917
|
+
function createSyntheticManifestFromPlan(input) {
|
|
4918
|
+
const groupEntries = (input.plan?.groups ?? []).flatMap((group) => {
|
|
4919
|
+
if (!group.commitHash || !group.shortCommitHash || group.estimatedTotalBytes === void 0 || !group.selectable) return [];
|
|
4920
|
+
return [[group.id, {
|
|
4921
|
+
id: group.id,
|
|
4922
|
+
baseGroupId: group.baseGroupId ?? group.id,
|
|
4923
|
+
label: group.label,
|
|
4924
|
+
displayLabel: group.label,
|
|
4925
|
+
description: group.description,
|
|
4926
|
+
profile: group.profile,
|
|
4927
|
+
dtype: group.dtype,
|
|
4928
|
+
commitHash: group.commitHash,
|
|
4929
|
+
shortCommitHash: group.shortCommitHash,
|
|
4930
|
+
rootDir: group.rootDir ?? getLocalLlamaModelArtifactGroupRoot(input.cacheDir, input.modelId, group.id),
|
|
4931
|
+
estimatedTotalBytes: group.estimatedTotalBytes,
|
|
4932
|
+
selectable: group.selectable,
|
|
4933
|
+
files: group.files.map((file) => ({
|
|
4934
|
+
...file,
|
|
4935
|
+
revision: file.revision ?? group.commitHash
|
|
4936
|
+
}))
|
|
4937
|
+
}]];
|
|
4938
|
+
});
|
|
4939
|
+
if (groupEntries.length === 0) return void 0;
|
|
4940
|
+
const firstGroup = groupEntries[0]?.[1];
|
|
4941
|
+
return LocalModelProfileManifestSchema.parse({
|
|
4942
|
+
modelId: input.modelId,
|
|
4943
|
+
source: "huggingface",
|
|
4944
|
+
endpoint: "",
|
|
4945
|
+
revision: firstGroup?.commitHash ?? "legacy",
|
|
4946
|
+
commitHash: firstGroup?.commitHash ?? "legacy",
|
|
4947
|
+
shortCommitHash: firstGroup?.shortCommitHash ?? "legacy",
|
|
4948
|
+
fetchedAt: 0,
|
|
4949
|
+
updatedAt: 0,
|
|
4950
|
+
groups: Object.fromEntries(groupEntries),
|
|
4951
|
+
groupOrder: groupEntries.map(([groupId]) => groupId)
|
|
4952
|
+
});
|
|
4953
|
+
}
|
|
4954
|
+
function buildPlanFromManifest$1(input) {
|
|
4955
|
+
const selectedGroupId = input.selectedGroupId && input.manifest.groups[input.selectedGroupId]?.selectable ? input.selectedGroupId : selectFirstManifestGroupId$1(input.manifest);
|
|
4956
|
+
const groups = input.manifest.groupOrder.flatMap((groupId) => {
|
|
4957
|
+
const manifestGroup = input.manifest.groups[groupId];
|
|
4958
|
+
if (!manifestGroup) return [];
|
|
4959
|
+
const groupState = input.groupsState[groupId];
|
|
4960
|
+
return [{
|
|
4961
|
+
id: manifestGroup.id,
|
|
4962
|
+
label: formatManifestGroupChipLabel$1(input.manifest, manifestGroup),
|
|
4963
|
+
description: manifestGroup.description,
|
|
4964
|
+
profile: manifestGroup.profile,
|
|
4965
|
+
dtype: manifestGroup.dtype,
|
|
4966
|
+
estimatedTotalBytes: manifestGroup.estimatedTotalBytes,
|
|
4967
|
+
baseGroupId: manifestGroup.baseGroupId,
|
|
4968
|
+
commitHash: manifestGroup.commitHash,
|
|
4969
|
+
shortCommitHash: manifestGroup.shortCommitHash,
|
|
4970
|
+
rootDir: manifestGroup.rootDir,
|
|
4971
|
+
status: groupState?.status ?? "not-downloaded",
|
|
4972
|
+
progress: groupState?.progress,
|
|
4973
|
+
bytesDownloaded: groupState?.bytesDownloaded,
|
|
4974
|
+
totalBytes: groupState?.totalBytes ?? manifestGroup.estimatedTotalBytes,
|
|
4975
|
+
resumable: groupState?.resumable,
|
|
4976
|
+
error: groupState?.error,
|
|
4977
|
+
selectable: manifestGroup.selectable,
|
|
4978
|
+
selected: manifestGroup.id === selectedGroupId,
|
|
4979
|
+
files: manifestGroup.files.map((file) => ({
|
|
4980
|
+
...file,
|
|
4981
|
+
required: file.required
|
|
4982
|
+
}))
|
|
4983
|
+
}];
|
|
4984
|
+
});
|
|
4985
|
+
const selectedGroup = groups.find((group) => group.id === selectedGroupId) ?? groups[0];
|
|
4986
|
+
if (!selectedGroup) return void 0;
|
|
4987
|
+
return {
|
|
4988
|
+
modelId: input.modelId,
|
|
4989
|
+
estimatedTotalBytes: selectedGroup.estimatedTotalBytes,
|
|
4990
|
+
files: selectedGroup.files,
|
|
4991
|
+
selectedGroupId: selectedGroup.id,
|
|
4992
|
+
groups
|
|
4993
|
+
};
|
|
4994
|
+
}
|
|
4995
|
+
function formatManifestGroupChipLabel$1(manifest, group) {
|
|
4996
|
+
if (group.commitHash === manifest.commitHash) return group.label;
|
|
4997
|
+
return `${group.label} · ${group.shortCommitHash}`;
|
|
4998
|
+
}
|
|
4999
|
+
function resolveManifestGroupId$1(manifest, requestedGroupId) {
|
|
5000
|
+
if (!manifest || !requestedGroupId) return requestedGroupId;
|
|
5001
|
+
if (manifest.groups[requestedGroupId]?.selectable) return requestedGroupId;
|
|
5002
|
+
return manifest.groupOrder.find((groupId) => {
|
|
5003
|
+
const group = manifest.groups[groupId];
|
|
5004
|
+
return group?.selectable && group.baseGroupId === requestedGroupId;
|
|
5005
|
+
});
|
|
5006
|
+
}
|
|
5007
|
+
function selectFirstManifestGroupId$1(manifest) {
|
|
5008
|
+
if (!manifest) return void 0;
|
|
5009
|
+
return manifest.groupOrder.find((groupId) => manifest.groups[groupId]?.selectable);
|
|
5010
|
+
}
|
|
5011
|
+
function selectPlanGroup(plan, selectedGroupId) {
|
|
5012
|
+
if (!plan?.groups?.length) return void 0;
|
|
5013
|
+
return plan.groups.find((group) => group.id === selectedGroupId) ?? plan.groups.find((group) => group.baseGroupId === selectedGroupId) ?? plan.groups.find((group) => group.selected) ?? plan.groups[0];
|
|
5014
|
+
}
|
|
5015
|
+
function removeManifestGroup$1(manifest, groupId) {
|
|
5016
|
+
if (!manifest || !manifest.groups[groupId]) return manifest;
|
|
5017
|
+
const groups = { ...manifest.groups };
|
|
5018
|
+
delete groups[groupId];
|
|
5019
|
+
const groupOrder = manifest.groupOrder.filter((entry) => entry !== groupId);
|
|
5020
|
+
if (groupOrder.length === 0) return void 0;
|
|
5021
|
+
return LocalModelProfileManifestSchema.parse({
|
|
5022
|
+
...manifest,
|
|
5023
|
+
groups,
|
|
5024
|
+
groupOrder,
|
|
5025
|
+
updatedAt: manifest.updatedAt
|
|
5026
|
+
});
|
|
5027
|
+
}
|
|
5028
|
+
function removePlanGroup$1(plan, groupId) {
|
|
5029
|
+
if (!plan?.groups?.length) return void 0;
|
|
5030
|
+
const groups = plan.groups.filter((group) => group.id !== groupId);
|
|
5031
|
+
if (groups.length === 0) return void 0;
|
|
5032
|
+
const selectedGroup = groups.find((group) => group.selected) ?? groups[0];
|
|
5033
|
+
return {
|
|
5034
|
+
modelId: plan.modelId,
|
|
5035
|
+
estimatedTotalBytes: selectedGroup?.estimatedTotalBytes,
|
|
5036
|
+
files: selectedGroup?.files ?? [],
|
|
5037
|
+
selectedGroupId: selectedGroup?.id,
|
|
5038
|
+
groups: groups.map((group) => ({
|
|
5039
|
+
...group,
|
|
5040
|
+
selected: group.id === selectedGroup?.id,
|
|
5041
|
+
files: [...group.files]
|
|
5042
|
+
}))
|
|
5043
|
+
};
|
|
5044
|
+
}
|
|
5045
|
+
function reconcileGroupFilesFromSnapshot$1(input) {
|
|
5046
|
+
return input.manifestGroup.files.map((file) => {
|
|
5047
|
+
const current = input.currentFiles.find((entry) => entry.path === file.path);
|
|
5048
|
+
return LocalModelLifecycleFileStateSchema.parse({
|
|
5049
|
+
path: file.path,
|
|
5050
|
+
sizeBytes: file.sizeBytes,
|
|
5051
|
+
downloadedBytes: current?.downloadedBytes ?? (input.currentStatus === "downloaded" ? file.sizeBytes : 0),
|
|
5052
|
+
required: file.required,
|
|
5053
|
+
status: current?.status ?? input.currentStatus,
|
|
5054
|
+
updatedAt: current?.updatedAt,
|
|
5055
|
+
error: current?.error
|
|
5056
|
+
});
|
|
5057
|
+
});
|
|
5058
|
+
}
|
|
5059
|
+
function reconcileGroupFiles$1(input) {
|
|
5060
|
+
return input.manifestGroup.files.map((file) => {
|
|
5061
|
+
const current = input.currentFiles.find((entry) => entry.path === file.path);
|
|
5062
|
+
return LocalModelLifecycleFileStateSchema.parse({
|
|
5063
|
+
path: file.path,
|
|
5064
|
+
sizeBytes: file.sizeBytes,
|
|
5065
|
+
downloadedBytes: current?.downloadedBytes,
|
|
5066
|
+
required: file.required,
|
|
5067
|
+
status: current?.status ?? "not-downloaded",
|
|
5068
|
+
updatedAt: current?.updatedAt,
|
|
5069
|
+
error: current?.error
|
|
5070
|
+
});
|
|
5071
|
+
});
|
|
5072
|
+
}
|
|
5073
|
+
async function reconcileGroupFilesFromDisk$1(input) {
|
|
5074
|
+
return Promise.all(input.manifestGroup.files.map(async (file) => {
|
|
5075
|
+
const current = input.currentFiles.find((entry) => entry.path === file.path);
|
|
5076
|
+
try {
|
|
5077
|
+
const downloadedBytes = (await stat(join(input.rootDir, file.path))).size;
|
|
5078
|
+
const complete = file.sizeBytes !== void 0 && downloadedBytes >= file.sizeBytes;
|
|
5079
|
+
return LocalModelLifecycleFileStateSchema.parse({
|
|
5080
|
+
path: file.path,
|
|
5081
|
+
sizeBytes: file.sizeBytes,
|
|
5082
|
+
downloadedBytes,
|
|
5083
|
+
required: file.required,
|
|
5084
|
+
status: complete ? "downloaded" : downloadedBytes > 0 ? "paused" : "not-downloaded",
|
|
5085
|
+
updatedAt: current?.updatedAt,
|
|
5086
|
+
error: current?.error
|
|
5087
|
+
});
|
|
5088
|
+
} catch {
|
|
5089
|
+
return LocalModelLifecycleFileStateSchema.parse({
|
|
5090
|
+
path: file.path,
|
|
5091
|
+
sizeBytes: file.sizeBytes,
|
|
5092
|
+
downloadedBytes: 0,
|
|
5093
|
+
required: file.required,
|
|
5094
|
+
status: "not-downloaded",
|
|
5095
|
+
updatedAt: current?.updatedAt,
|
|
5096
|
+
error: current?.error
|
|
5097
|
+
});
|
|
5098
|
+
}
|
|
5099
|
+
}));
|
|
5100
|
+
}
|
|
5101
|
+
function sumDownloadedBytes$1(files) {
|
|
5102
|
+
return files.reduce((total, file) => total + (file.downloadedBytes ?? 0), 0);
|
|
3582
5103
|
}
|
|
3583
|
-
function
|
|
3584
|
-
return
|
|
5104
|
+
function buildSessionKey$1(modelId, groupId) {
|
|
5105
|
+
return `${modelId}:${groupId}`;
|
|
3585
5106
|
}
|
|
3586
|
-
function
|
|
3587
|
-
|
|
3588
|
-
case ".html": return "text/html";
|
|
3589
|
-
case ".js":
|
|
3590
|
-
case ".mjs": return "application/javascript";
|
|
3591
|
-
case ".css": return "text/css";
|
|
3592
|
-
case ".json": return "application/json";
|
|
3593
|
-
case ".svg": return "image/svg+xml";
|
|
3594
|
-
case ".png": return "image/png";
|
|
3595
|
-
case ".jpg":
|
|
3596
|
-
case ".jpeg": return "image/jpeg";
|
|
3597
|
-
case ".woff": return "font/woff";
|
|
3598
|
-
case ".woff2": return "font/woff2";
|
|
3599
|
-
default: return inferFileMime(path) ?? "application/octet-stream";
|
|
3600
|
-
}
|
|
5107
|
+
function buildVersionedGroupId$1(baseGroupId, shortCommitHash) {
|
|
5108
|
+
return `${sanitizeId$1(baseGroupId)}-${sanitizeId$1(shortCommitHash)}`;
|
|
3601
5109
|
}
|
|
3602
|
-
function
|
|
3603
|
-
|
|
3604
|
-
return extension === ".html" || extension === ".js" || extension === ".mjs" || extension === ".css";
|
|
5110
|
+
function sanitizeId$1(value) {
|
|
5111
|
+
return value.replace(/[^a-zA-Z0-9._-]+/g, "-");
|
|
3605
5112
|
}
|
|
3606
|
-
function
|
|
3607
|
-
|
|
3608
|
-
return content.replaceAll("/assets/", sessionAssetPrefix);
|
|
5113
|
+
function isActiveDownloadStatus$1(status) {
|
|
5114
|
+
return status === "queued" || status === "downloading" || status === "deleting";
|
|
3609
5115
|
}
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
5116
|
+
function formatBytes$2(value) {
|
|
5117
|
+
if (value < 1024) return `${value} B`;
|
|
5118
|
+
const units = [
|
|
5119
|
+
"KB",
|
|
5120
|
+
"MB",
|
|
5121
|
+
"GB",
|
|
5122
|
+
"TB"
|
|
5123
|
+
];
|
|
5124
|
+
let size = value / 1024;
|
|
5125
|
+
let unitIndex = 0;
|
|
5126
|
+
while (size >= 1024 && unitIndex < units.length - 1) {
|
|
5127
|
+
size /= 1024;
|
|
5128
|
+
unitIndex += 1;
|
|
3615
5129
|
}
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
5130
|
+
const digits = size >= 100 ? 0 : size >= 10 ? 1 : 2;
|
|
5131
|
+
return `${size.toFixed(digits)} ${units[unitIndex]}`;
|
|
5132
|
+
}
|
|
5133
|
+
function formatDuration$1(ms) {
|
|
5134
|
+
if (ms < 1e3) return `${ms} ms`;
|
|
5135
|
+
const seconds = ms / 1e3;
|
|
5136
|
+
return seconds >= 10 ? `${seconds.toFixed(0)} s` : `${seconds.toFixed(1)} s`;
|
|
5137
|
+
}
|
|
5138
|
+
function throwIfAborted$1(signal) {
|
|
5139
|
+
if (signal.aborted) throw new DOMException("The operation was aborted.", "AbortError");
|
|
5140
|
+
}
|
|
5141
|
+
async function downloadUrlFileWithProgress(input) {
|
|
5142
|
+
let attempt = 0;
|
|
5143
|
+
let downloadedBytes = 0;
|
|
5144
|
+
while (true) try {
|
|
5145
|
+
await downloadUrlFileWithProgressOnce({
|
|
5146
|
+
...input,
|
|
5147
|
+
existingBytes: downloadedBytes,
|
|
5148
|
+
onProgress: async (nextDownloadedBytes) => {
|
|
5149
|
+
downloadedBytes = nextDownloadedBytes;
|
|
5150
|
+
await input.onProgress(nextDownloadedBytes);
|
|
5151
|
+
}
|
|
3638
5152
|
});
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
resourcePathname,
|
|
3648
|
-
entryPathname,
|
|
3649
|
-
urlPath: previewKind === "html" ? htmlPathname : `${entryPathname}?file=${encodeURIComponent(fileName)}`
|
|
3650
|
-
};
|
|
5153
|
+
return;
|
|
5154
|
+
} catch (error) {
|
|
5155
|
+
throwIfAborted$1(input.signal);
|
|
5156
|
+
if (!isRetryableNetworkError(error) || attempt >= input.retryPolicy.limit) throw error;
|
|
5157
|
+
attempt += 1;
|
|
5158
|
+
const retryDelayMs = Math.min(input.retryPolicy.maxDelayMs, input.retryPolicy.delayMs * Math.max(1, attempt));
|
|
5159
|
+
await input.onRetry({ retryDelayMs });
|
|
5160
|
+
await delay$2(retryDelayMs);
|
|
3651
5161
|
}
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
if (!absolutePath.startsWith(resolve(this.previewAssetsDir) + "/")) return null;
|
|
3679
|
-
if (!existsSync(absolutePath) || !statSync(absolutePath).isFile()) return null;
|
|
3680
|
-
if (isRewritablePreviewAsset(assetName)) {
|
|
3681
|
-
const rewritten = rewritePreviewAssetPaths(readFileSync(absolutePath, "utf8"), hash);
|
|
3682
|
-
return {
|
|
3683
|
-
content: Buffer.from(rewritten, "utf8"),
|
|
3684
|
-
contentType: inferPreviewAssetContentType(absolutePath)
|
|
3685
|
-
};
|
|
5162
|
+
}
|
|
5163
|
+
async function downloadUrlFileWithProgressOnce(input) {
|
|
5164
|
+
await mkdir(dirname(input.targetPath), { recursive: true });
|
|
5165
|
+
const tempPath = `${input.targetPath}.part`;
|
|
5166
|
+
const requestHeaders = new Headers();
|
|
5167
|
+
if (input.existingBytes > 0) requestHeaders.set("Range", `bytes=${input.existingBytes}-`);
|
|
5168
|
+
const response = await fetch(input.url, {
|
|
5169
|
+
signal: input.signal,
|
|
5170
|
+
headers: requestHeaders
|
|
5171
|
+
});
|
|
5172
|
+
if (!response.ok && response.status !== 206) throw new Error(`Download failed with status ${response.status}.`);
|
|
5173
|
+
const contentLength = Number(response.headers.get("content-length") ?? "0");
|
|
5174
|
+
const totalBytes = input.expectedSizeBytes ?? (contentLength > 0 ? input.existingBytes + contentLength : void 0);
|
|
5175
|
+
const fileHandle = await open(tempPath, input.existingBytes > 0 ? "a" : "w");
|
|
5176
|
+
try {
|
|
5177
|
+
const reader = response.body?.getReader();
|
|
5178
|
+
if (!reader) throw new Error("Download stream was not readable.");
|
|
5179
|
+
let bytesDownloaded = input.existingBytes;
|
|
5180
|
+
while (true) {
|
|
5181
|
+
throwIfAborted$1(input.signal);
|
|
5182
|
+
const { done, value } = await reader.read();
|
|
5183
|
+
if (done) break;
|
|
5184
|
+
if (!value) continue;
|
|
5185
|
+
await fileHandle.write(value);
|
|
5186
|
+
bytesDownloaded += value.byteLength;
|
|
5187
|
+
await input.onProgress(bytesDownloaded);
|
|
3686
5188
|
}
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
5189
|
+
await fileHandle.close();
|
|
5190
|
+
if (totalBytes !== void 0 && bytesDownloaded < totalBytes) throw new Error(`Download ended early for ${input.targetPath}.`);
|
|
5191
|
+
await rename(tempPath, input.targetPath);
|
|
5192
|
+
} catch (error) {
|
|
5193
|
+
await fileHandle.close().catch(() => void 0);
|
|
5194
|
+
throw error;
|
|
3691
5195
|
}
|
|
3692
|
-
}
|
|
5196
|
+
}
|
|
5197
|
+
async function delay$2(ms) {
|
|
5198
|
+
await new Promise((resolve$1) => setTimeout(resolve$1, ms));
|
|
5199
|
+
}
|
|
3693
5200
|
|
|
3694
5201
|
//#endregion
|
|
3695
5202
|
//#region src/local-model-local-cache.ts
|
|
@@ -5465,12 +6972,15 @@ function getHubCacheRepoPath(cacheDir, modelId) {
|
|
|
5465
6972
|
function toCatalogItem(candidate, asset) {
|
|
5466
6973
|
const downloadGroups = asset.plan?.groups ?? candidate.downloadGroups;
|
|
5467
6974
|
const hasSelectableGroup = downloadGroups?.some((group) => group.selectable) ?? false;
|
|
6975
|
+
const local = asset.status === "downloaded" || asset.status === "paused" || asset.status === "downloading" || (asset.progress ?? 0) > 0;
|
|
5468
6976
|
return {
|
|
5469
6977
|
...candidate,
|
|
5470
6978
|
downloadGroups,
|
|
5471
6979
|
asset,
|
|
5472
6980
|
selectable: hasSelectableGroup || (candidate.size.estimatedTotalBytes ?? 0) > 0,
|
|
5473
|
-
local
|
|
6981
|
+
local,
|
|
6982
|
+
primarySource: local ? "local" : "network",
|
|
6983
|
+
sources: [local ? "local" : "network"]
|
|
5474
6984
|
};
|
|
5475
6985
|
}
|
|
5476
6986
|
function compareCatalogItems(left, right) {
|
|
@@ -7147,7 +8657,8 @@ const globalSettingsRouter = router({
|
|
|
7147
8657
|
translationEngines: z.object({
|
|
7148
8658
|
openai: TranslationOpenAISettingsSchema.partial().optional(),
|
|
7149
8659
|
local: TranslationLocalSettingsSchema.partial().extend({ selectedGroupId: z.string().min(1).nullable().optional() }).optional(),
|
|
7150
|
-
localCt2: TranslationLocalCt2SettingsSchema.partial().extend({ selectedGroupId: z.string().min(1).nullable().optional() }).optional()
|
|
8660
|
+
localCt2: TranslationLocalCt2SettingsSchema.partial().extend({ selectedGroupId: z.string().min(1).nullable().optional() }).optional(),
|
|
8661
|
+
localLlama: TranslationLocalLlamaSettingsSchema.partial().extend({ selectedGroupId: z.string().min(1).nullable().optional() }).optional()
|
|
7151
8662
|
}).optional()
|
|
7152
8663
|
})).mutation(async ({ ctx, input }) => {
|
|
7153
8664
|
await ctx.globalSettingsManager.writeSettings(input);
|
|
@@ -7416,6 +8927,104 @@ const localCt2ModelsRouter = router({
|
|
|
7416
8927
|
};
|
|
7417
8928
|
})
|
|
7418
8929
|
});
|
|
8930
|
+
const localLlamaModelsRouter = router({
|
|
8931
|
+
listLocal: publicProcedure.query(({ ctx }) => {
|
|
8932
|
+
return ctx.localLlamaModelAssetService.listLocalCatalog();
|
|
8933
|
+
}),
|
|
8934
|
+
searchRemote: publicProcedure.input(z.object({
|
|
8935
|
+
requestId: z.string().min(1).optional(),
|
|
8936
|
+
query: z.string().optional(),
|
|
8937
|
+
sourceLanguage: z.string().optional(),
|
|
8938
|
+
targetLanguage: z.string().optional(),
|
|
8939
|
+
limit: z.number().int().positive().max(20).optional(),
|
|
8940
|
+
cursor: z.string().optional()
|
|
8941
|
+
})).query(({ ctx, input }) => {
|
|
8942
|
+
return ctx.localLlamaModelAssetService.searchRemoteCatalog({
|
|
8943
|
+
engineId: "local-llama",
|
|
8944
|
+
...input
|
|
8945
|
+
});
|
|
8946
|
+
}),
|
|
8947
|
+
searchRemoteStream: publicProcedure.input(z.object({
|
|
8948
|
+
requestId: z.string().min(1),
|
|
8949
|
+
query: z.string().optional(),
|
|
8950
|
+
sourceLanguage: z.string().optional(),
|
|
8951
|
+
targetLanguage: z.string().optional(),
|
|
8952
|
+
limit: z.number().int().positive().max(20).optional(),
|
|
8953
|
+
cursor: z.string().optional()
|
|
8954
|
+
})).subscription(({ ctx, input }) => {
|
|
8955
|
+
return ctx.localLlamaModelAssetService.subscribeRemoteCatalog({
|
|
8956
|
+
engineId: "local-llama",
|
|
8957
|
+
...input
|
|
8958
|
+
});
|
|
8959
|
+
}),
|
|
8960
|
+
state: publicProcedure.input(z.object({
|
|
8961
|
+
modelId: z.string().min(1),
|
|
8962
|
+
selectedGroupId: z.string().min(1).optional()
|
|
8963
|
+
})).query(({ ctx, input }) => {
|
|
8964
|
+
return ctx.localLlamaModelAssetService.readSelectedModelState(input.modelId, input.selectedGroupId);
|
|
8965
|
+
}),
|
|
8966
|
+
panelState: publicProcedure.input(z.object({
|
|
8967
|
+
modelId: z.string().min(1),
|
|
8968
|
+
selectedGroupId: z.string().min(1).optional()
|
|
8969
|
+
})).query(async ({ ctx, input }) => {
|
|
8970
|
+
const asset = await ctx.localLlamaModelAssetService.readSelectedModelState(input.modelId, input.selectedGroupId);
|
|
8971
|
+
return {
|
|
8972
|
+
modelId: input.modelId,
|
|
8973
|
+
selectedGroupId: asset.selectedGroupId ?? asset.plan?.selectedGroupId,
|
|
8974
|
+
asset,
|
|
8975
|
+
downloadPlan: asset.plan ?? null
|
|
8976
|
+
};
|
|
8977
|
+
}),
|
|
8978
|
+
subscribeLogs: publicProcedure.subscription(({ ctx }) => {
|
|
8979
|
+
return ctx.localLlamaModelAssetService.subscribeLogs();
|
|
8980
|
+
}),
|
|
8981
|
+
markSelected: publicProcedure.input(z.object({ modelId: z.string().min(1) })).mutation(async ({ ctx, input }) => {
|
|
8982
|
+
const asset = await ctx.localLlamaModelAssetService.markSelectedModel(input.modelId);
|
|
8983
|
+
return {
|
|
8984
|
+
modelId: input.modelId,
|
|
8985
|
+
selectedGroupId: asset.selectedGroupId ?? asset.plan?.selectedGroupId,
|
|
8986
|
+
asset,
|
|
8987
|
+
downloadPlan: asset.plan ?? null
|
|
8988
|
+
};
|
|
8989
|
+
}),
|
|
8990
|
+
download: publicProcedure.input(z.object({
|
|
8991
|
+
modelId: z.string().min(1),
|
|
8992
|
+
groupId: z.string().min(1).optional(),
|
|
8993
|
+
selectedGroupId: z.string().min(1).optional()
|
|
8994
|
+
})).mutation(async ({ ctx, input }) => {
|
|
8995
|
+
return ctx.localLlamaModelAssetService.startDownload(input.modelId, input.groupId ?? input.selectedGroupId);
|
|
8996
|
+
}),
|
|
8997
|
+
pause: publicProcedure.input(z.object({
|
|
8998
|
+
modelId: z.string().min(1),
|
|
8999
|
+
groupId: z.string().min(1).optional(),
|
|
9000
|
+
selectedGroupId: z.string().min(1).optional()
|
|
9001
|
+
})).mutation(({ ctx, input }) => {
|
|
9002
|
+
return ctx.localLlamaModelAssetService.pauseDownload(input.modelId, input.groupId ?? input.selectedGroupId);
|
|
9003
|
+
}),
|
|
9004
|
+
resume: publicProcedure.input(z.object({
|
|
9005
|
+
modelId: z.string().min(1),
|
|
9006
|
+
groupId: z.string().min(1).optional(),
|
|
9007
|
+
selectedGroupId: z.string().min(1).optional()
|
|
9008
|
+
})).mutation(async ({ ctx, input }) => {
|
|
9009
|
+
return ctx.localLlamaModelAssetService.resumeDownload(input.modelId, input.groupId ?? input.selectedGroupId);
|
|
9010
|
+
}),
|
|
9011
|
+
delete: publicProcedure.input(z.object({
|
|
9012
|
+
modelId: z.string().min(1),
|
|
9013
|
+
groupId: z.string().min(1).optional(),
|
|
9014
|
+
selectedGroupId: z.string().min(1).optional()
|
|
9015
|
+
})).mutation(({ ctx, input }) => {
|
|
9016
|
+
return ctx.localLlamaModelAssetService.deleteModel(input.modelId, input.groupId ?? input.selectedGroupId);
|
|
9017
|
+
}),
|
|
9018
|
+
refreshArtifacts: publicProcedure.input(z.object({ modelId: z.string().min(1).optional() })).mutation(async ({ ctx, input }) => {
|
|
9019
|
+
const asset = await ctx.localLlamaModelAssetService.refreshArtifacts(input.modelId);
|
|
9020
|
+
return {
|
|
9021
|
+
modelId: asset.modelId,
|
|
9022
|
+
selectedGroupId: asset.selectedGroupId ?? asset.plan?.selectedGroupId,
|
|
9023
|
+
asset,
|
|
9024
|
+
downloadPlan: asset.plan ?? null
|
|
9025
|
+
};
|
|
9026
|
+
})
|
|
9027
|
+
});
|
|
7419
9028
|
const OPSX_CORE_PROFILE_WORKFLOWS = [
|
|
7420
9029
|
"propose",
|
|
7421
9030
|
"explore",
|
|
@@ -8574,6 +10183,7 @@ const appRouter = router({
|
|
|
8574
10183
|
translationEngines: translationEnginesRouter,
|
|
8575
10184
|
localModels: localModelsRouter,
|
|
8576
10185
|
localCt2Models: localCt2ModelsRouter,
|
|
10186
|
+
localLlamaModels: localLlamaModelsRouter,
|
|
8577
10187
|
notifications: notificationsRouter,
|
|
8578
10188
|
sounds: soundsRouter,
|
|
8579
10189
|
cli: cliRouter,
|
|
@@ -9057,8 +10667,10 @@ var TranslationEngineService = class {
|
|
|
9057
10667
|
now;
|
|
9058
10668
|
localCacheDir;
|
|
9059
10669
|
localCt2CacheDir;
|
|
10670
|
+
localLlamaCacheDir;
|
|
9060
10671
|
localAssetStore;
|
|
9061
10672
|
localCt2AssetStore;
|
|
10673
|
+
localLlamaAssetStore;
|
|
9062
10674
|
constructor(options) {
|
|
9063
10675
|
ensureProxyAwareFetchDispatcher();
|
|
9064
10676
|
this.projectDir = options.projectDir;
|
|
@@ -9067,8 +10679,10 @@ var TranslationEngineService = class {
|
|
|
9067
10679
|
this.now = options.now ?? Date.now;
|
|
9068
10680
|
this.localCacheDir = options.localCacheDir ?? getDefaultLocalModelCacheDir();
|
|
9069
10681
|
this.localCt2CacheDir = options.localCt2CacheDir ?? getDefaultLocalCt2ModelCacheDir();
|
|
10682
|
+
this.localLlamaCacheDir = options.localLlamaCacheDir ?? getDefaultLocalLlamaModelCacheDir();
|
|
9070
10683
|
this.localAssetStore = new LocalModelAssetStore({ indexPath: options.localAssetIndexPath ?? getDefaultLocalModelIndexPath() });
|
|
9071
10684
|
this.localCt2AssetStore = new LocalModelAssetStore({ indexPath: options.localCt2AssetIndexPath ?? getDefaultLocalCt2ModelIndexPath() });
|
|
10685
|
+
this.localLlamaAssetStore = new LocalModelAssetStore({ indexPath: options.localLlamaAssetIndexPath ?? getDefaultLocalLlamaModelIndexPath() });
|
|
9072
10686
|
new LocalModelFetchCacheStore({
|
|
9073
10687
|
cachePath: options.localFetchCachePath ?? getDefaultLocalModelFetchCachePath(),
|
|
9074
10688
|
now: this.now
|
|
@@ -9077,6 +10691,10 @@ var TranslationEngineService = class {
|
|
|
9077
10691
|
cachePath: options.localCt2FetchCachePath ?? getDefaultLocalCt2ModelFetchCachePath(),
|
|
9078
10692
|
now: this.now
|
|
9079
10693
|
});
|
|
10694
|
+
new LocalModelFetchCacheStore({
|
|
10695
|
+
cachePath: options.localLlamaFetchCachePath ?? getDefaultLocalLlamaModelFetchCachePath(),
|
|
10696
|
+
now: this.now
|
|
10697
|
+
});
|
|
9080
10698
|
}
|
|
9081
10699
|
async listEngines() {
|
|
9082
10700
|
const [config, globalSettings] = await Promise.all([this.configManager.readConfig(), this.globalSettingsManager.readSettings()]);
|
|
@@ -9206,10 +10824,11 @@ var TranslationEngineService = class {
|
|
|
9206
10824
|
const globalSettings = await this.globalSettingsManager.readSettings();
|
|
9207
10825
|
if (input.engineId === "local") return searchLocalModels(input, { hfEndpoint: globalSettings.translationEngines.local.hfEndpoint });
|
|
9208
10826
|
if (input.engineId === "local-ct2") return searchCt2Models(input, { hfEndpoint: globalSettings.translationEngines.localCt2.hfEndpoint });
|
|
10827
|
+
if (input.engineId === "local-llama") return searchLlamaModels(input, { hfEndpoint: globalSettings.translationEngines.localLlama.hfEndpoint });
|
|
9209
10828
|
return { items: [] };
|
|
9210
10829
|
}
|
|
9211
10830
|
async getModelDownloadPlan(input) {
|
|
9212
|
-
const state = input.engineId === "local" ? (await this.localAssetStore.readMap()).get(input.model) : input.engineId === "local-ct2" ? (await this.localCt2AssetStore.readMap()).get(input.model) : void 0;
|
|
10831
|
+
const state = input.engineId === "local" ? (await this.localAssetStore.readMap()).get(input.model) : input.engineId === "local-ct2" ? (await this.localCt2AssetStore.readMap()).get(input.model) : input.engineId === "local-llama" ? (await this.localLlamaAssetStore.readMap()).get(input.model) : void 0;
|
|
9213
10832
|
if (!state) return null;
|
|
9214
10833
|
return selectPersistedLocalPlan(state, input.selectedGroupId);
|
|
9215
10834
|
}
|
|
@@ -9229,7 +10848,7 @@ var TranslationEngineService = class {
|
|
|
9229
10848
|
if (input.engineId === "browser") throw new Error("Browser translator runs in the browser runtime.");
|
|
9230
10849
|
const settingsSnapshot = await this.globalSettingsManager.readSettings();
|
|
9231
10850
|
const effectiveModel = resolveBatchTranslateModel(input, settingsSnapshot);
|
|
9232
|
-
if (
|
|
10851
|
+
if (isDirectionalManagedLocalTranslationEngineId(input.engineId)) {
|
|
9233
10852
|
const directionCheck = checkLocalDirectionalModelLanguagePair({
|
|
9234
10853
|
model: effectiveModel,
|
|
9235
10854
|
sourceLanguage: input.sourceLanguage,
|
|
@@ -9237,10 +10856,10 @@ var TranslationEngineService = class {
|
|
|
9237
10856
|
});
|
|
9238
10857
|
if (!directionCheck.supported) throw new Error(directionCheck.message ?? "Selected local model does not support the requested translation direction.");
|
|
9239
10858
|
}
|
|
9240
|
-
const effectiveSelectedGroupId = input.engineId === "local" ? input.selectedGroupId ?? settingsSnapshot.translationEngines.local.selectedGroupId : input.engineId === "local-ct2" ? input.selectedGroupId ?? settingsSnapshot.translationEngines.localCt2.selectedGroupId : void 0;
|
|
10859
|
+
const effectiveSelectedGroupId = input.engineId === "local" ? input.selectedGroupId ?? settingsSnapshot.translationEngines.local.selectedGroupId : input.engineId === "local-ct2" ? input.selectedGroupId ?? settingsSnapshot.translationEngines.localCt2.selectedGroupId : input.engineId === "local-llama" ? input.selectedGroupId ?? settingsSnapshot.translationEngines.localLlama.selectedGroupId : void 0;
|
|
9241
10860
|
const dtype = await this.readLocalDtype(input.engineId, effectiveModel, effectiveSelectedGroupId);
|
|
9242
|
-
if (
|
|
9243
|
-
const runtimeConfig =
|
|
10861
|
+
if (isManagedLocalTranslationEngineId(input.engineId) && effectiveModel) await this.assertManagedLocalModelReady(input.engineId, effectiveModel, effectiveSelectedGroupId);
|
|
10862
|
+
const runtimeConfig = isManagedLocalTranslationEngineId(input.engineId) && effectiveModel ? await this.readManagedLocalRuntimeConfig(input.engineId, effectiveModel, effectiveSelectedGroupId) : void 0;
|
|
9244
10863
|
const translator = await (await this.loadFactory(input.engineId, effectiveModel, settingsSnapshot)).create({
|
|
9245
10864
|
sourceLanguage: input.sourceLanguage,
|
|
9246
10865
|
targetLanguage: input.targetLanguage,
|
|
@@ -9295,7 +10914,7 @@ var TranslationEngineService = class {
|
|
|
9295
10914
|
const allMissingFiles = selectedGroup.rootDir ? await readMissingLocalGroupFiles(selectedGroup.rootDir, files) : files.map((file) => file.path);
|
|
9296
10915
|
const missingFiles = allMissingFiles.slice(0, 3);
|
|
9297
10916
|
const suffix = allMissingFiles.length > missingFiles.length ? ` and ${allMissingFiles.length - missingFiles.length} more` : "";
|
|
9298
|
-
const engineLabel = engineId
|
|
10917
|
+
const engineLabel = getManagedLocalTranslationEngineManifest(engineId).modelLabel.toLowerCase();
|
|
9299
10918
|
throw new Error(`Selected ${engineLabel} files are not installed locally: ${missingFiles.join(", ")}${suffix}.`);
|
|
9300
10919
|
}
|
|
9301
10920
|
async readManagedLocalRuntimeConfig(engineId, model, selectedGroupId) {
|
|
@@ -9304,6 +10923,10 @@ var TranslationEngineService = class {
|
|
|
9304
10923
|
model,
|
|
9305
10924
|
selectedGroupId
|
|
9306
10925
|
}), selectedGroupId);
|
|
10926
|
+
if (engineId === "local-llama") {
|
|
10927
|
+
const ggufPath = selectedGroup?.rootDir && selectedGroup.files[0]?.path ? join(selectedGroup.rootDir, selectedGroup.files[0].path) : selectedGroup?.rootDir;
|
|
10928
|
+
return ggufPath ? { modelPath: ggufPath } : void 0;
|
|
10929
|
+
}
|
|
9307
10930
|
const configPath = selectedGroup?.rootDir ? join(selectedGroup.rootDir, "config.json") : join(engineId === "local" ? this.localCacheDir : this.localCt2CacheDir, "models", model, "config.json");
|
|
9308
10931
|
try {
|
|
9309
10932
|
const runtimeConfig = JSON.parse(await readFile(configPath, "utf8"));
|
|
@@ -9318,11 +10941,11 @@ var TranslationEngineService = class {
|
|
|
9318
10941
|
}
|
|
9319
10942
|
}
|
|
9320
10943
|
async readSelectedManagedLocalGroupState(engineId, model, selectedGroupId) {
|
|
9321
|
-
return (engineId === "local" ? (await this.localAssetStore.readMap()).get(model) : (await this.localCt2AssetStore.readMap()).get(model))?.groupsState[selectedGroupId];
|
|
10944
|
+
return (engineId === "local" ? (await this.localAssetStore.readMap()).get(model) : engineId === "local-ct2" ? (await this.localCt2AssetStore.readMap()).get(model) : (await this.localLlamaAssetStore.readMap()).get(model))?.groupsState[selectedGroupId];
|
|
9322
10945
|
}
|
|
9323
10946
|
async loadFactory(engineId, model, settingsSnapshot) {
|
|
9324
10947
|
const globalSettings = settingsSnapshot ?? await this.globalSettingsManager.readSettings();
|
|
9325
|
-
if (engineId === "local") return (await import("./src
|
|
10948
|
+
if (engineId === "local") return (await import("./src-BHCDKXul.mjs")).createLocalTranslatorFactory({
|
|
9326
10949
|
defaultModel: model ?? globalSettings.translationEngines.local.model,
|
|
9327
10950
|
cacheDir: this.localCacheDir,
|
|
9328
10951
|
localOnly: true
|
|
@@ -9331,7 +10954,11 @@ var TranslationEngineService = class {
|
|
|
9331
10954
|
defaultModel: model ?? globalSettings.translationEngines.localCt2.model,
|
|
9332
10955
|
cacheDir: this.localCt2CacheDir
|
|
9333
10956
|
});
|
|
9334
|
-
return (await import("./src-
|
|
10957
|
+
if (engineId === "local-llama") return (await import("./src-Cc9NSywS.mjs")).createLocalLlamaTranslatorFactory({
|
|
10958
|
+
defaultModel: model ?? globalSettings.translationEngines.localLlama.model,
|
|
10959
|
+
cacheDir: this.localLlamaCacheDir
|
|
10960
|
+
});
|
|
10961
|
+
return (await import("./src-5XpFsBo7.mjs")).createOpenAICompletionTranslatorFactory({
|
|
9335
10962
|
baseUrl: globalSettings.translationEngines.openai.baseUrl,
|
|
9336
10963
|
token: globalSettings.translationEngines.openai.token,
|
|
9337
10964
|
model: model ?? globalSettings.translationEngines.openai.model
|
|
@@ -9343,7 +10970,7 @@ var TranslationEngineService = class {
|
|
|
9343
10970
|
state: "missing",
|
|
9344
10971
|
message: "Select a model before translating."
|
|
9345
10972
|
};
|
|
9346
|
-
const state = engineId === "local" ? (await this.localAssetStore.readMap()).get(selection.model) : (await this.localCt2AssetStore.readMap()).get(selection.model);
|
|
10973
|
+
const state = engineId === "local" ? (await this.localAssetStore.readMap()).get(selection.model) : engineId === "local-ct2" ? (await this.localCt2AssetStore.readMap()).get(selection.model) : (await this.localLlamaAssetStore.readMap()).get(selection.model);
|
|
9347
10974
|
const plan = selectPersistedLocalPlan(state, selection.selectedGroupId);
|
|
9348
10975
|
const selectedGroup = selectLocalPlanGroup(plan, selection.selectedGroupId);
|
|
9349
10976
|
if (!plan || !selectedGroup || selectedGroup.files.length === 0) return {
|
|
@@ -9369,6 +10996,7 @@ var TranslationEngineService = class {
|
|
|
9369
10996
|
if (engineId === "browser") return browserTranslationEngineLifecycleController;
|
|
9370
10997
|
if (engineId === "openai") return openAITranslationEngineLifecycleController;
|
|
9371
10998
|
if (engineId === "local-ct2") return createManagedLocalLifecycleController("local-ct2");
|
|
10999
|
+
if (engineId === "local-llama") return createManagedLocalLifecycleController("local-llama");
|
|
9372
11000
|
return createManagedLocalLifecycleController("local");
|
|
9373
11001
|
}
|
|
9374
11002
|
};
|
|
@@ -9399,6 +11027,10 @@ const browserTranslationEngineLifecycleController = {
|
|
|
9399
11027
|
model: "",
|
|
9400
11028
|
hfEndpoint: ""
|
|
9401
11029
|
},
|
|
11030
|
+
localLlama: {
|
|
11031
|
+
model: "",
|
|
11032
|
+
hfEndpoint: ""
|
|
11033
|
+
},
|
|
9402
11034
|
openai: {
|
|
9403
11035
|
baseUrl: "",
|
|
9404
11036
|
token: "",
|
|
@@ -9435,6 +11067,10 @@ const openAITranslationEngineLifecycleController = {
|
|
|
9435
11067
|
model: "",
|
|
9436
11068
|
hfEndpoint: ""
|
|
9437
11069
|
},
|
|
11070
|
+
localLlama: {
|
|
11071
|
+
model: "",
|
|
11072
|
+
hfEndpoint: ""
|
|
11073
|
+
},
|
|
9438
11074
|
openai: {
|
|
9439
11075
|
baseUrl: "",
|
|
9440
11076
|
token: "",
|
|
@@ -9457,6 +11093,7 @@ function createManagedLocalLifecycleController(engineId) {
|
|
|
9457
11093
|
function resolveEngineModel(engineId, config, globalSettings) {
|
|
9458
11094
|
if (engineId === "local") return config.translation.engines.local.model ?? globalSettings.translationEngines.local.model;
|
|
9459
11095
|
if (engineId === "local-ct2") return config.translation.engines.localCt2.model ?? globalSettings.translationEngines.localCt2.model;
|
|
11096
|
+
if (engineId === "local-llama") return config.translation.engines.localLlama.model ?? globalSettings.translationEngines.localLlama.model;
|
|
9460
11097
|
if (engineId === "openai") return config.translation.engines.openai.model ?? globalSettings.translationEngines.openai.model;
|
|
9461
11098
|
}
|
|
9462
11099
|
function resolveManagedLocalSelection(engineId, config, globalSettings) {
|
|
@@ -9465,6 +11102,10 @@ function resolveManagedLocalSelection(engineId, config, globalSettings) {
|
|
|
9465
11102
|
model: config.translation.engines.local.model ?? globalSettings.translationEngines.local.model ?? manifest.defaultModel,
|
|
9466
11103
|
selectedGroupId: config.translation.engines.local.selectedGroupId ?? globalSettings.translationEngines.local.selectedGroupId
|
|
9467
11104
|
};
|
|
11105
|
+
if (manifest.settingsKey === "localLlama") return {
|
|
11106
|
+
model: config.translation.engines.localLlama.model ?? globalSettings.translationEngines.localLlama.model ?? manifest.defaultModel,
|
|
11107
|
+
selectedGroupId: config.translation.engines.localLlama.selectedGroupId ?? globalSettings.translationEngines.localLlama.selectedGroupId
|
|
11108
|
+
};
|
|
9468
11109
|
return {
|
|
9469
11110
|
model: config.translation.engines.localCt2.model ?? globalSettings.translationEngines.localCt2.model ?? manifest.defaultModel,
|
|
9470
11111
|
selectedGroupId: config.translation.engines.localCt2.selectedGroupId ?? globalSettings.translationEngines.localCt2.selectedGroupId
|
|
@@ -9534,7 +11175,9 @@ async function probeManagedLocalRuntime(engineId) {
|
|
|
9534
11175
|
try {
|
|
9535
11176
|
if (engineId === "local") {
|
|
9536
11177
|
if (typeof (await import("@huggingface/transformers")).pipeline !== "function") throw new Error("Transformers.js did not expose a translation pipeline entry point.");
|
|
9537
|
-
} else if (
|
|
11178
|
+
} else if (engineId === "local-ct2") {
|
|
11179
|
+
if (typeof (await import("ctranslate2")).Ct2Translator !== "function") throw new Error("ctranslate2 did not expose a Ct2Translator constructor.");
|
|
11180
|
+
} else if (typeof (await import("node-llama-cpp")).getLlama !== "function") throw new Error("node-llama-cpp did not expose a getLlama entry point.");
|
|
9538
11181
|
return {
|
|
9539
11182
|
state: "ready",
|
|
9540
11183
|
message: `${manifest.label} runtime is ready.`
|
|
@@ -9685,6 +11328,14 @@ function getManagedLocalRuntimeSpec(engineId) {
|
|
|
9685
11328
|
return missing;
|
|
9686
11329
|
}
|
|
9687
11330
|
};
|
|
11331
|
+
if (engineId === "local-llama") return {
|
|
11332
|
+
packageNames: ["node-llama-cpp"],
|
|
11333
|
+
allowBuildPackages: ["node-llama-cpp"],
|
|
11334
|
+
fallbackRange: "~3.18.1",
|
|
11335
|
+
detectMissing(tree) {
|
|
11336
|
+
return hasRuntimePackageDependencyPath(tree, ["node-llama-cpp"]) ? [] : ["node-llama-cpp"];
|
|
11337
|
+
}
|
|
11338
|
+
};
|
|
9688
11339
|
return {
|
|
9689
11340
|
packageNames: ["ctranslate2"],
|
|
9690
11341
|
allowBuildPackages: ["ctranslate2"],
|
|
@@ -9698,11 +11349,9 @@ function resolveBatchTranslateModel(input, settings) {
|
|
|
9698
11349
|
if (input.model) return input.model;
|
|
9699
11350
|
if (input.engineId === "local") return settings.translationEngines.local.model;
|
|
9700
11351
|
if (input.engineId === "local-ct2") return settings.translationEngines.localCt2.model;
|
|
11352
|
+
if (input.engineId === "local-llama") return settings.translationEngines.localLlama.model;
|
|
9701
11353
|
if (input.engineId === "openai") return settings.translationEngines.openai.model;
|
|
9702
11354
|
}
|
|
9703
|
-
function isManagedLocalEngine(engineId) {
|
|
9704
|
-
return engineId === "local" || engineId === "local-ct2";
|
|
9705
|
-
}
|
|
9706
11355
|
function selectPersistedLocalPlan(state, selectedGroupId) {
|
|
9707
11356
|
if (!state) return null;
|
|
9708
11357
|
const plan = LocalModelAssetStateSchema.parse(state).plan;
|
|
@@ -10047,6 +11696,10 @@ function createServer(config) {
|
|
|
10047
11696
|
const ct2ModelIndexPath = config.runtimePaths?.localCt2ModelAssetIndexPath ?? getDefaultLocalCt2ModelIndexPath();
|
|
10048
11697
|
const ct2ModelProfileManifestPath = config.runtimePaths?.localCt2ModelProfileManifestPath ?? getDefaultLocalCt2ModelProfileManifestPath();
|
|
10049
11698
|
const ct2ModelFetchCachePath = config.runtimePaths?.localCt2ModelFetchCachePath ?? getDefaultLocalCt2ModelFetchCachePath();
|
|
11699
|
+
const llamaModelCacheDir = config.runtimePaths?.localLlamaModelCacheDir ?? getDefaultLocalLlamaModelCacheDir();
|
|
11700
|
+
const llamaModelIndexPath = config.runtimePaths?.localLlamaModelAssetIndexPath ?? getDefaultLocalLlamaModelIndexPath();
|
|
11701
|
+
const llamaModelProfileManifestPath = config.runtimePaths?.localLlamaModelProfileManifestPath ?? getDefaultLocalLlamaModelProfileManifestPath();
|
|
11702
|
+
const llamaModelFetchCachePath = config.runtimePaths?.localLlamaModelFetchCachePath ?? getDefaultLocalLlamaModelFetchCachePath();
|
|
10050
11703
|
const translationEngineService = new TranslationEngineService({
|
|
10051
11704
|
projectDir: config.projectDir,
|
|
10052
11705
|
configManager,
|
|
@@ -10056,7 +11709,10 @@ function createServer(config) {
|
|
|
10056
11709
|
localFetchCachePath: nmtModelFetchCachePath,
|
|
10057
11710
|
localCt2CacheDir: ct2ModelCacheDir,
|
|
10058
11711
|
localCt2AssetIndexPath: ct2ModelIndexPath,
|
|
10059
|
-
localCt2FetchCachePath: ct2ModelFetchCachePath
|
|
11712
|
+
localCt2FetchCachePath: ct2ModelFetchCachePath,
|
|
11713
|
+
localLlamaCacheDir: llamaModelCacheDir,
|
|
11714
|
+
localLlamaAssetIndexPath: llamaModelIndexPath,
|
|
11715
|
+
localLlamaFetchCachePath: llamaModelFetchCachePath
|
|
10060
11716
|
});
|
|
10061
11717
|
const localModelAssetService = new LocalModelAssetService({
|
|
10062
11718
|
projectDir: config.projectDir,
|
|
@@ -10075,6 +11731,14 @@ function createServer(config) {
|
|
|
10075
11731
|
profileManifestPath: ct2ModelProfileManifestPath,
|
|
10076
11732
|
fetchCachePath: ct2ModelFetchCachePath
|
|
10077
11733
|
});
|
|
11734
|
+
const localLlamaModelAssetService = new LlamaModelAssetService({
|
|
11735
|
+
projectDir: config.projectDir,
|
|
11736
|
+
globalSettingsManager,
|
|
11737
|
+
cacheDir: llamaModelCacheDir,
|
|
11738
|
+
indexPath: llamaModelIndexPath,
|
|
11739
|
+
profileManifestPath: llamaModelProfileManifestPath,
|
|
11740
|
+
fetchCachePath: llamaModelFetchCachePath
|
|
11741
|
+
});
|
|
10078
11742
|
const watcher = config.enableWatcher !== false ? new OpenSpecWatcher(config.projectDir) : void 0;
|
|
10079
11743
|
const entityReadOptionsContext = {
|
|
10080
11744
|
adapter,
|
|
@@ -10175,6 +11839,7 @@ function createServer(config) {
|
|
|
10175
11839
|
translationEngineService,
|
|
10176
11840
|
localModelAssetService,
|
|
10177
11841
|
localCt2ModelAssetService,
|
|
11842
|
+
localLlamaModelAssetService,
|
|
10178
11843
|
gitWorktreeHandoff: config.gitWorktreeHandoff,
|
|
10179
11844
|
watcher,
|
|
10180
11845
|
projectDir: config.projectDir
|
|
@@ -10199,6 +11864,7 @@ function createServer(config) {
|
|
|
10199
11864
|
translationEngineService,
|
|
10200
11865
|
localModelAssetService,
|
|
10201
11866
|
localCt2ModelAssetService,
|
|
11867
|
+
localLlamaModelAssetService,
|
|
10202
11868
|
gitWorktreeHandoff: config.gitWorktreeHandoff,
|
|
10203
11869
|
watcher,
|
|
10204
11870
|
projectDir: config.projectDir
|
|
@@ -10222,6 +11888,7 @@ function createServer(config) {
|
|
|
10222
11888
|
translationEngineService,
|
|
10223
11889
|
localModelAssetService,
|
|
10224
11890
|
localCt2ModelAssetService,
|
|
11891
|
+
localLlamaModelAssetService,
|
|
10225
11892
|
hookRuntime,
|
|
10226
11893
|
watcher,
|
|
10227
11894
|
createContext,
|
|
@@ -10322,6 +11989,7 @@ async function startServer(config, setupApp) {
|
|
|
10322
11989
|
await server.hookRuntime.dispose();
|
|
10323
11990
|
await server.createContext().localModelAssetService.close();
|
|
10324
11991
|
await server.createContext().localCt2ModelAssetService.close();
|
|
11992
|
+
await server.createContext().localLlamaModelAssetService.close();
|
|
10325
11993
|
server.translationCacheService.close();
|
|
10326
11994
|
wsServer.close();
|
|
10327
11995
|
httpServer.close();
|