dsh-think-translate 1.2.0 → 1.2.1
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/lib/client.js +40 -9
- package/lib/index.js +54 -13
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -23,7 +23,7 @@ window.__ModuleLoader__.load({
|
|
|
23
23
|
// there yet (an older process still serving the port), and the contract test
|
|
24
24
|
// pins PLUGIN_VERSION to package.json so the two can never drift.
|
|
25
25
|
var PLUGIN_NAME = "dsh-think-translate";
|
|
26
|
-
var PLUGIN_VERSION = "1.2.
|
|
26
|
+
var PLUGIN_VERSION = "1.2.1";
|
|
27
27
|
var PLUGIN_REPO = "https://github.com/UncleK/dsh-think-translate";
|
|
28
28
|
// The official MarkdownText contract changed in 0.1.2-alpha: it now takes a
|
|
29
29
|
// nested `labels` object and dereferences it without defaults inside its code
|
|
@@ -1984,6 +1984,18 @@ window.__ModuleLoader__.load({
|
|
|
1984
1984
|
})
|
|
1985
1985
|
.catch(function (e) { setMsg(t.connFail + ": " + (e && e.message ? e.message : e)); });
|
|
1986
1986
|
};
|
|
1987
|
+
// Where the host looked for the harness settings, and what it found there:
|
|
1988
|
+
// an empty scan used to be a dead end, so the panel now names the file it
|
|
1989
|
+
// read (the usual cause is a home directory the plugin guessed wrong).
|
|
1990
|
+
var dshScanNote = function () {
|
|
1991
|
+
var d = cfg && cfg.dshScan;
|
|
1992
|
+
if (!d || !d.home) return null;
|
|
1993
|
+
var where = d.home + "/settings.yaml";
|
|
1994
|
+
var detail = d.settingsFound
|
|
1995
|
+
? (d.providers && d.providers.length ? d.providers.join(", ") : t.dshImportEmpty)
|
|
1996
|
+
: (t.dshImportEmpty + " · " + where);
|
|
1997
|
+
return createElement("div", { className: "xl-note", style: { marginTop: "4px" } }, detail);
|
|
1998
|
+
};
|
|
1987
1999
|
var addAllDsh = function () {
|
|
1988
2000
|
var next = chain.slice();
|
|
1989
2001
|
dshAvailable.forEach(function (id) { if (next.indexOf(id) < 0) next.push(id); });
|
|
@@ -2127,17 +2139,15 @@ window.__ModuleLoader__.load({
|
|
|
2127
2139
|
})),
|
|
2128
2140
|
createElement("label", { className: "xl-form-row" },
|
|
2129
2141
|
createElement("span", { className: "xl-form-label" }, t.providerApiKey),
|
|
2130
|
-
//
|
|
2131
|
-
//
|
|
2132
|
-
//
|
|
2133
|
-
|
|
2142
|
+
// No env badge here: the apiKeyEnv a preset fills in is an
|
|
2143
|
+
// implementation detail of that endpoint, and the saved row shows an
|
|
2144
|
+
// env:NAME badge anyway. Typing a key clears the env name (below), so
|
|
2145
|
+
// the key the user can see is always the one that wins.
|
|
2134
2146
|
createElement("input", {
|
|
2135
2147
|
className: "xl-cfg", placeholder: t.providerApiKey, value: formApiKey,
|
|
2136
2148
|
onChange: function (e) {
|
|
2137
2149
|
var v = e.target.value;
|
|
2138
2150
|
setFormApiKey(v);
|
|
2139
|
-
// Typing a key means "use this key": drop the env name, or the
|
|
2140
|
-
// host would keep preferring the environment value.
|
|
2141
2151
|
if (v.trim() !== "") setFormApiKeyEnv("");
|
|
2142
2152
|
}
|
|
2143
2153
|
})),
|
|
@@ -2181,7 +2191,9 @@ window.__ModuleLoader__.load({
|
|
|
2181
2191
|
"+ " + t.dshImport + (dshAvailable.length ? " · " + dshAvailable.length : "")),
|
|
2182
2192
|
dshMenu ? createElement("div", { className: "xl-dsh-menu" },
|
|
2183
2193
|
dshAvailable.length === 0
|
|
2184
|
-
? createElement("div",
|
|
2194
|
+
? createElement("div", null,
|
|
2195
|
+
createElement("div", { className: "xl-note" }, t.dshImportEmpty),
|
|
2196
|
+
dshScanNote())
|
|
2185
2197
|
: createElement("div", null,
|
|
2186
2198
|
dshAvailable.length > 1 ? createElement("button", {
|
|
2187
2199
|
className: "xl-btn", type: "button", style: { marginBottom: "4px" }, onClick: addAllDsh
|
|
@@ -2219,6 +2231,23 @@ window.__ModuleLoader__.load({
|
|
|
2219
2231
|
// that node, and taking it out of the tree behind React's back means React
|
|
2220
2232
|
// can later patch or remove a detached node (NotFoundError on removeChild).
|
|
2221
2233
|
// A sibling in the same slot looks identical and leaves React's tree intact.
|
|
2234
|
+
function shellNavIcon(btn) {
|
|
2235
|
+
// The SHELL's svg, never ours: once the holder is in place it comes first
|
|
2236
|
+
// in document order, so a plain querySelector('svg') returns the book — and
|
|
2237
|
+
// the next pass then hides the plugin's own glyph. That is exactly how the
|
|
2238
|
+
// nav icon disappeared after the first pass (fixed in 1.2.1).
|
|
2239
|
+
var kids = btn.children || [];
|
|
2240
|
+
for (var i = 0; i < kids.length; i++) {
|
|
2241
|
+
var tag = (kids[i].tagName || "").toLowerCase();
|
|
2242
|
+
if (tag === "svg" && !kids[i].hasAttribute("data-xl-nav-glyph-svg")) return kids[i];
|
|
2243
|
+
}
|
|
2244
|
+
var nested = btn.querySelectorAll ? btn.querySelectorAll("svg") : [];
|
|
2245
|
+
for (var j = 0; j < nested.length; j++) {
|
|
2246
|
+
if (!nested[j].hasAttribute("data-xl-nav-glyph-svg")) return nested[j];
|
|
2247
|
+
}
|
|
2248
|
+
return null;
|
|
2249
|
+
}
|
|
2250
|
+
|
|
2222
2251
|
function decorateSettingsGlyph() {
|
|
2223
2252
|
if (typeof document === "undefined") return;
|
|
2224
2253
|
var st = UI_TEXT[store.lang] || UI_TEXT.en;
|
|
@@ -2231,7 +2260,7 @@ window.__ModuleLoader__.load({
|
|
|
2231
2260
|
var btn = buttons[j];
|
|
2232
2261
|
var label = btn.lastElementChild;
|
|
2233
2262
|
if (!label || (label.textContent || "").trim() !== title) continue;
|
|
2234
|
-
var svg = btn
|
|
2263
|
+
var svg = shellNavIcon(btn);
|
|
2235
2264
|
if (!svg || !svg.parentNode) continue;
|
|
2236
2265
|
if (!btn.querySelector('[data-xl-nav-glyph="1"]')) {
|
|
2237
2266
|
var holder = document.createElement("span");
|
|
@@ -2242,6 +2271,8 @@ window.__ModuleLoader__.load({
|
|
|
2242
2271
|
if (svgClass) holder.setAttribute("class", svgClass);
|
|
2243
2272
|
holder.style.display = "inline-flex";
|
|
2244
2273
|
holder.innerHTML = NAV_GLYPH_SVG;
|
|
2274
|
+
var inner = holder.querySelector ? holder.querySelector("svg") : null;
|
|
2275
|
+
if (inner) inner.setAttribute("data-xl-nav-glyph-svg", "1");
|
|
2245
2276
|
svg.parentNode.insertBefore(holder, svg);
|
|
2246
2277
|
}
|
|
2247
2278
|
// Self-healing: the shell may re-render its icon, and the decoration
|
package/lib/index.js
CHANGED
|
@@ -22,13 +22,40 @@ export const inject = ['webServer', 'subprocess', 'timer']
|
|
|
22
22
|
const CONFIG_PATH = fileURLToPath(new URL('./config.json', import.meta.url))
|
|
23
23
|
const PACKAGE_PATH = fileURLToPath(new URL('../package.json', import.meta.url))
|
|
24
24
|
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
25
|
+
// Where the harness keeps its settings.yaml and .credentials.yaml.
|
|
26
|
+
//
|
|
27
|
+
// This mirrors DSH's own resolution — `$DSH_HOME`, then `~/.dsh` (see
|
|
28
|
+
// @deepseek-ai/dsh-home-paths: an explicit path, then DSH_HOME, then
|
|
29
|
+
// homedir()/.dsh, with a blank DSH_HOME treated as unset) — and only then the
|
|
30
|
+
// desktop-app layouts. Picking one guess was the bug: an install whose home is
|
|
31
|
+
// ~/.dsh fell through to %APPDATA%\dsh-desktop\harness, which does not exist
|
|
32
|
+
// there, so discovery silently reported "no provider configured" while the
|
|
33
|
+
// settings file sat right there. The candidates are resolved per call and the
|
|
34
|
+
// first one that actually holds a settings.yaml wins (see resolveHarnessHome).
|
|
35
|
+
function harnessHomeCandidates() {
|
|
36
|
+
const out = []
|
|
37
|
+
const env = process.env.DSH_HOME
|
|
38
|
+
if (typeof env === 'string' && env.trim()) out.push(env.trim())
|
|
39
|
+
const home = process.env.HOME || process.env.USERPROFILE || ''
|
|
40
|
+
if (home) {
|
|
41
|
+
out.push(home.replace(/[\\/]+$/, '') + '/.dsh')
|
|
42
|
+
if (process.platform === 'darwin') out.push(home.replace(/[\\/]+$/, '') + '/Library/Application Support/dsh-desktop/harness')
|
|
43
|
+
}
|
|
44
|
+
if (process.env.APPDATA) out.push(process.env.APPDATA.replace(/[\\/]+$/, '') + '\\dsh-desktop\\harness')
|
|
45
|
+
return out
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// The first candidate holding a settings.yaml, else the first candidate at all
|
|
49
|
+
// (so the diagnostic can say which one was looked at).
|
|
50
|
+
function pickHarnessHome(candidates, exists) {
|
|
51
|
+
for (const dir of candidates) {
|
|
52
|
+
try { if (exists(dir + '/settings.yaml')) return { home: dir, settingsFound: true } } catch (e) { /* keep looking */ }
|
|
53
|
+
}
|
|
54
|
+
return { home: candidates[0] || '', settingsFound: false }
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Diagnostic for the settings panel: which home was used and what was read there.
|
|
58
|
+
let dshScanDiag = { home: null, settingsFound: false, credsFound: false, providers: [] }
|
|
32
59
|
|
|
33
60
|
// Defaults: local model (Ollama) enabled from the start — the chain is
|
|
34
61
|
// fail-open, so if Ollama is down it falls back to google/bing. `chain` is the
|
|
@@ -777,12 +804,17 @@ let dshScanOk = false
|
|
|
777
804
|
async function discoverDshProviders() {
|
|
778
805
|
const fs = await import('node:fs')
|
|
779
806
|
const out = {}
|
|
807
|
+
const candidates = harnessHomeCandidates()
|
|
808
|
+
const picked = pickHarnessHome(candidates, function (p) { return fs.existsSync(p) })
|
|
809
|
+
const home = picked.home
|
|
810
|
+
const diag = { home: home, candidates: candidates, settingsFound: picked.settingsFound, credsFound: false, providers: [], error: null }
|
|
780
811
|
try {
|
|
781
|
-
const settingsPath =
|
|
782
|
-
const credsPath =
|
|
812
|
+
const settingsPath = home + '/settings.yaml'
|
|
813
|
+
const credsPath = home + '/.credentials.yaml'
|
|
783
814
|
let settingsTxt = '', credsTxt = ''
|
|
784
815
|
try { settingsTxt = fs.readFileSync(settingsPath, 'utf8') } catch (e) {}
|
|
785
816
|
try { credsTxt = fs.readFileSync(credsPath, 'utf8') } catch (e) {}
|
|
817
|
+
diag.credsFound = credsTxt.length > 0
|
|
786
818
|
const providers = extractDshProviders(settingsTxt)
|
|
787
819
|
const refs = extractCredRefs(credsTxt)
|
|
788
820
|
dshScanOk = /llm-pi-ai:[\s\S]*?\n[ \t]{2}providers:/m.test(settingsTxt)
|
|
@@ -792,7 +824,7 @@ async function discoverDshProviders() {
|
|
|
792
824
|
for (const k of Object.keys(refs)) credRefsCache.set(k, refs[k])
|
|
793
825
|
for (const name of Object.keys(providers)) {
|
|
794
826
|
const p = providers[name]
|
|
795
|
-
if (!p.baseURL || !p.model) continue
|
|
827
|
+
if (!p.baseURL || !p.model) { diag.skipped = (diag.skipped || []).concat(name); continue }
|
|
796
828
|
const entry = {
|
|
797
829
|
type: 'openai',
|
|
798
830
|
source: 'dsh',
|
|
@@ -802,8 +834,12 @@ async function discoverDshProviders() {
|
|
|
802
834
|
}
|
|
803
835
|
if (p.apiKeyEnv) entry.apiKeyEnv = p.apiKeyEnv
|
|
804
836
|
out[name] = entry
|
|
837
|
+
diag.providers.push(name)
|
|
805
838
|
}
|
|
806
|
-
} catch (e) {
|
|
839
|
+
} catch (e) {
|
|
840
|
+
diag.error = e instanceof Error ? e.message : String(e)
|
|
841
|
+
}
|
|
842
|
+
dshScanDiag = diag
|
|
807
843
|
return out
|
|
808
844
|
}
|
|
809
845
|
|
|
@@ -1374,8 +1410,12 @@ export function apply(ctx) {
|
|
|
1374
1410
|
const cfg = await loadConfig()
|
|
1375
1411
|
await saveConfig(cfg)
|
|
1376
1412
|
// The fresh merge is what fills in resolved credentials, so this route
|
|
1377
|
-
// is the one that most needs the sanitizer.
|
|
1378
|
-
|
|
1413
|
+
// is the one that most needs the sanitizer. `dshScan` rides along (on the
|
|
1414
|
+
// response copy only, never saved) so the panel can explain an empty scan
|
|
1415
|
+
// instead of just saying "no provider configured".
|
|
1416
|
+
const out = stripResolvedKeys(cfg)
|
|
1417
|
+
out.dshScan = dshScanDiag
|
|
1418
|
+
sendJson(res, 200, out)
|
|
1379
1419
|
} catch (e) {
|
|
1380
1420
|
sendJson(res, 500, { error: e instanceof Error ? e.message : String(e) })
|
|
1381
1421
|
}
|
|
@@ -1395,4 +1435,5 @@ export {
|
|
|
1395
1435
|
needsTranslate, hasProse, chunkText, fnv, deepMerge,
|
|
1396
1436
|
structuredCloneSafe, ADAPTERS,
|
|
1397
1437
|
resolveApiKey, stripResolvedKeys, missingApiKey, clearNullFields, handleVersion,
|
|
1438
|
+
harnessHomeCandidates, pickHarnessHome,
|
|
1398
1439
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-think-translate",
|
|
3
3
|
"description": "Display-layer translation for the DSH web app: thinking chain, task cards, and optional answer text, in 8 target languages (local Ollama / any OpenAI-compatible or Anthropic endpoint / Google / Bing, fail-open)",
|
|
4
|
-
"version": "1.2.
|
|
4
|
+
"version": "1.2.1",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"exports": {
|