@objectivex666/dsh-settings-search 1.13.0 → 1.13.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/README.en.md CHANGED
@@ -17,7 +17,7 @@
17
17
  - 🔍 **Live search** — a search box in the settings panel filters every registered settings page (`settings.section`) and general item (`settings.general.item`) as you type.
18
18
  - 🧩 **Three search styles** — choose the classic sidebar, window top bar, or popup search page. The choice is saved in the current browser.
19
19
  - ⌨️ **Search shortcut (v1.10.0)** — focus the search box with `Ctrl+F` or `⌘F` inside the settings panel. You can enable or disable it from the "Settings Search" page; disabling keeps the browser's default find behavior.
20
- - 🤝 **Import DSH model config (v1.9.0)** — import the Base URL and model name from DSH's current default model in the AI config section. For credential safety, the API key is never imported.
20
+ - 🤝 **Import DSH model config (v1.9.0)** — import the Base URL and model name from DSH's current default model in the AI config section. When that default is a built-in DeepSeek route (whose settings carry no Base URL), the import uses DeepSeek's own endpoint, `https://api.deepseek.com/anthropic`. For credential safety, the API key is never imported.
21
21
  - 🎯 **First-run onboarding (v1.9.0)** — a welcome modal appears only the first time the settings panel opens after installation. It introduces the four search styles, pinyin/intent/AI hints, click-to-jump, and auto-indexing; there are no "Don't show again" or manual reopen buttons.
22
22
  - 📂 **In-page option search (v1.2.0)** — search beyond pages and reach the specific options inside them: Plugins-page tabs (`settings.plugins.tab`), the pre-0.2 plugin-card seats (`settings.plugin.item` / `web-ui.plugin.item`, both gone in 0.2), general rows, and more, each shown with a "page › option" breadcrumb.
23
23
  - 🔤 **Pinyin association search (v1.5.0)** — type initials (`sz`) or full pinyin (`shezhi`) to surface Chinese settings items; navigate with `↑`/`↓`, confirm with `Enter`, dismiss with `Esc`, and matched text is highlighted.
@@ -128,7 +128,8 @@ npm test # structural checks and mocked Ollama / OpenAI / Anthropic regression
128
128
  Issues are welcome — please report bugs, suggest features, or ask questions in
