@dsh-plus/llm-pi 0.1.5 → 0.1.7
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 +1881 -1845
- package/lib/index.d.ts +21 -4
- package/lib/index.js +573 -135
- package/package.json +16 -11
- package/src/catalog/builtin.ts +17 -4
- package/src/catalog/models-dev.ts +13 -6
- package/src/catalog/official.ts +71 -0
- package/src/client/api.ts +17 -4
- package/src/client/card.tsx +57 -20
- package/src/client/client.ts +20 -9
- package/src/client/constants.ts +5 -1
- package/src/client/draft.ts +110 -38
- package/src/client/fields.tsx +3 -1
- package/src/client/i18n.ts +4 -2
- package/src/client/scope.ts +22 -3
- package/src/client/views/compat.tsx +50 -44
- package/src/client/views/models.tsx +26 -7
- package/src/client/views/provider-fields.tsx +15 -3
- package/src/client/views/providers.tsx +31 -26
- package/src/compat.ts +18 -4
- package/src/config.ts +147 -11
- package/src/deepseek-routes.ts +139 -0
- package/src/discovery.ts +63 -11
- package/src/index.ts +6 -3
- package/src/inherit.ts +11 -3
- package/src/profiles-deepseek.ts +282 -0
- package/src/profiles.ts +71 -21
- package/src/resolve-dsh.ts +104 -8
- package/src/service.ts +64 -13
package/src/client/draft.ts
CHANGED
|
@@ -38,6 +38,8 @@ export interface ModelDraft {
|
|
|
38
38
|
input: InputDraft
|
|
39
39
|
reasoningEfforts: ReasoningDraft
|
|
40
40
|
compat: Record<string, unknown>
|
|
41
|
+
/** 卡片未认识的字段原样往返(如 adapter: deepseek 的 imagePixelBudget 等)。 */
|
|
42
|
+
extra: Record<string, unknown>
|
|
41
43
|
}
|
|
42
44
|
|
|
43
45
|
export interface ProviderDraft {
|
|
@@ -61,6 +63,8 @@ export interface ProviderDraft {
|
|
|
61
63
|
maxRequestImageBytes: string
|
|
62
64
|
retryPolicy: unknown
|
|
63
65
|
models: ModelDraft[]
|
|
66
|
+
/** 卡片未认识的字段原样往返(如 adapter/thinking/filesApiTimeoutMs 等)。 */
|
|
67
|
+
extra: Record<string, unknown>
|
|
64
68
|
}
|
|
65
69
|
|
|
66
70
|
export interface Draft {
|
|
@@ -100,7 +104,10 @@ export function pairsToHeaders(pairs: HeaderPair[]): Record<string, string> | un
|
|
|
100
104
|
}
|
|
101
105
|
|
|
102
106
|
function inputFromWire(list: string[] | undefined): InputDraft {
|
|
103
|
-
return {
|
|
107
|
+
return {
|
|
108
|
+
text: list?.includes('text') ?? false,
|
|
109
|
+
image: list?.includes('image') ?? false,
|
|
110
|
+
}
|
|
104
111
|
}
|
|
105
112
|
|
|
106
113
|
function inputToWire(input: InputDraft): string[] | undefined {
|
|
@@ -110,7 +117,9 @@ function inputToWire(input: InputDraft): string[] | undefined {
|
|
|
110
117
|
return out.length > 0 ? out : undefined
|
|
111
118
|
}
|
|
112
119
|
|
|
113
|
-
function reasoningFromWire(
|
|
120
|
+
function reasoningFromWire(
|
|
121
|
+
value: false | Record<string, string | null> | undefined,
|
|
122
|
+
): ReasoningDraft {
|
|
114
123
|
if (value === false) return { nonReasoning: true, levels: {} }
|
|
115
124
|
const levels: Record<string, string> = {}
|
|
116
125
|
for (const [level, line] of Object.entries(value ?? {})) levels[level] = line ?? ''
|
|
@@ -126,7 +135,9 @@ function reasoningToWire(value: ReasoningDraft): false | Record<string, string>
|
|
|
126
135
|
return Object.keys(out).length > 0 ? out : undefined
|
|
127
136
|
}
|
|
128
137
|
|
|
129
|
-
function budgetFromWire(
|
|
138
|
+
function budgetFromWire(
|
|
139
|
+
value: { minimal: number; low: number; medium: number; high: number } | undefined,
|
|
140
|
+
): BudgetDraft {
|
|
130
141
|
return {
|
|
131
142
|
minimal: numToText(value?.minimal),
|
|
132
143
|
low: numToText(value?.low),
|
|
@@ -135,13 +146,17 @@ function budgetFromWire(value: { minimal: number; low: number; medium: number; h
|
|
|
135
146
|
}
|
|
136
147
|
}
|
|
137
148
|
|
|
138
|
-
function budgetToWire(
|
|
149
|
+
function budgetToWire(
|
|
150
|
+
value: BudgetDraft,
|
|
151
|
+
): { minimal: number; low: number; medium: number; high: number } | undefined {
|
|
139
152
|
const out: Record<string, number> = {}
|
|
140
153
|
for (const key of ['minimal', 'low', 'medium', 'high'] as const) {
|
|
141
154
|
const num = toNum(value[key])
|
|
142
155
|
if (num !== undefined) out[key] = num
|
|
143
156
|
}
|
|
144
|
-
return Object.keys(out).length > 0
|
|
157
|
+
return Object.keys(out).length > 0
|
|
158
|
+
? (out as { minimal: number; low: number; medium: number; high: number })
|
|
159
|
+
: undefined
|
|
145
160
|
}
|
|
146
161
|
|
|
147
162
|
/** 剔除空值:undefined / '' / 空数组 / 空对象。 */
|
|
@@ -156,6 +171,50 @@ function omitEmpty(obj: Record<string, unknown>): Record<string, unknown> {
|
|
|
156
171
|
return out
|
|
157
172
|
}
|
|
158
173
|
|
|
174
|
+
/** 卡片已认识的 wire 字段(其余进 extra 原样往返)。 */
|
|
175
|
+
const KNOWN_MODEL_KEYS = new Set([
|
|
176
|
+
'id',
|
|
177
|
+
'extends',
|
|
178
|
+
'name',
|
|
179
|
+
'contextWindow',
|
|
180
|
+
'maxTokens',
|
|
181
|
+
'input',
|
|
182
|
+
'reasoningEfforts',
|
|
183
|
+
'compat',
|
|
184
|
+
])
|
|
185
|
+
|
|
186
|
+
const KNOWN_PROVIDER_KEYS = new Set([
|
|
187
|
+
'extends',
|
|
188
|
+
'displayName',
|
|
189
|
+
'api',
|
|
190
|
+
'baseURL',
|
|
191
|
+
'apiKeyEnv',
|
|
192
|
+
'headers',
|
|
193
|
+
'compat',
|
|
194
|
+
'defaultContextWindow',
|
|
195
|
+
'defaultMaxTokens',
|
|
196
|
+
'defaultInput',
|
|
197
|
+
'reasoning',
|
|
198
|
+
'thinkingBudgets',
|
|
199
|
+
'cacheRetention',
|
|
200
|
+
'transport',
|
|
201
|
+
'timeoutMs',
|
|
202
|
+
'websocketConnectTimeoutMs',
|
|
203
|
+
'streamIdleTimeoutMs',
|
|
204
|
+
'maxRequestImageBytes',
|
|
205
|
+
'retryPolicy',
|
|
206
|
+
'models',
|
|
207
|
+
])
|
|
208
|
+
|
|
209
|
+
/** 摘出 wire 对象里卡片未认识的字段(undefined 值不保留)。 */
|
|
210
|
+
function extraOf(wire: Record<string, unknown>, known: Set<string>): Record<string, unknown> {
|
|
211
|
+
const out: Record<string, unknown> = {}
|
|
212
|
+
for (const [key, value] of Object.entries(wire)) {
|
|
213
|
+
if (!known.has(key) && value !== undefined) out[key] = value
|
|
214
|
+
}
|
|
215
|
+
return out
|
|
216
|
+
}
|
|
217
|
+
|
|
159
218
|
function modelDraftFromWire(model: WireModel): ModelDraft {
|
|
160
219
|
return {
|
|
161
220
|
id: model.id,
|
|
@@ -166,21 +225,25 @@ function modelDraftFromWire(model: WireModel): ModelDraft {
|
|
|
166
225
|
input: inputFromWire(model.input),
|
|
167
226
|
reasoningEfforts: reasoningFromWire(model.reasoningEfforts),
|
|
168
227
|
compat: { ...(model.compat ?? {}) },
|
|
228
|
+
extra: extraOf(model as Record<string, unknown>, KNOWN_MODEL_KEYS),
|
|
169
229
|
}
|
|
170
230
|
}
|
|
171
231
|
|
|
172
232
|
function modelToWire(model: ModelDraft): WireModel {
|
|
173
233
|
const reasoningEfforts = reasoningToWire(model.reasoningEfforts)
|
|
174
|
-
return
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
234
|
+
return {
|
|
235
|
+
...model.extra,
|
|
236
|
+
...(omitEmpty({
|
|
237
|
+
id: model.id.trim(),
|
|
238
|
+
extends: model.extends.trim(),
|
|
239
|
+
name: model.name.trim(),
|
|
240
|
+
contextWindow: toNum(model.contextWindow),
|
|
241
|
+
maxTokens: toNum(model.maxTokens),
|
|
242
|
+
input: inputToWire(model.input),
|
|
243
|
+
...(reasoningEfforts === undefined ? {} : { reasoningEfforts }),
|
|
244
|
+
compat: model.compat,
|
|
245
|
+
}) as Record<string, unknown>),
|
|
246
|
+
} as unknown as WireModel
|
|
184
247
|
}
|
|
185
248
|
|
|
186
249
|
function providerDraftFromWire(provider: WireProvider): ProviderDraft {
|
|
@@ -205,32 +268,36 @@ function providerDraftFromWire(provider: WireProvider): ProviderDraft {
|
|
|
205
268
|
maxRequestImageBytes: numToText(provider.maxRequestImageBytes),
|
|
206
269
|
retryPolicy: provider.retryPolicy,
|
|
207
270
|
models: (provider.models ?? []).map(modelDraftFromWire),
|
|
271
|
+
extra: extraOf(provider as Record<string, unknown>, KNOWN_PROVIDER_KEYS),
|
|
208
272
|
}
|
|
209
273
|
}
|
|
210
274
|
|
|
211
275
|
function providerToWire(provider: ProviderDraft): WireProvider {
|
|
212
|
-
return
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
276
|
+
return {
|
|
277
|
+
...provider.extra,
|
|
278
|
+
...(omitEmpty({
|
|
279
|
+
extends: provider.extends.trim(),
|
|
280
|
+
displayName: provider.displayName.trim(),
|
|
281
|
+
api: provider.api,
|
|
282
|
+
baseURL: provider.baseURL.trim(),
|
|
283
|
+
apiKeyEnv: provider.apiKeyEnv.trim(),
|
|
284
|
+
headers: pairsToHeaders(provider.headers),
|
|
285
|
+
compat: provider.compat,
|
|
286
|
+
defaultContextWindow: toNum(provider.defaultContextWindow),
|
|
287
|
+
defaultMaxTokens: toNum(provider.defaultMaxTokens),
|
|
288
|
+
input: inputToWire(provider.input),
|
|
289
|
+
reasoning: provider.reasoning,
|
|
290
|
+
thinkingBudgets: budgetToWire(provider.thinkingBudgets),
|
|
291
|
+
cacheRetention: provider.cacheRetention,
|
|
292
|
+
transport: provider.transport,
|
|
293
|
+
timeoutMs: toNum(provider.timeoutMs),
|
|
294
|
+
websocketConnectTimeoutMs: toNum(provider.websocketConnectTimeoutMs),
|
|
295
|
+
streamIdleTimeoutMs: toNum(provider.streamIdleTimeoutMs),
|
|
296
|
+
maxRequestImageBytes: toNum(provider.maxRequestImageBytes),
|
|
297
|
+
retryPolicy: provider.retryPolicy,
|
|
298
|
+
models: provider.models.map(modelToWire),
|
|
299
|
+
}) as Record<string, unknown>),
|
|
300
|
+
} as unknown as WireProvider
|
|
234
301
|
}
|
|
235
302
|
|
|
236
303
|
export function emptyProviderDraft(): ProviderDraft {
|
|
@@ -255,6 +322,7 @@ export function emptyProviderDraft(): ProviderDraft {
|
|
|
255
322
|
maxRequestImageBytes: '',
|
|
256
323
|
retryPolicy: undefined,
|
|
257
324
|
models: [],
|
|
325
|
+
extra: {},
|
|
258
326
|
}
|
|
259
327
|
}
|
|
260
328
|
|
|
@@ -268,6 +336,7 @@ export function emptyModelDraft(): ModelDraft {
|
|
|
268
336
|
input: { text: false, image: false },
|
|
269
337
|
reasoningEfforts: { nonReasoning: false, levels: {} },
|
|
270
338
|
compat: {},
|
|
339
|
+
extra: {},
|
|
271
340
|
}
|
|
272
341
|
}
|
|
273
342
|
|
|
@@ -278,7 +347,10 @@ export function draftFromValue(value: ConfigValue): Draft {
|
|
|
278
347
|
catalogRefreshHours: String(value.catalogRefreshHours),
|
|
279
348
|
catalogProxy: value.catalogProxy,
|
|
280
349
|
providers: Object.fromEntries(
|
|
281
|
-
Object.entries(value.providers).map(([route, provider]) => [
|
|
350
|
+
Object.entries(value.providers).map(([route, provider]) => [
|
|
351
|
+
route,
|
|
352
|
+
providerDraftFromWire(provider),
|
|
353
|
+
]),
|
|
282
354
|
),
|
|
283
355
|
}
|
|
284
356
|
}
|
package/src/client/fields.tsx
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* 视觉对齐官方卡片字段(label + hint + 控件纵列),样式类前缀 lpc-。
|
|
4
4
|
* @module llm-pi/client/fields
|
|
5
5
|
*/
|
|
6
|
-
import { useEffect, useState
|
|
6
|
+
import { type ReactElement, useEffect, useState } from 'react'
|
|
7
7
|
|
|
8
8
|
import type { HeaderPair } from './draft.ts'
|
|
9
9
|
|
|
@@ -170,6 +170,7 @@ export function KeyValueEditor(props: KeyValueEditorProps): ReactElement {
|
|
|
170
170
|
</label>
|
|
171
171
|
</div>
|
|
172
172
|
{props.pairs.map((pair, index) => (
|
|
173
|
+
// biome-ignore lint/suspicious/noArrayIndexKey: 可编辑 KV 行无稳定 id,行序即身份(增删行经 onEdit 整体回写)
|
|
173
174
|
<div className="lpc-kvRow" key={index}>
|
|
174
175
|
<input
|
|
175
176
|
id={index === 0 ? props.id : undefined}
|
|
@@ -239,6 +240,7 @@ export interface JsonFieldProps {
|
|
|
239
240
|
|
|
240
241
|
export function JsonField(props: JsonFieldProps): ReactElement {
|
|
241
242
|
const [text, setText] = useState(() => toJsonText(props.value))
|
|
243
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: 仅在 epoch 递增(外部重置语义)时同步文本,避免编辑中被 value 回写打断
|
|
242
244
|
useEffect(() => {
|
|
243
245
|
setText(toJsonText(props.value))
|
|
244
246
|
}, [props.epoch])
|
package/src/client/i18n.ts
CHANGED
|
@@ -102,7 +102,8 @@ export const en: Record<string, string> = {
|
|
|
102
102
|
catalogUrl: 'models.dev catalog endpoint',
|
|
103
103
|
catalogUrlHint: 'Snapshot data source; usually no change needed.',
|
|
104
104
|
catalogRefreshHours: 'Catalog refresh (hours)',
|
|
105
|
-
catalogRefreshHoursHint:
|
|
105
|
+
catalogRefreshHoursHint:
|
|
106
|
+
'0 = no auto refresh (manual refresh or existing cache); >0 = refresh every N hours.',
|
|
106
107
|
catalogProxy: 'Fetch proxy',
|
|
107
108
|
catalogProxyHint: 'HTTP proxy (e.g. http://127.0.0.1:7890); leave empty for direct.',
|
|
108
109
|
kitSource: 'Module source',
|
|
@@ -151,7 +152,8 @@ export const en: Record<string, string> = {
|
|
|
151
152
|
retryPolicyHint: 'dsh-llm RetryPolicy shape; invalid JSON is not submitted.',
|
|
152
153
|
invalidJson: 'Invalid JSON (this field will not be submitted).',
|
|
153
154
|
compatGroup: 'Compat overrides',
|
|
154
|
-
compatApiHint:
|
|
155
|
+
compatApiHint:
|
|
156
|
+
'When api is unset, fields render per openai-completions; the backend validates per the effective protocol.',
|
|
155
157
|
compatUnset: 'Unset',
|
|
156
158
|
modelsGroup: 'Model catalog',
|
|
157
159
|
addModel: 'Add model',
|
package/src/client/scope.ts
CHANGED
|
@@ -31,7 +31,26 @@ export interface Scope {
|
|
|
31
31
|
|
|
32
32
|
/** connection api.settings 的 RPC 面(本插件用到的三个方法)。 */
|
|
33
33
|
export interface SettingsApi {
|
|
34
|
-
describe(payload?: Record<string, never>): Promise<{
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
describe(payload?: Record<string, never>): Promise<{
|
|
35
|
+
result: {
|
|
36
|
+
ok: boolean
|
|
37
|
+
value?: {
|
|
38
|
+
namespaces: Array<{
|
|
39
|
+
ns: string
|
|
40
|
+
secrets: Array<{ path: string[]; set: boolean }>
|
|
41
|
+
}>
|
|
42
|
+
}
|
|
43
|
+
error?: { message?: string }
|
|
44
|
+
}
|
|
45
|
+
}>
|
|
46
|
+
update(request: {
|
|
47
|
+
ns: string
|
|
48
|
+
patch: Record<string, unknown>
|
|
49
|
+
expectedRevision?: number
|
|
50
|
+
}): Promise<{ result: { ok: boolean; error?: { message?: string } } }>
|
|
51
|
+
replace(request: {
|
|
52
|
+
ns: string
|
|
53
|
+
section: Record<string, unknown>
|
|
54
|
+
expectedRevision?: number
|
|
55
|
+
}): Promise<{ result: { ok: boolean; error?: { message?: string } } }>
|
|
37
56
|
}
|
|
@@ -11,7 +11,10 @@ import { COMPAT_FALLBACK_API, compatFieldSpec, compatFieldsOf } from '../constan
|
|
|
11
11
|
import { CollapseSection, JsonField, SelectField } from '../fields.tsx'
|
|
12
12
|
|
|
13
13
|
/** api 变更后裁剪 compat:只保留新渲染组的字段,避免保存时被后端拒绝。 */
|
|
14
|
-
export function pruneCompatForApi(
|
|
14
|
+
export function pruneCompatForApi(
|
|
15
|
+
compat: Record<string, unknown>,
|
|
16
|
+
api: string,
|
|
17
|
+
): Record<string, unknown> {
|
|
15
18
|
const group = api !== '' && compatFieldsOf(api).length > 0 ? api : COMPAT_FALLBACK_API
|
|
16
19
|
const fields = new Set(compatFieldsOf(group))
|
|
17
20
|
const next: Record<string, unknown> = {}
|
|
@@ -33,7 +36,8 @@ export interface CompatEditorProps {
|
|
|
33
36
|
}
|
|
34
37
|
|
|
35
38
|
export function CompatEditor(props: CompatEditorProps): ReactElement {
|
|
36
|
-
const effective =
|
|
39
|
+
const effective =
|
|
40
|
+
props.api !== '' && compatFieldsOf(props.api).length > 0 ? props.api : COMPAT_FALLBACK_API
|
|
37
41
|
const fields = compatFieldsOf(effective)
|
|
38
42
|
const setField = (field: string, value: unknown): void => {
|
|
39
43
|
const next = { ...props.compat }
|
|
@@ -43,61 +47,63 @@ export function CompatEditor(props: CompatEditorProps): ReactElement {
|
|
|
43
47
|
}
|
|
44
48
|
return (
|
|
45
49
|
<div className={`lpc-field lpc-wide`}>
|
|
46
|
-
<CollapseSection
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
id={`${props.idPrefix}-${field}`}
|
|
57
|
-
label={field}
|
|
58
|
-
value={props.compat[field] === undefined ? '' : String(props.compat[field])}
|
|
59
|
-
options={['true', 'false']}
|
|
60
|
-
unsetLabel={props.t('compatUnset')}
|
|
61
|
-
disabled={props.disabled === true}
|
|
62
|
-
onEdit={(value) => {
|
|
63
|
-
if (value === '') setField(field, undefined)
|
|
64
|
-
else setField(field, value === 'true')
|
|
65
|
-
}}
|
|
66
|
-
/>
|
|
67
|
-
)
|
|
68
|
-
}
|
|
69
|
-
if (spec === 'object') {
|
|
70
|
-
return (
|
|
71
|
-
<JsonField
|
|
72
|
-
key={field}
|
|
73
|
-
id={`${props.idPrefix}-${field}`}
|
|
74
|
-
label={field}
|
|
75
|
-
value={props.compat[field]}
|
|
76
|
-
epoch={props.epoch}
|
|
77
|
-
disabled={props.disabled === true}
|
|
78
|
-
invalidText={props.t('invalidJson')}
|
|
79
|
-
onEdit={(value) => setField(field, value)}
|
|
80
|
-
/>
|
|
81
|
-
)
|
|
82
|
-
}
|
|
50
|
+
<CollapseSection
|
|
51
|
+
id={`${props.idPrefix}-collapse`}
|
|
52
|
+
title={props.t('compatGroup')}
|
|
53
|
+
defaultOpen={false}
|
|
54
|
+
>
|
|
55
|
+
{props.api === '' ? <p className="lpc-hint">{props.t('compatApiHint')}</p> : null}
|
|
56
|
+
<div className="lpc-grid">
|
|
57
|
+
{fields.map((field) => {
|
|
58
|
+
const spec = compatFieldSpec(effective, field)
|
|
59
|
+
if (spec === 'boolean') {
|
|
83
60
|
return (
|
|
84
61
|
<SelectField
|
|
85
62
|
key={field}
|
|
86
63
|
id={`${props.idPrefix}-${field}`}
|
|
87
64
|
label={field}
|
|
88
65
|
value={props.compat[field] === undefined ? '' : String(props.compat[field])}
|
|
89
|
-
options={
|
|
66
|
+
options={['true', 'false']}
|
|
90
67
|
unsetLabel={props.t('compatUnset')}
|
|
91
68
|
disabled={props.disabled === true}
|
|
92
69
|
onEdit={(value) => {
|
|
93
70
|
if (value === '') setField(field, undefined)
|
|
94
|
-
else setField(field, value)
|
|
71
|
+
else setField(field, value === 'true')
|
|
95
72
|
}}
|
|
96
73
|
/>
|
|
97
74
|
)
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
|
|
75
|
+
}
|
|
76
|
+
if (spec === 'object') {
|
|
77
|
+
return (
|
|
78
|
+
<JsonField
|
|
79
|
+
key={field}
|
|
80
|
+
id={`${props.idPrefix}-${field}`}
|
|
81
|
+
label={field}
|
|
82
|
+
value={props.compat[field]}
|
|
83
|
+
epoch={props.epoch}
|
|
84
|
+
disabled={props.disabled === true}
|
|
85
|
+
invalidText={props.t('invalidJson')}
|
|
86
|
+
onEdit={(value) => setField(field, value)}
|
|
87
|
+
/>
|
|
88
|
+
)
|
|
89
|
+
}
|
|
90
|
+
return (
|
|
91
|
+
<SelectField
|
|
92
|
+
key={field}
|
|
93
|
+
id={`${props.idPrefix}-${field}`}
|
|
94
|
+
label={field}
|
|
95
|
+
value={props.compat[field] === undefined ? '' : String(props.compat[field])}
|
|
96
|
+
options={spec}
|
|
97
|
+
unsetLabel={props.t('compatUnset')}
|
|
98
|
+
disabled={props.disabled === true}
|
|
99
|
+
onEdit={(value) => {
|
|
100
|
+
if (value === '') setField(field, undefined)
|
|
101
|
+
else setField(field, value)
|
|
102
|
+
}}
|
|
103
|
+
/>
|
|
104
|
+
)
|
|
105
|
+
})}
|
|
106
|
+
</div>
|
|
101
107
|
</CollapseSection>
|
|
102
108
|
</div>
|
|
103
109
|
)
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* (builtin / models-dev)与 provider 后拉取其 models 列表。
|
|
5
5
|
* @module llm-pi/client/views/models
|
|
6
6
|
*/
|
|
7
|
-
import { useEffect, useState
|
|
7
|
+
import { type ReactElement, useEffect, useState } from 'react'
|
|
8
8
|
|
|
9
9
|
import { fetchCatalog, type WireModelsDevStatus } from '../api.ts'
|
|
10
10
|
import { MODALITIES, THINKING_LEVELS } from '../constants.ts'
|
|
@@ -41,6 +41,7 @@ export function ModelsTable(props: ModelsTableProps): ReactElement {
|
|
|
41
41
|
const [note, setNote] = useState('')
|
|
42
42
|
const listId = `lpc-models-${props.route.replace(/[^a-zA-Z0-9_-]/g, '_')}`
|
|
43
43
|
|
|
44
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: 目录拉取仅随 source 切换重跑;t/defaultProvider 取首帧值即可
|
|
44
45
|
useEffect(() => {
|
|
45
46
|
let alive = true
|
|
46
47
|
setNote(t('catalogLoading'))
|
|
@@ -91,7 +92,9 @@ export function ModelsTable(props: ModelsTableProps): ReactElement {
|
|
|
91
92
|
</button>
|
|
92
93
|
</div>
|
|
93
94
|
<div className="lpc-catalogBar">
|
|
94
|
-
<label className="lpc-catalogLabel" htmlFor={`${listId}-source`}>
|
|
95
|
+
<label className="lpc-catalogLabel" htmlFor={`${listId}-source`}>
|
|
96
|
+
{t('catalogSource')}
|
|
97
|
+
</label>
|
|
95
98
|
<select
|
|
96
99
|
id={`${listId}-source`}
|
|
97
100
|
className="lpc-input lpc-select lpc-catalogSelect"
|
|
@@ -102,7 +105,9 @@ export function ModelsTable(props: ModelsTableProps): ReactElement {
|
|
|
102
105
|
<option value="builtin">builtin</option>
|
|
103
106
|
<option value="models-dev">models-dev</option>
|
|
104
107
|
</select>
|
|
105
|
-
<label className="lpc-catalogLabel" htmlFor={`${listId}-provider`}>
|
|
108
|
+
<label className="lpc-catalogLabel" htmlFor={`${listId}-provider`}>
|
|
109
|
+
{t('catalogProvider')}
|
|
110
|
+
</label>
|
|
106
111
|
<select
|
|
107
112
|
id={`${listId}-provider`}
|
|
108
113
|
className="lpc-input lpc-select lpc-catalogSelect"
|
|
@@ -112,7 +117,9 @@ export function ModelsTable(props: ModelsTableProps): ReactElement {
|
|
|
112
117
|
>
|
|
113
118
|
<option value="">-</option>
|
|
114
119
|
{providerIds.map((id) => (
|
|
115
|
-
<option key={id} value={id}>
|
|
120
|
+
<option key={id} value={id}>
|
|
121
|
+
{id}
|
|
122
|
+
</option>
|
|
116
123
|
))}
|
|
117
124
|
</select>
|
|
118
125
|
</div>
|
|
@@ -124,6 +131,7 @@ export function ModelsTable(props: ModelsTableProps): ReactElement {
|
|
|
124
131
|
</datalist>
|
|
125
132
|
{props.models.map((model, index) => (
|
|
126
133
|
<ModelRow
|
|
134
|
+
// biome-ignore lint/suspicious/noArrayIndexKey: model.id 可重复(手填),index 前缀保证 key 唯一且随行序稳定
|
|
127
135
|
key={`${index}:${model.id}`}
|
|
128
136
|
index={index}
|
|
129
137
|
model={model}
|
|
@@ -160,7 +168,9 @@ export function ModelRow(props: ModelRowProps): ReactElement {
|
|
|
160
168
|
return (
|
|
161
169
|
<div className="lpc-modelRow">
|
|
162
170
|
<div className="lpc-modelHead">
|
|
163
|
-
<span className="lpc-modelTitle">
|
|
171
|
+
<span className="lpc-modelTitle">
|
|
172
|
+
{t('modelRow')} {props.index + 1}
|
|
173
|
+
</span>
|
|
164
174
|
<button
|
|
165
175
|
type="button"
|
|
166
176
|
className="lpc-btn lpc-btnGhost lpc-btnSmall"
|
|
@@ -222,7 +232,11 @@ export function ModelRow(props: ModelRowProps): ReactElement {
|
|
|
222
232
|
label={modality}
|
|
223
233
|
checked={model.input[modality]}
|
|
224
234
|
disabled={props.disabled === true}
|
|
225
|
-
onEdit={(checked) =>
|
|
235
|
+
onEdit={(checked) =>
|
|
236
|
+
props.onPatch({
|
|
237
|
+
input: { ...model.input, [modality]: checked },
|
|
238
|
+
})
|
|
239
|
+
}
|
|
226
240
|
/>
|
|
227
241
|
))}
|
|
228
242
|
</div>
|
|
@@ -282,7 +296,12 @@ export function ReasoningEditor(props: ReasoningEditorProps): ReactElement {
|
|
|
282
296
|
label={level}
|
|
283
297
|
value={value.levels[level] ?? ''}
|
|
284
298
|
disabled={props.disabled === true}
|
|
285
|
-
onEdit={(text) =>
|
|
299
|
+
onEdit={(text) =>
|
|
300
|
+
props.onEdit({
|
|
301
|
+
...value,
|
|
302
|
+
levels: { ...value.levels, [level]: text },
|
|
303
|
+
})
|
|
304
|
+
}
|
|
286
305
|
/>
|
|
287
306
|
))}
|
|
288
307
|
</div>
|
|
@@ -7,7 +7,12 @@
|
|
|
7
7
|
import type { ReactElement } from 'react'
|
|
8
8
|
|
|
9
9
|
import {
|
|
10
|
-
BUDGET_KEYS,
|
|
10
|
+
BUDGET_KEYS,
|
|
11
|
+
CACHE_RETENTION_OPTIONS,
|
|
12
|
+
MODALITIES,
|
|
13
|
+
PROTOCOL_IDS,
|
|
14
|
+
THINKING_LEVELS,
|
|
15
|
+
TRANSPORT_OPTIONS,
|
|
11
16
|
} from '../constants.ts'
|
|
12
17
|
import type { ProviderDraft } from '../draft.ts'
|
|
13
18
|
import { CheckRow, SelectField, TextField } from '../fields.tsx'
|
|
@@ -129,7 +134,9 @@ export function ProviderScalarFields(props: ProviderScalarFieldsProps): ReactEle
|
|
|
129
134
|
value={draft.thinkingBudgets[key]}
|
|
130
135
|
disabled={props.disabled === true}
|
|
131
136
|
onEdit={(value) =>
|
|
132
|
-
props.onPatch({
|
|
137
|
+
props.onPatch({
|
|
138
|
+
thinkingBudgets: { ...draft.thinkingBudgets, [key]: value },
|
|
139
|
+
})
|
|
133
140
|
}
|
|
134
141
|
/>
|
|
135
142
|
))}
|
|
@@ -151,7 +158,12 @@ export function ProviderSelectFields(props: ProviderScalarFieldsProps): ReactEle
|
|
|
151
158
|
options={PROTOCOL_IDS}
|
|
152
159
|
unsetLabel={t('compatUnset')}
|
|
153
160
|
disabled={props.disabled === true}
|
|
154
|
-
onEdit={(value) =>
|
|
161
|
+
onEdit={(value) =>
|
|
162
|
+
props.onPatch({
|
|
163
|
+
api: value,
|
|
164
|
+
compat: pruneCompatForApi(draft.compat, value),
|
|
165
|
+
})
|
|
166
|
+
}
|
|
155
167
|
/>
|
|
156
168
|
<SelectField
|
|
157
169
|
id={`${props.id}-reasoning`}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* 支持新增 route(输入键名)与删除 route。
|
|
5
5
|
* @module llm-pi/client/views/providers
|
|
6
6
|
*/
|
|
7
|
-
import {
|
|
7
|
+
import { type ReactElement, useState } from 'react'
|
|
8
8
|
|
|
9
9
|
import type { ProviderDraft } from '../draft.ts'
|
|
10
10
|
import { CollapseSection, JsonField, KeyValueEditor } from '../fields.tsx'
|
|
@@ -93,8 +93,15 @@ export function ProviderSection(props: ProviderSectionProps): ReactElement {
|
|
|
93
93
|
const [open, setOpen] = useState(false)
|
|
94
94
|
const { route, draft, t } = props
|
|
95
95
|
const id = route.replace(/[^a-zA-Z0-9_-]/g, '_')
|
|
96
|
-
const summary =
|
|
97
|
-
|
|
96
|
+
const summary =
|
|
97
|
+
draft.api !== '' ? draft.api : draft.extends !== '' ? `extends ${draft.extends}` : ''
|
|
98
|
+
const fieldProps = {
|
|
99
|
+
id,
|
|
100
|
+
draft,
|
|
101
|
+
disabled: props.disabled === true,
|
|
102
|
+
t,
|
|
103
|
+
onPatch: props.onPatch,
|
|
104
|
+
}
|
|
98
105
|
return (
|
|
99
106
|
<div className="lpc-route">
|
|
100
107
|
<div className="lpc-routeHead">
|
|
@@ -135,29 +142,27 @@ export function ProviderSection(props: ProviderSectionProps): ReactElement {
|
|
|
135
142
|
onEdit={(headers) => props.onPatch({ headers })}
|
|
136
143
|
/>
|
|
137
144
|
<CollapseSection id={`${id}-advanced`} title={t('advancedGroup')} defaultOpen={false}>
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
/>
|
|
160
|
-
</>
|
|
145
|
+
<JsonField
|
|
146
|
+
id={`${id}-retry`}
|
|
147
|
+
label={t('retryPolicy')}
|
|
148
|
+
hint={t('retryPolicyHint')}
|
|
149
|
+
invalidText={t('invalidJson')}
|
|
150
|
+
value={draft.retryPolicy}
|
|
151
|
+
epoch={props.epoch}
|
|
152
|
+
disabled={props.disabled === true}
|
|
153
|
+
wide
|
|
154
|
+
onEdit={(retryPolicy) => props.onPatch({ retryPolicy })}
|
|
155
|
+
/>
|
|
156
|
+
<CompatEditor
|
|
157
|
+
idPrefix={`${id}-compat`}
|
|
158
|
+
api={draft.api}
|
|
159
|
+
compat={draft.compat}
|
|
160
|
+
epoch={props.epoch}
|
|
161
|
+
disabled={props.disabled === true}
|
|
162
|
+
wide
|
|
163
|
+
t={t}
|
|
164
|
+
onEdit={(compat) => props.onPatch({ compat })}
|
|
165
|
+
/>
|
|
161
166
|
</CollapseSection>
|
|
162
167
|
<ModelsTable
|
|
163
168
|
route={route}
|