@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.
@@ -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 { text: list?.includes('text') ?? false, image: list?.includes('image') ?? false }
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(value: false | Record<string, string | null> | undefined): ReasoningDraft {
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(value: { minimal: number; low: number; medium: number; high: number } | undefined): BudgetDraft {
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(value: BudgetDraft): { minimal: number; low: number; medium: number; high: number } | undefined {
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 ? (out as { minimal: number; low: number; medium: number; high: number }) : undefined
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 omitEmpty({
175
- id: model.id.trim(),
176
- extends: model.extends.trim(),
177
- name: model.name.trim(),
178
- contextWindow: toNum(model.contextWindow),
179
- maxTokens: toNum(model.maxTokens),
180
- input: inputToWire(model.input),
181
- ...(reasoningEfforts === undefined ? {} : { reasoningEfforts }),
182
- compat: model.compat,
183
- }) as WireModel
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 omitEmpty({
213
- extends: provider.extends.trim(),
214
- displayName: provider.displayName.trim(),
215
- api: provider.api,
216
- baseURL: provider.baseURL.trim(),
217
- apiKeyEnv: provider.apiKeyEnv.trim(),
218
- headers: pairsToHeaders(provider.headers),
219
- compat: provider.compat,
220
- defaultContextWindow: toNum(provider.defaultContextWindow),
221
- defaultMaxTokens: toNum(provider.defaultMaxTokens),
222
- input: inputToWire(provider.input),
223
- reasoning: provider.reasoning,
224
- thinkingBudgets: budgetToWire(provider.thinkingBudgets),
225
- cacheRetention: provider.cacheRetention,
226
- transport: provider.transport,
227
- timeoutMs: toNum(provider.timeoutMs),
228
- websocketConnectTimeoutMs: toNum(provider.websocketConnectTimeoutMs),
229
- streamIdleTimeoutMs: toNum(provider.streamIdleTimeoutMs),
230
- maxRequestImageBytes: toNum(provider.maxRequestImageBytes),
231
- retryPolicy: provider.retryPolicy,
232
- models: provider.models.map(modelToWire),
233
- }) as WireProvider
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]) => [route, providerDraftFromWire(provider)]),
350
+ Object.entries(value.providers).map(([route, provider]) => [
351
+ route,
352
+ providerDraftFromWire(provider),
353
+ ]),
282
354
  ),
283
355
  }
284
356
  }
@@ -3,7 +3,7 @@
3
3
  * 视觉对齐官方卡片字段(label + hint + 控件纵列),样式类前缀 lpc-。
4
4
  * @module llm-pi/client/fields
5
5
  */
6
- import { useEffect, useState, type ReactElement } from 'react'
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])
@@ -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: '0 = no auto refresh (manual refresh or existing cache); >0 = refresh every N hours.',
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: 'When api is unset, fields render per openai-completions; the backend validates per the effective protocol.',
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',
@@ -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<{ result: { ok: boolean; value?: { namespaces: Array<{ ns: string; secrets: Array<{ path: string[]; set: boolean }> }> }; error?: { message?: string } } }>
35
- update(request: { ns: string; patch: Record<string, unknown>; expectedRevision?: number }): Promise<{ result: { ok: boolean; error?: { message?: string } } }>
36
- replace(request: { ns: string; section: Record<string, unknown>; expectedRevision?: number }): Promise<{ result: { ok: boolean; error?: { message?: string } } }>
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(compat: Record<string, unknown>, api: string): Record<string, unknown> {
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 = props.api !== '' && compatFieldsOf(props.api).length > 0 ? props.api : COMPAT_FALLBACK_API
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 id={`${props.idPrefix}-collapse`} title={props.t('compatGroup')} defaultOpen={false}>
47
- <>
48
- {props.api === '' ? <p className="lpc-hint">{props.t('compatApiHint')}</p> : null}
49
- <div className="lpc-grid">
50
- {fields.map((field) => {
51
- const spec = compatFieldSpec(effective, field)
52
- if (spec === 'boolean') {
53
- return (
54
- <SelectField
55
- key={field}
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={spec}
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
- </div>
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, type ReactElement } from 'react'
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`}>{t('catalogSource')}</label>
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`}>{t('catalogProvider')}</label>
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}>{id}</option>
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">{t('modelRow')} {props.index + 1}</span>
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) => props.onPatch({ input: { ...model.input, [modality]: 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) => props.onEdit({ ...value, levels: { ...value.levels, [level]: 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, CACHE_RETENTION_OPTIONS, MODALITIES, PROTOCOL_IDS, THINKING_LEVELS, TRANSPORT_OPTIONS,
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({ thinkingBudgets: { ...draft.thinkingBudgets, [key]: value } })
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) => props.onPatch({ api: value, compat: pruneCompatForApi(draft.compat, 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 { useState, type ReactElement } from 'react'
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 = draft.api !== '' ? draft.api : draft.extends !== '' ? `extends ${draft.extends}` : ''
97
- const fieldProps = { id, draft, disabled: props.disabled === true, t, onPatch: props.onPatch }
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
- <JsonField
140
- id={`${id}-retry`}
141
- label={t('retryPolicy')}
142
- hint={t('retryPolicyHint')}
143
- invalidText={t('invalidJson')}
144
- value={draft.retryPolicy}
145
- epoch={props.epoch}
146
- disabled={props.disabled === true}
147
- wide
148
- onEdit={(retryPolicy) => props.onPatch({ retryPolicy })}
149
- />
150
- <CompatEditor
151
- idPrefix={`${id}-compat`}
152
- api={draft.api}
153
- compat={draft.compat}
154
- epoch={props.epoch}
155
- disabled={props.disabled === true}
156
- wide
157
- t={t}
158
- onEdit={(compat) => props.onPatch({ compat })}
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}