129
129
  [GitHub Issues](https://github.com/objectivex666/dsh-settings-search/issues).
130
130
  Pull requests are equally welcome via
131
- [GitHub Pull Requests](https://github.com/objectivex666/dsh-settings-search/pulls).
131
+ [GitHub Pull Requests](https://github.com/objectivex666/dsh-settings-search/pulls) —
132
+ please read [CONTRIBUTING.en.md](./CONTRIBUTING.en.md) first (layout, conventions, tests, and the release flow).
132
133
 
133
134
  When submitting an issue, please include as much of the following as possible so we can locate and reproduce it:
134
135
 
package/README.md CHANGED
@@ -17,7 +17,7 @@
17
17
  - 🔍 **实时搜索**:在设置面板中嵌入搜索框,输入关键词立即过滤所有已注册的设置页(`settings.section`)与通用设置项(`settings.general.item`)。
18
18
  - 🧩 **三种搜索样式**:可选择经典侧栏、窗口顶栏或弹出搜索页;选择会保存在当前浏览器。
19
19
  - ⌨️ **搜索快捷键(v1.10.0)**:在设置面板内按 `Ctrl+F` 或 `⌘F` 聚焦搜索框;可在「设置搜索」页启用或关闭,关闭后保留浏览器默认查找。
20
- - 🤝 **导入 DSH 模型配置(v1.9.0)**:在 AI 配置区一键读取 DSH 当前默认模型的 Base URL 与模型名;为避免凭据扩散,导入始终不包含 API Key。
20
+ - 🤝 **导入 DSH 模型配置(v1.9.0)**:在 AI 配置区一键读取 DSH 当前默认模型的 Base URL 与模型名;默认模型是 DSH 内置的 DeepSeek 路由时(设置里不含 Base URL),导入 DeepSeek 的接口地址 `https://api.deepseek.com/anthropic`。为避免凭据扩散,导入始终不包含 API Key。
21
21
  - 🎯 **新用户引导(v1.9.0)**:仅在安装后首次打开设置面板时弹出引导弹窗,介绍四种搜索样式、拼音/意图/AI 联想、点击即达与自动导入索引等核心功能;不再提供“不再显示”和手动重开按钮。
22
22
  - 📂 **页内选项搜索(v1.2.0)**:不只搜页面,还能直达每个设置页内部的具体选项——插件页的标签页(`settings.plugins.tab`)、旧版的插件卡片座位(`settings.plugin.item` / `web-ui.plugin.item`,0.2 已移除)、通用设置行等,结果以「页面 › 选项」面包屑展示。
23
23
  - 🔤 **拼音联想搜索(v1.5.0)**:输入拼音首字母(如 `sz`)或全拼(如 `shezhi`)即可联想中文设置项;支持 `↑`/`↓` 选择、`Enter` 跳转、`Esc` 关闭,命中的文字自动高亮。
@@ -127,7 +127,8 @@ npm test # 结构检查及 Ollama / OpenAI / Anthropic 模拟接口回归测
127
127
 
128
128
  欢迎提交 **Issue** 反馈 Bug、功能建议或使用疑问:
129
129
  [GitHub Issues](https://github.com/objectivex666/dsh-settings-search/issues),
130
- 也欢迎直接发起 [Pull Request](https://github.com/objectivex666/dsh-settings-search/pulls)。
130
+ 也欢迎直接发起 [Pull Request](https://github.com/objectivex666/dsh-settings-search/pulls)——
131
+ 动手前请先读一遍 [贡献指南](./CONTRIBUTING.md)(结构、代码约定、测试与发布流程)。
131
132
 
132
133
  提交 Issue 时,请尽量完整地提供以下信息,便于定位与复现:
133
134
 
package/lib/client.js CHANGED
@@ -30,7 +30,7 @@ window.__ModuleLoader__.load({
30
30
 
31
31
  const NS = 'settings-search-v5'
32
32
  /** Single source of truth is package.json — scripts/check.mjs asserts this matches. */
33
- const PKG_VERSION = '1.13.0'
33
+ const PKG_VERSION = '1.13.1'
34
34
  const NPM_NAME = '@objectivex666/dsh-settings-search'
35
35
  const REPO_URL = 'https://github.com/objectivex666/dsh-settings-search'
36
36
  const ISSUES_URL = 'https://github.com/objectivex666/dsh-settings-search/issues/new/choose'
@@ -107,6 +107,7 @@ window.__ModuleLoader__.load({
107
107
  aiDshImportOk: '已导入 Base URL 与模型名;API Key 未改动 ✓',
108
108
  aiDshImportFail: '导入失败:{reason}',
109
109
  aiDshImportEmpty: '未找到 DSH 当前默认模型配置',
110
+ aiDshImportBuiltin: '已导入 DeepSeek 内置地址与模型名;API Key 未改动,插件内使用需填写自己的 DeepSeek API Key',
110
111
  aiDshImportApi: '当前 DSH 版本未提供模型配置接口(remote.settings)',
111
112
  aiDshImportNoBaseUrl: '默认模型「{model}」在 DSH 设置中没有可导入的 Base URL',
112
113
  aiConfigSave: '保存配置',
@@ -253,6 +254,7 @@ window.__ModuleLoader__.load({
253
254
  aiDshImportOk: 'Imported Base URL and model; API key unchanged ✓',
254
255
  aiDshImportFail: 'Import failed: {reason}',
255
256
  aiDshImportEmpty: 'No current DSH default model config found',
257
+ aiDshImportBuiltin: 'Imported the built-in DeepSeek address and model; the API key is unchanged, so fill in your own DeepSeek key to use it here',
256
258
  aiDshImportApi: 'This DSH build exposes no model config API (remote.settings)',
257
259
  aiDshImportNoBaseUrl: 'The default model "{model}" has no Base URL in DSH settings to import',
258
260
  aiConfigSave: 'Save config',
@@ -1922,15 +1924,36 @@ window.__ModuleLoader__.load({
1922
1924
  return null
1923
1925
  }
1924
1926
 
1927
+ /** DSH's built-in DeepSeek routes and the namespaces that own them. */
1928
+ const DEEPSEEK_PROVIDERS = new Set(['deepseek-official', 'deepseek-account'])
1929
+ const DEEPSEEK_SETTINGS = new Set(['llm-deepseek', 'llm-deepseek-api-key', 'llm-deepseek-account'])
1930
+ /** The endpoint the shipped DeepSeek adapter falls back to (`PUBLIC_BASE_URL`). */
1931
+ const DEEPSEEK_PUBLIC_BASE_URL = 'https://api.deepseek.com/anthropic'
1932
+ /**
1933
+ * Whether one provider route is a built-in DeepSeek one.
1934
+ * @param providerId - the route's provider id.
1935
+ * @param settingsNs - the settings namespace the route reads, when known.
1936
+ * @returns true for the shipped DeepSeek routes.
1937
+ */
1938
+ const isBuiltInDeepseekRoute = (providerId, settingsNs) =>
1939
+ DEEPSEEK_PROVIDERS.has(String(providerId ?? '')) || DEEPSEEK_SETTINGS.has(String(settingsNs ?? ''))
1940
+
1925
1941
  /**
1926
1942
  * Read the model fields of DSH's current default model; credentials are
1927
1943
  * never requested. `agent-default-model` names the provider and the model,
1928
1944
  * the provider's own settings namespace carries the Base URL, and the model
1929
1945
  * profile inside that namespace may override the wire protocol.
1930
1946
  *
1947
+ * A built-in DeepSeek route keeps its endpoint in the adapter rather than in
1948
+ * settings, so nothing is importable from the namespace there. Those routes
1949
+ * fall back to the endpoint DSH itself calls — the adapter resolves
1950
+ * `config.baseURL ?? $DEEPSEEK_BASE_URL ?? https://api.deepseek.com/anthropic`,
1951
+ * and only the last of those is visible to a browser — so importing the
1952
+ * default DeepSeek model still yields a usable address.
1953
+ *
1931
1954
  * @param ctx - the client plugin context.
1932
1955
  * @param t - translate function for the failure messages.
1933
- * @returns `{ provider, baseUrl, model }`, or null when DSH has no default model.
1956
+ * @returns `{ provider, baseUrl, model, builtin? }`, or null when DSH has no default model.
1934
1957
  * @throws when this build exposes no settings API, or the default model has no Base URL.
1935
1958
  */
1936
1959
  const readDshModelConfig = async (ctx, t) => {
@@ -1951,8 +1974,19 @@ window.__ModuleLoader__.load({
1951
1974
  : modelNamespace?.value?.providers?.[providerId] ?? {}
1952
1975
  const configuredModels = Array.isArray(providerProfile.models) ? providerProfile.models : []
1953
1976
  const modelProfile = configuredModels.find((entry) => entry?.id === modelId) ?? {}
1954
- const protocol = String(modelProfile.api ?? providerProfile.api ?? '').trim()
1955
- const baseUrl = String(providerProfile.baseURL ?? '').trim()
1977
+ // The built-in DeepSeek routes fix their protocol in the adapter: whatever
1978
+ // address they use, they speak Messages.
1979
+ const deepseekRoute = isBuiltInDeepseekRoute(providerId, providerRow?.settingsNs)
1980
+ let protocol = String(modelProfile.api ?? providerProfile.api ?? '').trim()
1981
+ let baseUrl = String(providerProfile.baseURL ?? '').trim()
1982
+ if (deepseekRoute && protocol === '') protocol = 'anthropic-messages'
1983
+ let builtin = false
1984
+ if (baseUrl === '' && deepseekRoute) {
1985
+ // Nothing is importable from settings, so mirror the endpoint the
1986
+ // adapter itself falls back to (`PUBLIC_BASE_URL`).
1987
+ baseUrl = DEEPSEEK_PUBLIC_BASE_URL
1988
+ builtin = true
1989
+ }
1956
1990
  if (!baseUrl) throw new Error(t('aiDshImportNoBaseUrl').replace('{model}', modelId))
1957
1991
  return {
1958
1992
  provider: providerId.toLowerCase() === 'ollama' || protocol === 'ollama'
@@ -1960,6 +1994,7 @@ window.__ModuleLoader__.load({
1960
1994
  : protocol === 'anthropic-messages' ? 'anthropic' : 'openai',
1961
1995
  baseUrl,
1962
1996
  model: modelId,
1997
+ ...builtin ? { builtin: true } : {},
1963
1998
  }
1964
1999
  }
1965
2000
 
@@ -2900,15 +2935,18 @@ window.__ModuleLoader__.load({
2900
2935
  try {
2901
2936
  const imported = await readDshModelConfig(ctx, t)
2902
2937
  if (!imported) throw new Error(t('aiDshImportEmpty'))
2938
+ const { builtin, ...config } = imported
2903
2939
  providerDrafts.current[provider] = { baseUrl, model }
2904
- setAiConfig({ ...imported, apiKey })
2905
- setProvider(imported.provider)
2906
- setBaseUrl(imported.baseUrl)
2907
- setModel(imported.model)
2940
+ setAiConfig({ ...config, apiKey })
2941
+ setProvider(config.provider)
2942
+ setBaseUrl(config.baseUrl)
2943
+ setModel(config.model)
2908
2944
  resetModelList()
2909
2945
  setDshImportState('ok')
2910
- setDshImportMsg(t('aiDshImportOk'))
2911
- log('info', '导入 DSH 模型配置', { provider: imported.provider, model: imported.model })
2946
+ // A built-in DeepSeek route has no stored address and no API key of
2947
+ // its own, so the note says where the address came from.
2948
+ setDshImportMsg(builtin === true ? t('aiDshImportBuiltin') : t('aiDshImportOk'))
2949
+ log('info', '导入 DSH 模型配置', { provider: config.provider, model: config.model, builtin: builtin === true })
2912
2950
  } catch (error) {
2913
2951
  const reason = String(error?.message ?? error).slice(0, 160)
2914
2952
  setDshImportState('fail')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@objectivex666/dsh-settings-search",
3
- "version": "1.13.0",
3
+ "version": "1.13.1",
4
4
  "description": "DSH settings panel search plugin – instantly filter setting sections and general items with navigation guidance. Installable via `dsh plugin add`.",
5
5
  "keywords": [
6
6
  "dsh",
@@ -497,26 +497,64 @@ test('a DSH build with no settings API fails with a readable reason instead of a
497
497
  await assert.rejects(api.readDshModelConfig({ get: () => undefined }, (key) => key), { message: 'aiDshImportApi' })
498
498
  })
499
499
 
500
- test('a default model without an importable Base URL names the model, and no default model imports nothing', async () => {
501
- const { api } = load()
502
- const account = {
503
- get: (name) => name === 'remote.settings'
500
+ // --- DSH default-model import: the built-in DeepSeek route ---
501
+
502
+ /** A ctx whose default model is one route, with whatever namespaces it reads. */
503
+ const dshDefaultModel = (provider, model, { settingsNs, namespaces = [] } = {}) => ({
504
+ get: (name) => name === 'remote.settings'
505
+ ? {
506
+ describe: async () => describeReply([
507
+ { ns: 'agent-default-model', value: { provider, model } },
508
+ ...namespaces,
509
+ ]),
510
+ }
511
+ : name === 'remote.llm'
504
512
  ? {
505
- describe: async () => describeReply([
506
- { ns: 'agent-default-model', value: { provider: 'deepseek-account', model: 'deepseek-flash' } },
507
- ]),
513
+ listConfigurableProviders: async () => ({
514
+ ok: true,
515
+ value: [{ provider, settingsNs, settingsPath: [] }],
516
+ }),
508
517
  }
509
- : name === 'remote.llm'
510
- ? {
511
- listConfigurableProviders: async () => ({
512
- ok: true,
513
- value: [{ provider: 'deepseek-account', settingsNs: 'llm-deepseek-account', settingsPath: [] }],
514
- }),
515
- }
516
- : undefined,
518
+ : undefined,
519
+ })
520
+
521
+ test('a built-in DeepSeek default model imports the adapter\u2019s own endpoint', async () => {
522
+ const { api } = load()
523
+ for (const [provider, settingsNs] of [['deepseek-account', 'llm-deepseek-account'], ['deepseek-official', 'llm-deepseek']]) {
524
+ const ctx = dshDefaultModel(provider, 'deepseek-flash', { settingsNs })
525
+ assert.deepEqual(plain(await api.readDshModelConfig(ctx, (key) => key)), {
526
+ provider: 'anthropic',
527
+ baseUrl: 'https://api.deepseek.com/anthropic',
528
+ model: 'deepseek-flash',
529
+ builtin: true,
530
+ }, `${provider} must fall back to the DeepSeek endpoint`)
517
531
  }
518
- await assert.rejects(api.readDshModelConfig(account, (key) => key), { message: 'aiDshImportNoBaseUrl' })
532
+ })
519
533
 
534
+ test('a configured DeepSeek base URL wins over the built-in fallback', async () => {
535
+ const { api } = load()
536
+ const ctx = dshDefaultModel('deepseek-official', 'deepseek-chat', {
537
+ settingsNs: 'llm-deepseek',
538
+ namespaces: [{ ns: 'llm-deepseek', value: { baseURL: 'https://proxy.test/anthropic' } }],
539
+ })
540
+ assert.deepEqual(plain(await api.readDshModelConfig(ctx, (key) => key)), {
541
+ provider: 'anthropic',
542
+ baseUrl: 'https://proxy.test/anthropic',
543
+ model: 'deepseek-chat',
544
+ })
545
+ })
546
+
547
+ test('a third-party route without an importable Base URL still names the model', async () => {
548
+ const { api } = load()
549
+ const ctx = dshDefaultModel('acme-gateway', 'acme-large', {
550
+ settingsNs: 'llm-pi-ai',
551
+ namespaces: [{ ns: 'llm-pi-ai', value: { providers: { 'acme-gateway': { models: [{ id: 'acme-large' }] } } } }],
552
+ })
553
+ await assert.rejects(api.readDshModelConfig(ctx, (key) => key), { message: 'aiDshImportNoBaseUrl' })
554
+ })
555
+
556
+ test('no default model imports nothing', async () => {
557
+ const { api } = load()
520
558
  const idle = {
521
559
  get: (name) => name === 'remote.settings'
522
560
  ? { describe: async () => describeReply([{ ns: 'agent-default-model', value: {} }]) }
@@ -584,7 +622,7 @@ const logRouteCalls = (calls) => calls.filter((call) => String(call.url).include
584
622
 
585
623
  test('log entries are batched to the host log route as one formatted line each', () => {
586
624
  const { api, calls } = load()
587
- api.log('info', '应用装载', { version: '1.13.0' })
625
+ api.log('info', '应用装载', { version: '1.13.1' })
588
626
  api.log('error', '导入失败')
589
627
  api.flushLogs()
590
628
  const [call] = logRouteCalls(calls)
@@ -593,7 +631,7 @@ test('log entries are batched to the host log route as one formatted line each',
593
631
  assert.deepEqual(plain(call.headers), { 'content-type': 'application/json' })
594
632
  assert.equal(call.body.action, 'append')
595
633
  assert.equal(call.body.lines.length, 2)
596
- assert.match(call.body.lines[0], /^\d{4}-\d{2}-\d{2}T[\d:.]+Z \[info\] \[[0-9a-f]{8}:1\] 应用装载 \{"version":"1\.13\.0"\}$/)
634
+ assert.match(call.body.lines[0], /^\d{4}-\d{2}-\d{2}T[\d:.]+Z \[info\] \[[0-9a-f]{8}:1\] 应用装载 \{"version":"1\.13\.1"\}$/)
597
635
  assert.match(call.body.lines[1], /\[error\] \[[0-9a-f]{8}:2\] 导入失败$/)
598
636
  })
599
637