@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
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { join, posix } from "node:path";
|
|
2
|
+
|
|
3
|
+
//#region ../local-llama-translator/src/factory.ts
|
|
4
|
+
const DEFAULT_SYSTEM_PROMPT = "You are a translation engine. Return only the translated text, preserve Markdown structure, inline code, URLs, and file paths.";
|
|
5
|
+
var LocalLlamaTranslatorFactory = class {
|
|
6
|
+
constructor(options = {}) {
|
|
7
|
+
this.options = options;
|
|
8
|
+
}
|
|
9
|
+
async prepare(options) {
|
|
10
|
+
const model = options.model || this.options.defaultModel;
|
|
11
|
+
if (!model) throw new Error("A GGUF model id or runtime model path is required.");
|
|
12
|
+
const module = await (this.options.loadModule ?? loadLlamaRuntimeModule)();
|
|
13
|
+
const resolvedConfig = readRuntimeConfig(options.runtimeConfig);
|
|
14
|
+
const runtimeModel = await loadRuntimeModel({
|
|
15
|
+
module,
|
|
16
|
+
model,
|
|
17
|
+
cacheDir: this.options.cacheDir,
|
|
18
|
+
runtimeConfig: resolvedConfig,
|
|
19
|
+
defaultGpuLayers: this.options.gpuLayers,
|
|
20
|
+
monitor: options.monitor
|
|
21
|
+
});
|
|
22
|
+
const context = await runtimeModel.createContext({ contextSize: resolvedConfig.contextSize ?? this.options.contextSize });
|
|
23
|
+
options.monitor?.setStatus({
|
|
24
|
+
message: `Llama model ${model} is ready.`,
|
|
25
|
+
progress: 1
|
|
26
|
+
});
|
|
27
|
+
await disposeRuntimeNode(context);
|
|
28
|
+
await disposeRuntimeNode(runtimeModel);
|
|
29
|
+
}
|
|
30
|
+
async create(options) {
|
|
31
|
+
const model = options.model || this.options.defaultModel;
|
|
32
|
+
if (!model) throw new Error("A GGUF model id or runtime model path is required.");
|
|
33
|
+
const module = await (this.options.loadModule ?? loadLlamaRuntimeModule)();
|
|
34
|
+
const resolvedConfig = readRuntimeConfig(options.runtimeConfig);
|
|
35
|
+
const runtimeModel = await loadRuntimeModel({
|
|
36
|
+
module,
|
|
37
|
+
model,
|
|
38
|
+
cacheDir: this.options.cacheDir,
|
|
39
|
+
runtimeConfig: resolvedConfig,
|
|
40
|
+
defaultGpuLayers: this.options.gpuLayers,
|
|
41
|
+
monitor: options.monitor
|
|
42
|
+
});
|
|
43
|
+
options.monitor?.setStatus({
|
|
44
|
+
message: `Llama model ${model} is ready.`,
|
|
45
|
+
progress: 1
|
|
46
|
+
});
|
|
47
|
+
return new LocalLlamaTranslator(module, runtimeModel, {
|
|
48
|
+
sourceLanguage: options.sourceLanguage,
|
|
49
|
+
targetLanguage: options.targetLanguage,
|
|
50
|
+
runtimeConfig: resolvedConfig,
|
|
51
|
+
factoryOptions: this.options,
|
|
52
|
+
model
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
function createLocalLlamaTranslatorFactory(options = {}) {
|
|
57
|
+
return new LocalLlamaTranslatorFactory(options);
|
|
58
|
+
}
|
|
59
|
+
var LocalLlamaTranslator = class {
|
|
60
|
+
constructor(module, model, options) {
|
|
61
|
+
this.module = module;
|
|
62
|
+
this.model = model;
|
|
63
|
+
this.options = options;
|
|
64
|
+
}
|
|
65
|
+
async *batchTranslate(inputs, options) {
|
|
66
|
+
for (const [index, input] of inputs.entries()) {
|
|
67
|
+
throwIfAborted(options?.signal);
|
|
68
|
+
const context = await this.model.createContext({ contextSize: this.options.runtimeConfig.contextSize ?? this.options.factoryOptions.contextSize });
|
|
69
|
+
try {
|
|
70
|
+
const session = new this.module.LlamaChatSession({
|
|
71
|
+
contextSequence: context.getSequence(),
|
|
72
|
+
systemPrompt: this.options.runtimeConfig.systemPrompt ?? this.options.factoryOptions.systemPrompt ?? DEFAULT_SYSTEM_PROMPT
|
|
73
|
+
});
|
|
74
|
+
try {
|
|
75
|
+
const output = await session.prompt(buildTranslationPrompt({
|
|
76
|
+
sourceLanguage: this.options.sourceLanguage,
|
|
77
|
+
targetLanguage: this.options.targetLanguage,
|
|
78
|
+
text: input,
|
|
79
|
+
instructions: options?.instructions,
|
|
80
|
+
context: options?.context
|
|
81
|
+
}));
|
|
82
|
+
throwIfAborted(options?.signal);
|
|
83
|
+
yield {
|
|
84
|
+
index,
|
|
85
|
+
output: output.trim()
|
|
86
|
+
};
|
|
87
|
+
} finally {
|
|
88
|
+
await disposeRuntimeNode(session);
|
|
89
|
+
}
|
|
90
|
+
} finally {
|
|
91
|
+
await disposeRuntimeNode(context);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
destroy() {
|
|
96
|
+
disposeRuntimeNode(this.model);
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
async function loadRuntimeModel(input) {
|
|
100
|
+
input.monitor?.setStatus({ message: `Loading llama model ${input.model}.` });
|
|
101
|
+
return (await input.module.getLlama()).loadModel({
|
|
102
|
+
modelPath: resolveModelPath({
|
|
103
|
+
model: input.model,
|
|
104
|
+
cacheDir: input.cacheDir,
|
|
105
|
+
runtimeConfig: input.runtimeConfig
|
|
106
|
+
}),
|
|
107
|
+
gpuLayers: input.runtimeConfig.gpuLayers ?? input.defaultGpuLayers
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
function buildTranslationPrompt(input) {
|
|
111
|
+
const sections = [`Translate the following text from ${input.sourceLanguage} to ${input.targetLanguage}.`, "Return only the translated text."];
|
|
112
|
+
if (input.instructions?.trim()) sections.push(`Additional instructions:\n${input.instructions.trim()}`);
|
|
113
|
+
if (input.context?.trim()) sections.push(`Translation context:\n${input.context.trim()}`);
|
|
114
|
+
sections.push(`Text:\n${input.text}`);
|
|
115
|
+
return sections.join("\n\n");
|
|
116
|
+
}
|
|
117
|
+
async function loadLlamaRuntimeModule() {
|
|
118
|
+
return await import("node-llama-cpp");
|
|
119
|
+
}
|
|
120
|
+
function resolveModelPath(input) {
|
|
121
|
+
if (input.runtimeConfig.modelPath) return input.runtimeConfig.modelPath;
|
|
122
|
+
if (input.cacheDir) return join(input.cacheDir, "models", input.model);
|
|
123
|
+
return input.model;
|
|
124
|
+
}
|
|
125
|
+
function readRuntimeConfig(runtimeConfig) {
|
|
126
|
+
return {
|
|
127
|
+
modelPath: readString(runtimeConfig, "modelPath"),
|
|
128
|
+
contextSize: readNumber(runtimeConfig, "contextSize"),
|
|
129
|
+
gpuLayers: readNumber(runtimeConfig, "gpuLayers"),
|
|
130
|
+
systemPrompt: readString(runtimeConfig, "systemPrompt")
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
function readString(record, key) {
|
|
134
|
+
const value = record?.[key];
|
|
135
|
+
return typeof value === "string" && value.trim().length > 0 ? value : void 0;
|
|
136
|
+
}
|
|
137
|
+
function readNumber(record, key) {
|
|
138
|
+
const value = record?.[key];
|
|
139
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
140
|
+
}
|
|
141
|
+
async function disposeRuntimeNode(value) {
|
|
142
|
+
await value?.dispose?.();
|
|
143
|
+
}
|
|
144
|
+
function throwIfAborted(signal) {
|
|
145
|
+
if (signal?.aborted) throw new DOMException("The operation was aborted.", "AbortError");
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
//#endregion
|
|
149
|
+
//#region ../local-llama-translator/src/gguf-download-plan.ts
|
|
150
|
+
function resolveGgufModelDownloadPlanFromRepositoryFiles(input) {
|
|
151
|
+
const groups = dedupeFiles(input.files.filter((file) => file.path.trim().length > 0).map((file) => ({
|
|
152
|
+
...file,
|
|
153
|
+
path: normalizePath(file.path)
|
|
154
|
+
}))).filter((file) => file.path.toLowerCase().endsWith(".gguf")).map((file) => createGroup(file));
|
|
155
|
+
if (groups.length === 0) return null;
|
|
156
|
+
const selectedGroup = selectRequestedGroup(groups, input.selectedGroupId) ?? selectPreferredSelectableGroup(groups);
|
|
157
|
+
const selectedGroupId = selectedGroup?.id;
|
|
158
|
+
return {
|
|
159
|
+
modelId: input.modelId,
|
|
160
|
+
estimatedTotalBytes: selectedGroup?.estimatedTotalBytes,
|
|
161
|
+
files: selectedGroup?.files ?? [],
|
|
162
|
+
selectedGroupId,
|
|
163
|
+
groups: groups.map((group) => ({
|
|
164
|
+
...group,
|
|
165
|
+
selected: group.id === selectedGroupId,
|
|
166
|
+
files: [...group.files]
|
|
167
|
+
}))
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
function createGroup(file) {
|
|
171
|
+
const planFile = toPlanFile(file);
|
|
172
|
+
const baseGroupId = stripGgufExtension(posix.basename(file.path));
|
|
173
|
+
return {
|
|
174
|
+
id: file.path,
|
|
175
|
+
baseGroupId,
|
|
176
|
+
label: baseGroupId,
|
|
177
|
+
description: `GGUF runtime file from ${file.path}.`,
|
|
178
|
+
estimatedTotalBytes: file.sizeBytes,
|
|
179
|
+
selectable: typeof file.sizeBytes === "number" && file.sizeBytes > 0,
|
|
180
|
+
selected: false,
|
|
181
|
+
files: [planFile]
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
function toPlanFile(file) {
|
|
185
|
+
return {
|
|
186
|
+
path: file.path,
|
|
187
|
+
sizeBytes: file.sizeBytes,
|
|
188
|
+
required: true,
|
|
189
|
+
etag: file.etag,
|
|
190
|
+
revision: file.revision,
|
|
191
|
+
sourceUrl: file.sourceUrl,
|
|
192
|
+
raw: file.raw
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
function selectRequestedGroup(groups, selectedGroupId) {
|
|
196
|
+
if (!selectedGroupId) return null;
|
|
197
|
+
return groups.find((group) => group.selectable && (group.id === selectedGroupId || group.baseGroupId === selectedGroupId)) ?? null;
|
|
198
|
+
}
|
|
199
|
+
function selectPreferredSelectableGroup(groups) {
|
|
200
|
+
const selectableGroups = groups.filter((group) => group.selectable && group.estimatedTotalBytes !== void 0);
|
|
201
|
+
if (selectableGroups.length === 0) return null;
|
|
202
|
+
return selectableGroups.sort((left, right) => {
|
|
203
|
+
const compatibilityDelta = scorePreferredLlamaGroup(right.baseGroupId ?? right.id) - scorePreferredLlamaGroup(left.baseGroupId ?? left.id);
|
|
204
|
+
if (compatibilityDelta !== 0) return compatibilityDelta;
|
|
205
|
+
return (left.estimatedTotalBytes ?? Number.POSITIVE_INFINITY) - (right.estimatedTotalBytes ?? Number.POSITIVE_INFINITY) || left.id.localeCompare(right.id);
|
|
206
|
+
})[0] ?? null;
|
|
207
|
+
}
|
|
208
|
+
function scorePreferredLlamaGroup(groupId) {
|
|
209
|
+
const normalized = groupId.toUpperCase();
|
|
210
|
+
if (normalized.includes("Q4_K_M")) return 5;
|
|
211
|
+
if (normalized.includes("Q4_K_S")) return 4;
|
|
212
|
+
if (normalized.includes("Q5_K_M")) return 3;
|
|
213
|
+
if (normalized.includes("Q5_K_S")) return 2;
|
|
214
|
+
if (normalized.includes("Q6_K")) return 1;
|
|
215
|
+
if (normalized.includes("IQ1") || normalized.includes("IQ2") || normalized.includes("IQ3")) return -2;
|
|
216
|
+
if (normalized.includes("TQ1") || normalized.includes("TQ2") || normalized.includes("1.25BIT")) return -3;
|
|
217
|
+
return 0;
|
|
218
|
+
}
|
|
219
|
+
function stripGgufExtension(value) {
|
|
220
|
+
return value.replace(/\.gguf$/iu, "");
|
|
221
|
+
}
|
|
222
|
+
function normalizePath(input) {
|
|
223
|
+
return input.replace(/^\.\/+/u, "").replace(/\/+/gu, "/");
|
|
224
|
+
}
|
|
225
|
+
function dedupeFiles(files) {
|
|
226
|
+
const deduped = /* @__PURE__ */ new Map();
|
|
227
|
+
for (const file of files) deduped.set(file.path, file);
|
|
228
|
+
return [...deduped.values()];
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
//#endregion
|
|
232
|
+
export { LocalLlamaTranslatorFactory as n, createLocalLlamaTranslatorFactory as r, resolveGgufModelDownloadPlanFromRepositoryFiles as t };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openspecui/server",
|
|
3
|
-
"version": "3.11.
|
|
3
|
+
"version": "3.11.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.mjs",
|
|
6
6
|
"exports": {
|
|
@@ -24,8 +24,8 @@
|
|
|
24
24
|
"@hono/node-server": "^1.14.1",
|
|
25
25
|
"@huggingface/hub": "^2.12.0",
|
|
26
26
|
"@lydell/node-pty": "^1.1.0",
|
|
27
|
-
"@openspecui/core": "3.11.
|
|
28
|
-
"@openspecui/search": "3.11.
|
|
27
|
+
"@openspecui/core": "3.11.3",
|
|
28
|
+
"@openspecui/search": "3.11.3",
|
|
29
29
|
"@trpc/server": "^11.0.0",
|
|
30
30
|
"better-sqlite3": "^12.5.0",
|
|
31
31
|
"hono": "^4.7.3",
|
|
@@ -34,10 +34,11 @@
|
|
|
34
34
|
"zod": "^3.24.1"
|
|
35
35
|
},
|
|
36
36
|
"// runtime-note": "These heavy runtimes are owned by the host package because translation engine installation is lifecycle-managed at the host boundary.",
|
|
37
|
-
"// bundle-note": "The translator engine packages keep runtime imports external in tsdown output, so this host package remains the install/detect truth during development runs.",
|
|
37
|
+
"// bundle-note": "The translator engine packages keep heavyweight runtime imports external in tsdown output, so this host package remains the install/detect truth during development runs even when the translator adapters are bundled.",
|
|
38
38
|
"optionalDependencies": {
|
|
39
39
|
"@huggingface/transformers": "~4.2.0",
|
|
40
|
-
"ctranslate2": "0.1.0"
|
|
40
|
+
"ctranslate2": "0.1.0",
|
|
41
|
+
"node-llama-cpp": "~3.18.1"
|
|
41
42
|
},
|
|
42
43
|
"devDependencies": {
|
|
43
44
|
"@trpc/client": "^11.7.2",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|