dsh-plugin-effort-declare 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTRIBUTING.en.md +57 -0
- package/CONTRIBUTING.md +57 -0
- package/INSTALL.en.md +62 -0
- package/INSTALL.md +62 -0
- package/LICENSE +21 -0
- package/README.en.md +64 -0
- package/README.md +64 -0
- package/cordis.patch.yml +5 -0
- package/lib/client.js +1237 -0
- package/lib/client.js.map +1 -0
- package/lib/index.js +6 -0
- package/lib/types/client/EffortDeclareSection.d.ts +24 -0
- package/lib/types/client/EffortDeclareSection.d.ts.map +1 -0
- package/lib/types/client/index.d.ts +15 -0
- package/lib/types/client/index.d.ts.map +1 -0
- package/lib/types/client/load-drafts.d.ts +20 -0
- package/lib/types/client/load-drafts.d.ts.map +1 -0
- package/lib/types/client/locales.d.ts +8 -0
- package/lib/types/client/locales.d.ts.map +1 -0
- package/lib/types/client/schema-ops.d.ts +13 -0
- package/lib/types/client/schema-ops.d.ts.map +1 -0
- package/lib/types/core/catalog.d.ts +31 -0
- package/lib/types/core/catalog.d.ts.map +1 -0
- package/lib/types/core/drafts.d.ts +53 -0
- package/lib/types/core/drafts.d.ts.map +1 -0
- package/lib/types/core/efforts.d.ts +32 -0
- package/lib/types/core/efforts.d.ts.map +1 -0
- package/lib/types/core/filter.d.ts +28 -0
- package/lib/types/core/filter.d.ts.map +1 -0
- package/lib/types/core/path-ops.d.ts +40 -0
- package/lib/types/core/path-ops.d.ts.map +1 -0
- package/lib/types/core/paths.d.ts +16 -0
- package/lib/types/core/paths.d.ts.map +1 -0
- package/lib/types/core/presets.d.ts +31 -0
- package/lib/types/core/presets.d.ts.map +1 -0
- package/lib/types/core/validate.d.ts +3 -0
- package/lib/types/core/validate.d.ts.map +1 -0
- package/lib/types/index.d.ts +10 -0
- package/lib/types/index.d.ts.map +1 -0
- package/package.json +87 -0
- package/src/README.en.md +23 -0
- package/src/README.md +23 -0
- package/src/client/EffortDeclareSection.tsx +496 -0
- package/src/client/README.en.md +27 -0
- package/src/client/README.md +27 -0
- package/src/client/effort-declare.module.css +278 -0
- package/src/client/index.ts +97 -0
- package/src/client/load-drafts.ts +83 -0
- package/src/client/locales.ts +133 -0
- package/src/client/schema-ops.ts +20 -0
- package/src/core/README.en.md +27 -0
- package/src/core/README.md +27 -0
- package/src/core/catalog.ts +61 -0
- package/src/core/drafts.ts +137 -0
- package/src/core/efforts.ts +119 -0
- package/src/core/filter.ts +82 -0
- package/src/core/path-ops.ts +65 -0
- package/src/core/paths.ts +45 -0
- package/src/core/presets.ts +104 -0
- package/src/core/validate.ts +7 -0
- package/src/css-modules.d.ts +6 -0
- package/src/index.ts +11 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# src/client/
|
|
2
|
+
|
|
3
|
+
<p align="center">
|
|
4
|
+
<samp>
|
|
5
|
+
<strong>中文</strong> ·
|
|
6
|
+
<a href="./README.en.md">English</a>
|
|
7
|
+
</samp>
|
|
8
|
+
</p>
|
|
9
|
+
|
|
10
|
+
浏览器半区:在 Web UI 的设置面板注册「推理档位」,并经官方 settings RPC 写入 `llm-pi-ai`。
|
|
11
|
+
|
|
12
|
+
`apply` 接线失败会抛出,与官方模型页相同;不要整段 `try/catch` 吞掉。卸载时 locale、CSS 与事件订阅随 `ctx.effect` 清理。
|
|
13
|
+
|
|
14
|
+
跨插件协作只使用 Cordis 服务(`connection`、`settingsScope`、`settingsSchema`、`slots`、`locale`、`remote`)。不要 value-import `@deepseek-ai/dsh-client-ui-settings-models`:client bundle 的 purity 规则禁止把其它 `@deepseek-ai/*` 打进包内(shell 冻结模块表里的平台模块除外)。
|
|
15
|
+
|
|
16
|
+
## 这个目录里有什么
|
|
17
|
+
|
|
18
|
+
| 文件 | 说明 |
|
|
19
|
+
| --- | --- |
|
|
20
|
+
| [`index.ts`](./index.ts) | 注册 zh/en 文案、CSS(`ctx.effect` 插入/移除)与 `settings.section`(id `effort-declare`,order 15)。在 `apply` 里订阅 `settings/document-updated`(仅 `llm-pi-ai`)、`llm/adapters-updated`、`connection/reset`。 |
|
|
21
|
+
| [`EffortDeclareSection.tsx`](./EffortDeclareSection.tsx) | 路由卡片、预设、Off 三态、高级协议开关、保存与取消。 |
|
|
22
|
+
| [`load-drafts.ts`](./load-drafts.ts) | 从 `llm.providers` + 镜像组装草稿;草稿取自 `user`,协议分类可用 `value`。 |
|
|
23
|
+
| [`locales.ts`](./locales.ts) | 文案 namespace `plugin-effort-declare`。 |
|
|
24
|
+
| [`effort-declare.module.css`](./effort-declare.module.css) | 仅使用 `--dsw-alias-*` 设计令牌,保证暗色主题正确。 |
|
|
25
|
+
| [`schema-ops.ts`](./schema-ops.ts) | 将 `settingsSchema` 收成普通回调,避免把服务身份带进 React。 |
|
|
26
|
+
|
|
27
|
+
样式与交互对齐官方模型页,而不是带浅色 fallback 的 `--ds-*` 变量。
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
/* Effort-declare settings section — --dsw-alias-* only (dark theme safe). */
|
|
2
|
+
|
|
3
|
+
.section {
|
|
4
|
+
display: flex;
|
|
5
|
+
flex-direction: column;
|
|
6
|
+
gap: 12px;
|
|
7
|
+
max-width: 720px;
|
|
8
|
+
color: var(--dsw-alias-label-primary);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.title {
|
|
12
|
+
margin: 0;
|
|
13
|
+
font-size: 16px;
|
|
14
|
+
line-height: 24px;
|
|
15
|
+
font-weight: 500;
|
|
16
|
+
color: var(--dsw-alias-label-primary);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.intro {
|
|
20
|
+
margin: 0;
|
|
21
|
+
font-size: 14px;
|
|
22
|
+
line-height: 22px;
|
|
23
|
+
color: var(--dsw-alias-label-tertiary);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.notice {
|
|
27
|
+
margin: 0;
|
|
28
|
+
font-size: 12px;
|
|
29
|
+
line-height: 18px;
|
|
30
|
+
color: var(--dsw-alias-state-warn-label);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.savedNotice {
|
|
34
|
+
margin: 0;
|
|
35
|
+
font-size: 12px;
|
|
36
|
+
line-height: 18px;
|
|
37
|
+
color: var(--dsw-alias-state-success-primary);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.error {
|
|
41
|
+
margin: 0;
|
|
42
|
+
font-size: 12px;
|
|
43
|
+
line-height: 18px;
|
|
44
|
+
color: var(--dsw-alias-state-error-primary);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.rows {
|
|
48
|
+
list-style: none;
|
|
49
|
+
margin: 12px 0 0;
|
|
50
|
+
padding: 0;
|
|
51
|
+
display: flex;
|
|
52
|
+
flex-direction: column;
|
|
53
|
+
gap: 8px;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.rowCard {
|
|
57
|
+
border: 1px solid var(--dsw-alias-border-l2);
|
|
58
|
+
border-radius: 12px;
|
|
59
|
+
padding: 12px 14px;
|
|
60
|
+
display: flex;
|
|
61
|
+
flex-direction: column;
|
|
62
|
+
gap: 12px;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.rowHead {
|
|
66
|
+
display: flex;
|
|
67
|
+
align-items: baseline;
|
|
68
|
+
gap: 8px;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.rowName {
|
|
72
|
+
font-size: 14px;
|
|
73
|
+
line-height: 22px;
|
|
74
|
+
font-weight: 500;
|
|
75
|
+
color: var(--dsw-alias-label-primary);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.rowTag {
|
|
79
|
+
flex: none;
|
|
80
|
+
padding: 1px 6px;
|
|
81
|
+
border: 1px solid var(--dsw-alias-border-l3);
|
|
82
|
+
border-radius: 4px;
|
|
83
|
+
font-size: 11px;
|
|
84
|
+
line-height: 16px;
|
|
85
|
+
color: var(--dsw-alias-label-secondary);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.compatSummary {
|
|
89
|
+
margin: 0;
|
|
90
|
+
font-size: 12px;
|
|
91
|
+
line-height: 18px;
|
|
92
|
+
color: var(--dsw-alias-label-tertiary);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.presetRow,
|
|
96
|
+
.actions {
|
|
97
|
+
display: flex;
|
|
98
|
+
flex-wrap: wrap;
|
|
99
|
+
gap: 8px;
|
|
100
|
+
align-items: center;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.fieldLabel {
|
|
104
|
+
font-size: 12px;
|
|
105
|
+
line-height: 18px;
|
|
106
|
+
font-weight: 500;
|
|
107
|
+
color: var(--dsw-alias-label-secondary);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.primaryButton,
|
|
111
|
+
.secondaryButton {
|
|
112
|
+
box-sizing: border-box;
|
|
113
|
+
display: inline-flex;
|
|
114
|
+
align-items: center;
|
|
115
|
+
justify-content: center;
|
|
116
|
+
height: 36px;
|
|
117
|
+
padding: 0 14px;
|
|
118
|
+
border-radius: 18px;
|
|
119
|
+
font: inherit;
|
|
120
|
+
font-size: 14px;
|
|
121
|
+
line-height: 22px;
|
|
122
|
+
cursor: pointer;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.primaryButton {
|
|
126
|
+
border: none;
|
|
127
|
+
background: var(--dsw-alias-button-primary-fill);
|
|
128
|
+
color: var(--dsw-alias-label-primary-foreground);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.primaryButton:hover:not(:disabled) {
|
|
132
|
+
background: var(--dsw-alias-button-primary-hover);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.secondaryButton {
|
|
136
|
+
border: 1px solid var(--dsw-alias-border-l2);
|
|
137
|
+
background: transparent;
|
|
138
|
+
color: var(--dsw-alias-label-primary);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.secondaryButton:hover:not(:disabled) {
|
|
142
|
+
background: var(--dsw-alias-interactive-bg-hover-solid);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.primaryButton:disabled,
|
|
146
|
+
.secondaryButton:disabled,
|
|
147
|
+
.linkButton:disabled,
|
|
148
|
+
.input:disabled {
|
|
149
|
+
opacity: 0.4;
|
|
150
|
+
cursor: default;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.primaryButton:focus-visible,
|
|
154
|
+
.secondaryButton:focus-visible,
|
|
155
|
+
.linkButton:focus-visible,
|
|
156
|
+
.input:focus-visible {
|
|
157
|
+
outline: none;
|
|
158
|
+
box-shadow: 0 0 0 2px var(--dsw-alias-border-l3);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.linkButton {
|
|
162
|
+
box-sizing: border-box;
|
|
163
|
+
display: inline-flex;
|
|
164
|
+
align-items: center;
|
|
165
|
+
height: 28px;
|
|
166
|
+
padding: 0 10px;
|
|
167
|
+
border: none;
|
|
168
|
+
border-radius: 14px;
|
|
169
|
+
background: transparent;
|
|
170
|
+
color: var(--dsw-alias-label-tertiary);
|
|
171
|
+
font: inherit;
|
|
172
|
+
font-size: 12px;
|
|
173
|
+
line-height: 18px;
|
|
174
|
+
cursor: pointer;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.linkButton:hover:not(:disabled) {
|
|
178
|
+
background: var(--dsw-alias-interactive-bg-hover);
|
|
179
|
+
color: var(--dsw-alias-label-secondary);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.modelEntry {
|
|
183
|
+
display: flex;
|
|
184
|
+
flex-direction: column;
|
|
185
|
+
gap: 8px;
|
|
186
|
+
padding: 10px 0;
|
|
187
|
+
border-top: 1px solid var(--dsw-alias-border-l2);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
.modelHead {
|
|
191
|
+
display: flex;
|
|
192
|
+
flex-wrap: wrap;
|
|
193
|
+
align-items: baseline;
|
|
194
|
+
gap: 8px;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
.modelId {
|
|
198
|
+
font-size: 13px;
|
|
199
|
+
line-height: 20px;
|
|
200
|
+
font-weight: 500;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
.modelName {
|
|
204
|
+
font-size: 12px;
|
|
205
|
+
line-height: 18px;
|
|
206
|
+
color: var(--dsw-alias-label-tertiary);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.levels {
|
|
210
|
+
display: flex;
|
|
211
|
+
flex-wrap: wrap;
|
|
212
|
+
gap: 10px 14px;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
.level {
|
|
216
|
+
display: inline-flex;
|
|
217
|
+
align-items: center;
|
|
218
|
+
gap: 6px;
|
|
219
|
+
font-size: 12px;
|
|
220
|
+
line-height: 18px;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.wireRow {
|
|
224
|
+
display: flex;
|
|
225
|
+
flex-wrap: wrap;
|
|
226
|
+
gap: 8px;
|
|
227
|
+
align-items: center;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
.input,
|
|
231
|
+
.selectInput {
|
|
232
|
+
box-sizing: border-box;
|
|
233
|
+
height: 32px;
|
|
234
|
+
padding: 0 10px;
|
|
235
|
+
border: 1px solid var(--dsw-alias-border-l2);
|
|
236
|
+
border-radius: 8px;
|
|
237
|
+
background: var(--dsw-alias-bg-module-platform);
|
|
238
|
+
color: var(--dsw-alias-label-primary);
|
|
239
|
+
font: inherit;
|
|
240
|
+
font-size: 13px;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
.wireInput {
|
|
244
|
+
width: 7em;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
.offGroup {
|
|
248
|
+
display: flex;
|
|
249
|
+
flex-direction: column;
|
|
250
|
+
gap: 6px;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
.advanced {
|
|
254
|
+
border-top: 1px solid var(--dsw-alias-border-l2);
|
|
255
|
+
padding-top: 8px;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
.advanced summary {
|
|
259
|
+
cursor: pointer;
|
|
260
|
+
font-size: 12px;
|
|
261
|
+
line-height: 18px;
|
|
262
|
+
color: var(--dsw-alias-label-secondary);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
.advancedBody {
|
|
266
|
+
display: flex;
|
|
267
|
+
flex-direction: column;
|
|
268
|
+
gap: 10px;
|
|
269
|
+
padding-top: 10px;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
.check {
|
|
273
|
+
display: flex;
|
|
274
|
+
align-items: flex-start;
|
|
275
|
+
gap: 8px;
|
|
276
|
+
font-size: 12px;
|
|
277
|
+
line-height: 18px;
|
|
278
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-plugin-effort-declare — browser half: settings.section for per-model
|
|
3
|
+
* reasoning effort declarations.
|
|
4
|
+
*/
|
|
5
|
+
import type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client'
|
|
6
|
+
import type { ConnectionHandle } from '@deepseek-ai/dsh-api-remotes/client'
|
|
7
|
+
import type {} from '@deepseek-ai/dsh-client-ui-settings/client'
|
|
8
|
+
import type {} from '@deepseek-ai/dsh-client-locale/client'
|
|
9
|
+
import type {} from '@deepseek-ai/dsh-api-remotes/client'
|
|
10
|
+
import { EffortDeclareSection } from './EffortDeclareSection.tsx'
|
|
11
|
+
import type { EffortDeclareSectionInjected, InvalidationSource } from './EffortDeclareSection.tsx'
|
|
12
|
+
import { bindSchema } from './schema-ops.ts'
|
|
13
|
+
import { LLM_PI_AI_NS } from '../core/catalog.ts'
|
|
14
|
+
import { NS, en, zh, type EffortDeclareKey } from './locales.ts'
|
|
15
|
+
import { cssTagId, cssText } from './effort-declare.module.css'
|
|
16
|
+
|
|
17
|
+
declare module '@deepseek-ai/dsh-client-ui-slots' {
|
|
18
|
+
interface LocaleNamespaceMap {
|
|
19
|
+
/** Effort-declare settings section copy. */
|
|
20
|
+
'plugin-effort-declare': EffortDeclareKey
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const inject = ['slots', 'locale', 'connection', 'remote', 'settingsScope', 'settingsSchema']
|
|
25
|
+
|
|
26
|
+
const PLUGIN_ID = 'dsh-plugin-effort-declare'
|
|
27
|
+
|
|
28
|
+
function mountPluginCss(): () => void {
|
|
29
|
+
if (typeof document === 'undefined') return () => {}
|
|
30
|
+
const selector = `style[data-plugin-css=${JSON.stringify(cssTagId)}]`
|
|
31
|
+
let tag = document.querySelector(selector) as HTMLStyleElement | null
|
|
32
|
+
if (tag === null) {
|
|
33
|
+
tag = document.createElement('style')
|
|
34
|
+
tag.dataset.plugin = PLUGIN_ID
|
|
35
|
+
tag.dataset.pluginCss = cssTagId
|
|
36
|
+
document.head.appendChild(tag)
|
|
37
|
+
}
|
|
38
|
+
tag.textContent = cssText
|
|
39
|
+
return () => { tag?.remove() }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function apply(ctx: ClientContext): void {
|
|
43
|
+
ctx.effect(() => ctx.locale.register(NS, { zh, en }), `${PLUGIN_ID}: dictionaries`)
|
|
44
|
+
ctx.effect(() => mountPluginCss(), `${PLUGIN_ID}: css`)
|
|
45
|
+
|
|
46
|
+
const connection = ctx.get('connection') as ConnectionHandle
|
|
47
|
+
const settingsSchema = ctx.settingsSchema
|
|
48
|
+
const schema = bindSchema({
|
|
49
|
+
rehydrate: serialized => settingsSchema.rehydrate(serialized),
|
|
50
|
+
nodeAtPath: (root, path) => settingsSchema.nodeAtPath(
|
|
51
|
+
root as Parameters<typeof settingsSchema.nodeAtPath>[0],
|
|
52
|
+
path,
|
|
53
|
+
),
|
|
54
|
+
getPath: (value, path) => settingsSchema.getPath(value, path),
|
|
55
|
+
hasPath: (value, path) => settingsSchema.hasPath(value, path),
|
|
56
|
+
})
|
|
57
|
+
const t = ctx.locale.bind(NS) as EffortDeclareSectionInjected['t']
|
|
58
|
+
const describe = ctx.settingsScope.describe()
|
|
59
|
+
const invalidation = new Set<(source: InvalidationSource) => void>()
|
|
60
|
+
|
|
61
|
+
ctx.effect(() => {
|
|
62
|
+
const emit = (source: InvalidationSource) => {
|
|
63
|
+
for (const listener of invalidation) listener(source)
|
|
64
|
+
}
|
|
65
|
+
const disposers = [
|
|
66
|
+
ctx.remote.$on('settings/document-updated', (ns: string) => {
|
|
67
|
+
if (ns !== LLM_PI_AI_NS) return
|
|
68
|
+
emit('settings')
|
|
69
|
+
}),
|
|
70
|
+
ctx.remote.$on('llm/adapters-updated', () => { emit('directory') }),
|
|
71
|
+
ctx.on('connection/reset', () => { emit('directory') }),
|
|
72
|
+
]
|
|
73
|
+
return () => {
|
|
74
|
+
for (const dispose of disposers) dispose()
|
|
75
|
+
}
|
|
76
|
+
}, `${PLUGIN_ID}: invalidations`)
|
|
77
|
+
|
|
78
|
+
const subscribeInvalidate: EffortDeclareSectionInjected['subscribeInvalidate'] = (listener) => {
|
|
79
|
+
invalidation.add(listener)
|
|
80
|
+
return () => { invalidation.delete(listener) }
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
ctx.slots.inject('settings.section', () => ctx.slots.register({
|
|
84
|
+
name: 'settings.section',
|
|
85
|
+
id: 'effort-declare',
|
|
86
|
+
order: 15,
|
|
87
|
+
label: () => t('nav'),
|
|
88
|
+
locale: NS,
|
|
89
|
+
inject: (): EffortDeclareSectionInjected => ({
|
|
90
|
+
api: connection.api,
|
|
91
|
+
describe,
|
|
92
|
+
schema,
|
|
93
|
+
t,
|
|
94
|
+
subscribeInvalidate,
|
|
95
|
+
}),
|
|
96
|
+
}, EffortDeclareSection))
|
|
97
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Load editable route drafts from llm.providers + the llm-pi-ai namespace.
|
|
3
|
+
* Drafts come from the user layer; route protocol classification may use effective value.
|
|
4
|
+
*/
|
|
5
|
+
import type { IApiClient, SettingsNamespaceView } from '@deepseek-ai/dsh-api-remotes/client'
|
|
6
|
+
import type { SettingsDescribeFace } from '@deepseek-ai/dsh-client-ui-settings/client'
|
|
7
|
+
import {
|
|
8
|
+
FALLBACK_THINKING_FORMATS,
|
|
9
|
+
LLM_PI_AI_NS,
|
|
10
|
+
SCHEMA_PROBE_ROUTE,
|
|
11
|
+
} from '../core/catalog.ts'
|
|
12
|
+
import { routeDraftFromUserProfile, type RouteDraft } from '../core/drafts.ts'
|
|
13
|
+
import { classifyRoute, profileAt, unionStringChoices } from '../core/filter.ts'
|
|
14
|
+
import { isPlainObject } from '../core/paths.ts'
|
|
15
|
+
import type { SchemaOps } from './schema-ops.ts'
|
|
16
|
+
|
|
17
|
+
function schemaDefaultString(node: unknown): string | undefined {
|
|
18
|
+
if (!isPlainObject(node) || !isPlainObject(node.meta)) return undefined
|
|
19
|
+
return typeof node.meta.default === 'string' ? node.meta.default : undefined
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface LoadDraftsResult {
|
|
23
|
+
writable: boolean
|
|
24
|
+
formats: string[]
|
|
25
|
+
drafts: RouteDraft[]
|
|
26
|
+
error?: string
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* First paint: `ensure()` (reads only from idle). Never treat ensure as refresh.
|
|
31
|
+
* Callers that must not apply a stale settlement compare generation themselves.
|
|
32
|
+
*/
|
|
33
|
+
export async function loadDrafts(
|
|
34
|
+
api: Pick<IApiClient, 'llm'>,
|
|
35
|
+
describe: Pick<SettingsDescribeFace, 'ensure' | 'getSnapshot'>,
|
|
36
|
+
schema: SchemaOps,
|
|
37
|
+
): Promise<LoadDraftsResult> {
|
|
38
|
+
await describe.ensure()
|
|
39
|
+
const mirrored = describe.getSnapshot()
|
|
40
|
+
if (mirrored.view === undefined) {
|
|
41
|
+
return { writable: false, formats: [...FALLBACK_THINKING_FORMATS], drafts: [], error: mirrored.error ?? undefined }
|
|
42
|
+
}
|
|
43
|
+
const providersResponse = await api.llm.providers({})
|
|
44
|
+
if (!providersResponse.result.ok) {
|
|
45
|
+
return {
|
|
46
|
+
writable: mirrored.view.writable,
|
|
47
|
+
formats: [...FALLBACK_THINKING_FORMATS],
|
|
48
|
+
drafts: [],
|
|
49
|
+
error: providersResponse.result.error.message,
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const namespaces = new Map(mirrored.view.namespaces.map((view: SettingsNamespaceView) => [view.ns, view]))
|
|
53
|
+
const pi = namespaces.get(LLM_PI_AI_NS)
|
|
54
|
+
let formats: string[] = [...FALLBACK_THINKING_FORMATS]
|
|
55
|
+
let schemaDefaultApi: string | undefined
|
|
56
|
+
if (pi !== undefined) {
|
|
57
|
+
try {
|
|
58
|
+
const root = schema.rehydrate(pi.schema)
|
|
59
|
+
const formatNode = schema.nodeAtPath(root, ['providers', SCHEMA_PROBE_ROUTE, 'compat', 'thinkingFormat'])
|
|
60
|
+
const fromSchema = unionStringChoices(formatNode)
|
|
61
|
+
if (fromSchema.length > 0) formats = fromSchema
|
|
62
|
+
schemaDefaultApi = schemaDefaultString(schema.nodeAtPath(root, ['providers', SCHEMA_PROBE_ROUTE, 'api']))
|
|
63
|
+
} catch {
|
|
64
|
+
// Live schema walk is best-effort; fallback list is test-pinned.
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
const drafts: RouteDraft[] = []
|
|
68
|
+
for (const entry of providersResponse.result.value.providers) {
|
|
69
|
+
const settingsPath = entry.settingsPath !== undefined && entry.settingsPath.length > 0
|
|
70
|
+
? [...entry.settingsPath]
|
|
71
|
+
: ['providers', entry.provider]
|
|
72
|
+
const effective = profileAt(pi?.value, settingsPath, entry.provider)
|
|
73
|
+
if (!classifyRoute(entry, effective, schemaDefaultApi).editable) continue
|
|
74
|
+
drafts.push(routeDraftFromUserProfile({
|
|
75
|
+
provider: entry.provider,
|
|
76
|
+
displayName: entry.displayName ?? entry.provider,
|
|
77
|
+
settingsPath,
|
|
78
|
+
revision: pi?.revision ?? 0,
|
|
79
|
+
userProfile: profileAt(pi?.user, settingsPath, entry.provider),
|
|
80
|
+
}))
|
|
81
|
+
}
|
|
82
|
+
return { writable: mirrored.view.writable, formats, drafts }
|
|
83
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dictionary namespace for the effort-declare settings section.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export type EffortDeclareKey =
|
|
6
|
+
| 'nav'
|
|
7
|
+
| 'title'
|
|
8
|
+
| 'intro'
|
|
9
|
+
| 'empty'
|
|
10
|
+
| 'emptyHint'
|
|
11
|
+
| 'loadError'
|
|
12
|
+
| 'loading'
|
|
13
|
+
| 'readOnly'
|
|
14
|
+
| 'save'
|
|
15
|
+
| 'saving'
|
|
16
|
+
| 'cancel'
|
|
17
|
+
| 'saved'
|
|
18
|
+
| 'conflict'
|
|
19
|
+
| 'dirtyConflict'
|
|
20
|
+
| 'presets'
|
|
21
|
+
| 'presetDeepSeek'
|
|
22
|
+
| 'presetOpenAI'
|
|
23
|
+
| 'presetToggle'
|
|
24
|
+
| 'presetToggleWarn'
|
|
25
|
+
| 'model'
|
|
26
|
+
| 'levels'
|
|
27
|
+
| 'wire'
|
|
28
|
+
| 'offMode'
|
|
29
|
+
| 'offAbsent'
|
|
30
|
+
| 'offEmpty'
|
|
31
|
+
| 'offValue'
|
|
32
|
+
| 'offValuePlaceholder'
|
|
33
|
+
| 'clear'
|
|
34
|
+
| 'advanced'
|
|
35
|
+
| 'thinkingFormat'
|
|
36
|
+
| 'thinkingFormatDefault'
|
|
37
|
+
| 'supportsDeveloperRole'
|
|
38
|
+
| 'supportsReasoningEffort'
|
|
39
|
+
| 'developerTrueHint'
|
|
40
|
+
| 'compatSummary'
|
|
41
|
+
| 'errorEmpty'
|
|
42
|
+
| 'errorOffOnly'
|
|
43
|
+
| 'errorBadWire'
|
|
44
|
+
| 'noModels'
|
|
45
|
+
| 'reload'
|
|
46
|
+
|
|
47
|
+
export const NS = 'plugin-effort-declare'
|
|
48
|
+
|
|
49
|
+
export const zh: Record<EffortDeclareKey, string> = {
|
|
50
|
+
nav: '推理档位',
|
|
51
|
+
title: '推理档位声明',
|
|
52
|
+
intro: '本页声明「这模型能选哪些推理档」。对话里的档位选择仍在输入框模型菜单。没声明 = 和现在一样没有 Effort 行。不会改密钥、目录或当次选择。',
|
|
53
|
+
empty: '没有可编辑的手工 openai-completions 路由。',
|
|
54
|
+
emptyHint: '请先到「模型」页添加提供方(llm-pi-ai 手工路由)。官方 DeepSeek 与 catalog 路由不在本页编辑。需要 DSH ≥ 0.1.0-rc.8(providers.declared)。',
|
|
55
|
+
loadError: '无法加载提供方或设置。',
|
|
56
|
+
loading: '正在加载…',
|
|
57
|
+
readOnly: '当前设置为只读,无法保存。',
|
|
58
|
+
save: '保存',
|
|
59
|
+
saving: '保存中…',
|
|
60
|
+
cancel: '取消',
|
|
61
|
+
saved: '已保存。对话选择器会按新的能力声明显示 Effort 行。',
|
|
62
|
+
conflict: '设置已被其他地方改过,请重新加载后再保存。',
|
|
63
|
+
dirtyConflict: '此路由有未保存修改,设置已在其他地方更新。保存会写到最新文档上,或先取消再改。',
|
|
64
|
+
presets: '预设',
|
|
65
|
+
presetDeepSeek: 'DeepSeek 兼容',
|
|
66
|
+
presetOpenAI: 'OpenAI 兼容',
|
|
67
|
+
presetToggle: '仅开/关',
|
|
68
|
+
presetToggleWarn: '选择器里多档在线上没有区别:除 Off 外请求体相同。',
|
|
69
|
+
model: '模型',
|
|
70
|
+
levels: '可选档位',
|
|
71
|
+
wire: '线上拼写',
|
|
72
|
+
offMode: 'Off',
|
|
73
|
+
offAbsent: '无 Off',
|
|
74
|
+
offEmpty: 'Off 且不发参数',
|
|
75
|
+
offValue: 'Off 且发字符串',
|
|
76
|
+
offValuePlaceholder: 'none',
|
|
77
|
+
clear: '清除本模型声明',
|
|
78
|
+
advanced: '高级:协议方言',
|
|
79
|
+
thinkingFormat: 'thinkingFormat',
|
|
80
|
+
thinkingFormatDefault: '默认(openai)',
|
|
81
|
+
supportsDeveloperRole: '系统提示走 system 而不是 developer(supportsDeveloperRole: false)',
|
|
82
|
+
supportsReasoningEffort: '不发 reasoning_effort,只发开关(supportsReasoningEffort: false)',
|
|
83
|
+
developerTrueHint: '当前文档是 supportsDeveloperRole: true。v1 只能强制 false 或缺席;勾选会写成 false,取消勾选会删除该键。',
|
|
84
|
+
compatSummary: '协议',
|
|
85
|
+
errorEmpty: '不能保存空的 reasoningEfforts。请选择档位,或清除声明。',
|
|
86
|
+
errorOffOnly: '不能只开 Off,必须再开一档思考档。',
|
|
87
|
+
errorBadWire: '思考档必须填写线上拼写;只有 Off 可以留空。不能使用未知档位键。',
|
|
88
|
+
noModels: '这条路由还没有 models 列表。请先到「模型」页添加模型。',
|
|
89
|
+
reload: '重新加载',
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export const en: Record<EffortDeclareKey, string> = {
|
|
93
|
+
nav: 'Reasoning efforts',
|
|
94
|
+
title: 'Reasoning effort declarations',
|
|
95
|
+
intro: 'This page declares which reasoning levels a model can offer. The per-turn choice stays in the composer model menu. Undeclared models keep having no Effort row. Keys, catalogs, and the current selection are not edited here.',
|
|
96
|
+
empty: 'No editable hand-declared openai-completions routes.',
|
|
97
|
+
emptyHint: 'Add a provider on the Models page first (an llm-pi-ai hand-declared route). Official DeepSeek and catalog routes are not edited here. Requires DSH ≥ 0.1.0-rc.8 (providers.declared).',
|
|
98
|
+
loadError: 'Could not load providers or settings.',
|
|
99
|
+
loading: 'Loading…',
|
|
100
|
+
readOnly: 'Settings are read-only; saving is disabled.',
|
|
101
|
+
save: 'Save',
|
|
102
|
+
saving: 'Saving…',
|
|
103
|
+
cancel: 'Cancel',
|
|
104
|
+
saved: 'Saved. The composer Effort row follows this capability declaration.',
|
|
105
|
+
conflict: 'Settings changed elsewhere. Reload, then save again.',
|
|
106
|
+
dirtyConflict: 'This route has unsaved edits and settings changed elsewhere. Save writes onto the latest document, or cancel first.',
|
|
107
|
+
presets: 'Presets',
|
|
108
|
+
presetDeepSeek: 'DeepSeek compatible',
|
|
109
|
+
presetOpenAI: 'OpenAI compatible',
|
|
110
|
+
presetToggle: 'On/off only',
|
|
111
|
+
presetToggleWarn: 'Multiple selector levels are identical on the wire except Off.',
|
|
112
|
+
model: 'Model',
|
|
113
|
+
levels: 'Offered levels',
|
|
114
|
+
wire: 'Wire spelling',
|
|
115
|
+
offMode: 'Off',
|
|
116
|
+
offAbsent: 'No Off',
|
|
117
|
+
offEmpty: 'Off, send nothing',
|
|
118
|
+
offValue: 'Off, send a string',
|
|
119
|
+
offValuePlaceholder: 'none',
|
|
120
|
+
clear: 'Clear this model’s declaration',
|
|
121
|
+
advanced: 'Advanced: protocol dialect',
|
|
122
|
+
thinkingFormat: 'thinkingFormat',
|
|
123
|
+
thinkingFormatDefault: 'Default (openai)',
|
|
124
|
+
supportsDeveloperRole: 'Send system prompts as system, not developer (supportsDeveloperRole: false)',
|
|
125
|
+
supportsReasoningEffort: 'Do not send reasoning_effort; switch only (supportsReasoningEffort: false)',
|
|
126
|
+
developerTrueHint: 'The document has supportsDeveloperRole: true. v1 can only force false or omit the key; checking writes false, unchecking deletes the key.',
|
|
127
|
+
compatSummary: 'Protocol',
|
|
128
|
+
errorEmpty: 'Empty reasoningEfforts cannot be saved. Pick levels, or clear the declaration.',
|
|
129
|
+
errorOffOnly: 'Off alone is not enough; declare at least one thinking level.',
|
|
130
|
+
errorBadWire: 'Thinking levels need a wire spelling; only Off may be empty. Unknown effort keys are rejected.',
|
|
131
|
+
noModels: 'This route has no models list yet. Add models on the Models page first.',
|
|
132
|
+
reload: 'Reload',
|
|
133
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bind the settings-owned schema service without importing executable
|
|
3
|
+
* helpers from another client plugin (bundle purity).
|
|
4
|
+
*/
|
|
5
|
+
export interface SchemaOps {
|
|
6
|
+
rehydrate: (serialized: unknown) => unknown
|
|
7
|
+
nodeAtPath: (root: unknown, path: readonly string[]) => unknown
|
|
8
|
+
getPath: (value: unknown, path: readonly string[]) => unknown
|
|
9
|
+
hasPath: (value: unknown, path: readonly string[]) => boolean
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** Wrap a live settingsSchema service as plain callbacks. */
|
|
13
|
+
export function bindSchema(service: SchemaOps): SchemaOps {
|
|
14
|
+
return {
|
|
15
|
+
rehydrate: serialized => service.rehydrate(serialized),
|
|
16
|
+
nodeAtPath: (root, path) => service.nodeAtPath(root, path),
|
|
17
|
+
getPath: (value, path) => service.getPath(value, path),
|
|
18
|
+
hasPath: (value, path) => service.hasPath(value, path),
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# src/core/
|
|
2
|
+
|
|
3
|
+
<p align="center">
|
|
4
|
+
<samp>
|
|
5
|
+
<a href="./README.md">中文</a> ·
|
|
6
|
+
<strong>English</strong>
|
|
7
|
+
</samp>
|
|
8
|
+
</p>
|
|
9
|
+
|
|
10
|
+
Domain logic for the plugin: no React, no Cordis service instances. The settings page and Vitest both depend on these pure functions, so effort semantics can regress without booting DSH.
|
|
11
|
+
|
|
12
|
+
Canonical level names and `thinkingFormat` values follow `@deepseek-ai/dsh-llm-pi-ai` (0.1.0-rc.8). A fallback whitelist is pinned by tests so the UI cannot silently drift from the upstream schema.
|
|
13
|
+
|
|
14
|
+
## What's in this directory
|
|
15
|
+
|
|
16
|
+
| File | Role |
|
|
17
|
+
| --- | --- |
|
|
18
|
+
| [`catalog.ts`](./catalog.ts) | Canonical level order, `thinkingFormat` fallback list, `llm-pi-ai` / DeepSeek constants. |
|
|
19
|
+
| [`efforts.ts`](./efforts.ts) | `reasoningEfforts` read/write, Off tri-state, validation (empty object / Off-only / unknown keys return an error code, never throw). |
|
|
20
|
+
| [`presets.ts`](./presets.ts) | DeepSeek, OpenAI, and on/off presets; each states all three dialect keys; spread onto existing model rows and route `compat`. |
|
|
21
|
+
| [`path-ops.ts`](./path-ops.ts) | Same one-level key diff as the official Models page; `buildSaveOps` uses `settingsPath` and only submits that route’s full `models` table and dirty `compat` keys. |
|
|
22
|
+
| [`drafts.ts`](./drafts.ts) | User-layer drafts, dirty aligned with pathOps, post-save revision bump, dirty merge, generation. |
|
|
23
|
+
| [`paths.ts`](./paths.ts) | Nested reads; clones of objects and model tables (non-object rows skipped). |
|
|
24
|
+
| [`filter.ts`](./filter.ts) | Which routes are editable: hand-declared `llm-pi-ai` + openai-completions; skip catalog and official DeepSeek. |
|
|
25
|
+
| [`validate.ts`](./validate.ts) | Per-row `reasoningEfforts` validation. |
|
|
26
|
+
|
|
27
|
+
An **absent** field means default-off. Do not encode “undeclared” as `reasoningEfforts: false` — in official semantics `false` strips reasoning from a catalog model.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# src/core/
|
|
2
|
+
|
|
3
|
+
<p align="center">
|
|
4
|
+
<samp>
|
|
5
|
+
<strong>中文</strong> ·
|
|
6
|
+
<a href="./README.en.md">English</a>
|
|
7
|
+
</samp>
|
|
8
|
+
</p>
|
|
9
|
+
|
|
10
|
+
本插件的领域逻辑:无 React、无 Cordis 服务实例。设置页与 Vitest 都只依赖这些纯函数,因此档位语义可以在不启动 DSH 的情况下回归。
|
|
11
|
+
|
|
12
|
+
规范键名与 `thinkingFormat` 取值对齐 `@deepseek-ai/dsh-llm-pi-ai`(0.1.0-rc.8)。仓库内有回退白名单,并由测试钉住,避免 UI 与上游 schema 静默分叉。
|
|
13
|
+
|
|
14
|
+
## 这个目录里有什么
|
|
15
|
+
|
|
16
|
+
| 文件 | 说明 |
|
|
17
|
+
| --- | --- |
|
|
18
|
+
| [`catalog.ts`](./catalog.ts) | 规范档位顺序、`thinkingFormat` 回退列表、`llm-pi-ai` / DeepSeek 相关常量。 |
|
|
19
|
+
| [`efforts.ts`](./efforts.ts) | `reasoningEfforts` 读写、Off 三态、校验(空对象 / 只开 Off / 未知键返回错误码,不抛异常)。 |
|
|
20
|
+
| [`presets.ts`](./presets.ts) | DeepSeek、OpenAI、仅开/关三套预设;每套对三个方言键都表态;spread 到已有模型行与路由 `compat`。 |
|
|
21
|
+
| [`path-ops.ts`](./path-ops.ts) | 与官方模型页相同的一层键 diff;`buildSaveOps` 使用 `settingsPath`,只提交该路由的 `models` 整表和有差异的 `compat` 键。 |
|
|
22
|
+
| [`drafts.ts`](./drafts.ts) | 用户层草稿、dirty 与 pathOps 一致、保存后 revision、脏卡合并、generation。 |
|
|
23
|
+
| [`paths.ts`](./paths.ts) | 嵌套读取、对象与模型表的 clone(非对象行跳过)。 |
|
|
24
|
+
| [`filter.ts`](./filter.ts) | 哪些路由可编辑:手工 `llm-pi-ai` + openai-completions;排除 catalog 与官方 DeepSeek。 |
|
|
25
|
+
| [`validate.ts`](./validate.ts) | 对单行 `reasoningEfforts` 调用校验。 |
|
|
26
|
+
|
|
27
|
+
**缺席字段**表示默认关闭。不要把「未声明」写成 `reasoningEfforts: false`——`false` 在官方语义里是从 catalog 模型上剥掉推理。
|