dsh-plugin-subscriptions 0.5.3 → 0.6.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/README.md +45 -6
- package/README.zh.md +43 -4
- package/lib/auth/rpc.d.ts +36 -2
- package/lib/auth/rpc.js +47 -5
- package/lib/client/ImageGenerateToolview.d.ts +1 -1
- package/lib/client/SpeedSelect.d.ts +25 -2
- package/lib/client/SpeedSelect.js +10 -6
- package/lib/client/SubscriptionsSection.d.ts +74 -0
- package/lib/client/SubscriptionsSection.js +325 -4
- package/lib/client/VideoGenerateToolview.d.ts +1 -1
- package/lib/client/index.d.ts +1 -9
- package/lib/client/index.js +7 -4
- package/lib/client/locales.d.ts +28 -0
- package/lib/client/locales.js +28 -0
- package/lib/client.js +458 -10
- package/lib/client.js.map +1 -1
- package/lib/compat.d.ts +36 -0
- package/lib/compat.js +20 -0
- package/lib/index.d.ts +5 -1
- package/lib/index.js +865 -111
- package/lib/model-defaults.d.ts +23 -0
- package/lib/model-defaults.js +237 -0
- package/lib/providers/claude.d.ts +24 -3
- package/lib/providers/claude.js +35 -24
- package/lib/providers/codex.d.ts +21 -0
- package/lib/providers/codex.js +37 -10
- package/lib/providers/common.d.ts +70 -6
- package/lib/providers/common.js +118 -19
- package/lib/providers/copilot.d.ts +10 -0
- package/lib/providers/copilot.js +21 -8
- package/lib/providers/grok.d.ts +21 -0
- package/lib/providers/grok.js +37 -7
- package/lib/providers/pool-usage.d.ts +23 -2
- package/lib/providers/pool-usage.js +70 -15
- package/lib/providers/rate-limit.d.ts +192 -0
- package/lib/providers/rate-limit.js +338 -0
- package/lib/translate/anthropic.js +5 -4
- package/lib/translate/chat-completions.js +5 -4
- package/lib/translate/responses.js +5 -4
- package/package.json +21 -21
- package/lib/providers/antigravity.d.ts +0 -90
- package/lib/providers/antigravity.js +0 -392
- package/lib/translate/antigravity.d.ts +0 -110
- package/lib/translate/antigravity.js +0 -303
|
@@ -19,6 +19,17 @@ import { en } from './locales.js';
|
|
|
19
19
|
const SUBSCRIPTIONS_AUTH_CHANNEL = '/subscriptions-auth';
|
|
20
20
|
/** Poll cadence while a provider login attempt is busy. */
|
|
21
21
|
const POLL_INTERVAL_MS = 2000;
|
|
22
|
+
/**
|
|
23
|
+
* Model count above which the expanded default-effort list also offers a name
|
|
24
|
+
* filter; below it the list is short enough to scan.
|
|
25
|
+
*/
|
|
26
|
+
const MODEL_FILTER_THRESHOLD = 8;
|
|
27
|
+
/**
|
|
28
|
+
* Height cap of the expanded default-effort list, in px. Past it the list
|
|
29
|
+
* scrolls internally so a provider with dozens of models cannot stretch the
|
|
30
|
+
* card (roughly 7 rows, which keeps the next card's header on screen).
|
|
31
|
+
*/
|
|
32
|
+
const MODEL_LIST_MAX_HEIGHT = 260;
|
|
22
33
|
/** Card display metadata, in page order (names are brand names, not translated). */
|
|
23
34
|
const PROVIDERS = [
|
|
24
35
|
{ id: 'codex', name: 'Codex (ChatGPT)' },
|
|
@@ -141,6 +152,46 @@ const styles = {
|
|
|
141
152
|
background: 'var(--dsw-alias-bg-layer-1)', border: '1px solid var(--dsw-alias-border-l2)',
|
|
142
153
|
},
|
|
143
154
|
usageFill: { height: '100%', borderRadius: 3 },
|
|
155
|
+
defaultEffort: {
|
|
156
|
+
display: 'flex', flexDirection: 'column', gap: 6, marginTop: 4,
|
|
157
|
+
borderTop: '1px solid var(--dsw-alias-border-l2)', paddingTop: 8,
|
|
158
|
+
},
|
|
159
|
+
/** The always-visible disclosure header: title, summary, chevron. */
|
|
160
|
+
defaultEffortToggle: {
|
|
161
|
+
boxSizing: 'border-box', display: 'flex', alignItems: 'center', gap: 8,
|
|
162
|
+
width: '100%', padding: 0, border: 'none', background: 'transparent',
|
|
163
|
+
font: 'inherit', textAlign: 'left', cursor: 'pointer',
|
|
164
|
+
},
|
|
165
|
+
defaultEffortChevron: {
|
|
166
|
+
marginLeft: 'auto', flexShrink: 0, fontSize: 10, lineHeight: '18px',
|
|
167
|
+
color: 'var(--dsw-alias-label-tertiary)',
|
|
168
|
+
},
|
|
169
|
+
/** Body of the expanded disclosure: bounded height so a long catalog scrolls. */
|
|
170
|
+
defaultEffortList: {
|
|
171
|
+
display: 'flex', flexDirection: 'column', gap: 6,
|
|
172
|
+
maxHeight: MODEL_LIST_MAX_HEIGHT, overflowY: 'auto', paddingRight: 2,
|
|
173
|
+
},
|
|
174
|
+
defaultEffortRow: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8 },
|
|
175
|
+
defaultEffortName: {
|
|
176
|
+
fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-label-primary)',
|
|
177
|
+
overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
|
|
178
|
+
},
|
|
179
|
+
defaultEffortSaving: {
|
|
180
|
+
marginLeft: 'auto', flexShrink: 0,
|
|
181
|
+
fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-label-tertiary)',
|
|
182
|
+
},
|
|
183
|
+
defaultEffortSelect: {
|
|
184
|
+
maxWidth: 220, flexShrink: 0, height: 28, boxSizing: 'border-box',
|
|
185
|
+
border: '1px solid var(--dsw-alias-border-l2)', borderRadius: 8,
|
|
186
|
+
padding: '0 8px', font: 'inherit', fontSize: 12, lineHeight: '18px',
|
|
187
|
+
background: 'var(--dsw-alias-bg-layer-1)', color: 'var(--dsw-alias-label-primary)',
|
|
188
|
+
},
|
|
189
|
+
defaultEffortFilter: {
|
|
190
|
+
height: 28, width: '100%', boxSizing: 'border-box',
|
|
191
|
+
border: '1px solid var(--dsw-alias-border-l2)', borderRadius: 8,
|
|
192
|
+
padding: '0 8px', font: 'inherit', fontSize: 12, lineHeight: '18px',
|
|
193
|
+
background: 'var(--dsw-alias-bg-layer-1)', color: 'var(--dsw-alias-label-primary)',
|
|
194
|
+
},
|
|
144
195
|
manual: { marginTop: 4, fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-label-secondary)' },
|
|
145
196
|
manualRow: { display: 'flex', gap: 8, marginTop: 6 },
|
|
146
197
|
manualInput: {
|
|
@@ -197,6 +248,16 @@ function dotColor(status) {
|
|
|
197
248
|
return 'var(--dsw-alias-state-success-primary)';
|
|
198
249
|
return 'var(--dsw-alias-label-dimmed)';
|
|
199
250
|
}
|
|
251
|
+
/**
|
|
252
|
+
* Whether a provider has at least one connected account. Multi-account made
|
|
253
|
+
* "logged in" a property of the account list rather than a flag, so every
|
|
254
|
+
* logged-in test goes through this one predicate.
|
|
255
|
+
* @param status - the provider's last reported state, possibly absent.
|
|
256
|
+
* @returns true when the provider serves at least one account.
|
|
257
|
+
*/
|
|
258
|
+
function hasAccount(status) {
|
|
259
|
+
return (status?.accounts.length ?? 0) > 0;
|
|
260
|
+
}
|
|
200
261
|
/**
|
|
201
262
|
* One-line status text for one provider state.
|
|
202
263
|
* @param t - section translate.
|
|
@@ -250,6 +311,65 @@ function messageColor(tone) {
|
|
|
250
311
|
? 'var(--dsw-alias-state-error-primary)'
|
|
251
312
|
: 'var(--dsw-alias-state-success-primary)';
|
|
252
313
|
}
|
|
314
|
+
/**
|
|
315
|
+
* Derive one provider's default-effort section from its catalog and filter.
|
|
316
|
+
* Pure so the collapsed-header counts and the filter stay testable without a
|
|
317
|
+
* DOM: rows come only from models that advertise levels, the count of the rest
|
|
318
|
+
* rides as one line, and the filter matches display name or model id.
|
|
319
|
+
* @param models - the provider's catalog models, or undefined while loading.
|
|
320
|
+
* @param filter - the raw filter input (trimmed and lowercased here).
|
|
321
|
+
* @returns the section's rows and header counts.
|
|
322
|
+
*/
|
|
323
|
+
export function deriveModelDefaultsView(models, filter) {
|
|
324
|
+
const all = models ?? [];
|
|
325
|
+
const withEfforts = all.filter(model => model.efforts.length > 0);
|
|
326
|
+
const query = filter.trim().toLowerCase();
|
|
327
|
+
const shown = query === ''
|
|
328
|
+
? withEfforts
|
|
329
|
+
: withEfforts.filter(model => model.name.toLowerCase().includes(query)
|
|
330
|
+
|| model.id.toLowerCase().includes(query));
|
|
331
|
+
return {
|
|
332
|
+
shown,
|
|
333
|
+
total: withEfforts.length,
|
|
334
|
+
overridden: withEfforts.filter(model => model.configured !== undefined).length,
|
|
335
|
+
withoutEfforts: all.length - withEfforts.length,
|
|
336
|
+
showFilter: withEfforts.length > MODEL_FILTER_THRESHOLD,
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Whether the default-effort catalog needs (re)fetching.
|
|
341
|
+
*
|
|
342
|
+
* Fetching is gated on an *attempt* signature rather than on the payload
|
|
343
|
+
* being empty: an empty answer is a legitimate result (a narrowed
|
|
344
|
+
* `config.providers`, or a catalog that is momentarily unavailable), and
|
|
345
|
+
* treating it as "not loaded yet" re-ran this effect forever. The signature
|
|
346
|
+
* also covers the accounts, so logging a second provider in refetches
|
|
347
|
+
* instead of leaving that card on the previous answer.
|
|
348
|
+
* @param input - the decision inputs.
|
|
349
|
+
* @returns true when the caller should start a fetch.
|
|
350
|
+
*/
|
|
351
|
+
export function shouldFetchModelDefaults(input) {
|
|
352
|
+
if (input.failed)
|
|
353
|
+
return false;
|
|
354
|
+
if (input.loggedIn.length === 0)
|
|
355
|
+
return false;
|
|
356
|
+
// Only an open disclosure pays for the per-model live resolve.
|
|
357
|
+
if (!input.open.some(provider => input.loggedIn.includes(provider)))
|
|
358
|
+
return false;
|
|
359
|
+
return input.loadedFor !== input.signature;
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Stable signature of the accounts a catalog answer depends on. A change
|
|
363
|
+
* means a previous answer is stale (an account arrived or left), so the next
|
|
364
|
+
* open disclosure refetches.
|
|
365
|
+
* @param statuses - the per-provider status snapshot.
|
|
366
|
+
* @returns a signature string, stable across renders with equal accounts.
|
|
367
|
+
*/
|
|
368
|
+
export function modelDefaultsSignature(statuses) {
|
|
369
|
+
return PROVIDERS
|
|
370
|
+
.map(({ id }) => `${id}:${(statuses[id]?.accounts ?? []).map(account => account.key).sort().join(',')}`)
|
|
371
|
+
.join('|');
|
|
372
|
+
}
|
|
253
373
|
/**
|
|
254
374
|
* The Subscriptions settings page component.
|
|
255
375
|
* @param props - the slot inject face ({@link SubscriptionsSectionInjected}).
|
|
@@ -289,6 +409,24 @@ export function SubscriptionsSection(props) {
|
|
|
289
409
|
const [proxyTesting, setProxyTesting] = useState(false);
|
|
290
410
|
const [proxyMessage, setProxyMessage] = useState(undefined);
|
|
291
411
|
const [proxyTestResult, setProxyTestResult] = useState(undefined);
|
|
412
|
+
/** Per-model default-effort picker state as answered by `modelDefaults`. */
|
|
413
|
+
const [modelDefaults, setModelDefaults] = useState({});
|
|
414
|
+
const [modelDefaultsLoading, setModelDefaultsLoading] = useState(false);
|
|
415
|
+
const [modelDefaultsLoadError, setModelDefaultsLoadError] = useState(undefined);
|
|
416
|
+
/** One set in flight: the `${provider}/${model}` key. */
|
|
417
|
+
const [modelDefaultsSaving, setModelDefaultsSaving] = useState(undefined);
|
|
418
|
+
/** Per-model save failures, keyed `${provider}/${model}`. */
|
|
419
|
+
const [modelDefaultsSaveErrors, setModelDefaultsSaveErrors] = useState({});
|
|
420
|
+
/** Providers whose default-effort disclosure is open (collapsed by default). */
|
|
421
|
+
const [modelDefaultsOpen, setModelDefaultsOpen] = useState({});
|
|
422
|
+
/** Per-provider name filter of the expanded list. */
|
|
423
|
+
const [modelDefaultsFilters, setModelDefaultsFilters] = useState({});
|
|
424
|
+
/** Account signature the last completed catalog fetch was answered for. */
|
|
425
|
+
const [modelDefaultsLoadedFor, setModelDefaultsLoadedFor] = useState(undefined);
|
|
426
|
+
/** Optimistic in-flight selections, keyed `${provider}/${model}` ('' = follow provider). */
|
|
427
|
+
const [modelDefaultsPending, setModelDefaultsPending] = useState({});
|
|
428
|
+
/** Guard the catalog effect against concurrent loads. */
|
|
429
|
+
const modelDefaultsInflightRef = useRef(false);
|
|
292
430
|
const setProviderError = useCallback((provider, message) => {
|
|
293
431
|
if (!mountedRef.current)
|
|
294
432
|
return;
|
|
@@ -366,14 +504,14 @@ export function SubscriptionsSection(props) {
|
|
|
366
504
|
pollersRef.current.clear();
|
|
367
505
|
};
|
|
368
506
|
}, [refresh, startPolling]);
|
|
369
|
-
const loadUsage = useCallback(async (provider, account) => {
|
|
507
|
+
const loadUsage = useCallback(async (provider, account, force = false) => {
|
|
370
508
|
const key = `${provider}:${account}`;
|
|
371
509
|
if (rpc === undefined || usageInflightRef.current.has(key))
|
|
372
510
|
return;
|
|
373
511
|
usageInflightRef.current.add(key);
|
|
374
512
|
setUsageLoading(prev => ({ ...prev, [key]: true }));
|
|
375
513
|
try {
|
|
376
|
-
const usage = await callSubscriptionsAuth(rpc, 'usage', { provider, account });
|
|
514
|
+
const usage = await callSubscriptionsAuth(rpc, 'usage', { provider, account, ...force ? { force: true } : {} });
|
|
377
515
|
if (!mountedRef.current)
|
|
378
516
|
return;
|
|
379
517
|
setUsages(prev => ({ ...prev, [key]: usage }));
|
|
@@ -409,6 +547,146 @@ export function SubscriptionsSection(props) {
|
|
|
409
547
|
setUsages(prev => dropStale(prev, live));
|
|
410
548
|
setUsageErrors(prev => dropStale(prev, live));
|
|
411
549
|
}, [statuses, usages, usageErrors, loadUsage]);
|
|
550
|
+
const loadModelDefaultsData = useCallback(async (signature) => {
|
|
551
|
+
if (rpc === undefined || modelDefaultsInflightRef.current)
|
|
552
|
+
return;
|
|
553
|
+
modelDefaultsInflightRef.current = true;
|
|
554
|
+
setModelDefaultsLoading(true);
|
|
555
|
+
try {
|
|
556
|
+
const catalog = await callSubscriptionsAuth(rpc, 'modelDefaults', {});
|
|
557
|
+
if (!mountedRef.current)
|
|
558
|
+
return;
|
|
559
|
+
const next = {};
|
|
560
|
+
for (const entry of catalog)
|
|
561
|
+
next[entry.provider] = entry;
|
|
562
|
+
setModelDefaults(next);
|
|
563
|
+
setModelDefaultsLoadError(undefined);
|
|
564
|
+
// Latch the answered signature, empty answer included: an empty catalog
|
|
565
|
+
// is a result, not a missing load, and re-deriving "loaded" from the
|
|
566
|
+
// payload re-triggered this fetch on every render.
|
|
567
|
+
setModelDefaultsLoadedFor(signature);
|
|
568
|
+
// The shown state is now authoritative; stale per-row failures would
|
|
569
|
+
// otherwise linger next to rows that are correct again.
|
|
570
|
+
setModelDefaultsSaveErrors({});
|
|
571
|
+
}
|
|
572
|
+
catch (error) {
|
|
573
|
+
if (mountedRef.current)
|
|
574
|
+
setModelDefaultsLoadError(messageOf(error));
|
|
575
|
+
}
|
|
576
|
+
finally {
|
|
577
|
+
modelDefaultsInflightRef.current = false;
|
|
578
|
+
if (mountedRef.current)
|
|
579
|
+
setModelDefaultsLoading(false);
|
|
580
|
+
}
|
|
581
|
+
}, [rpc]);
|
|
582
|
+
// Fetch the default-effort catalogs only once a card's list is expanded: the
|
|
583
|
+
// node half resolves live model info per model, so a collapsed page must not
|
|
584
|
+
// pay for it. One fetch covers every logged-in provider (the node half
|
|
585
|
+
// answers them together), and the levels follow the picker's catalog union
|
|
586
|
+
// across that provider's accounts. The account signature drives refetching,
|
|
587
|
+
// so connecting another provider or account does not leave an open card on
|
|
588
|
+
// the previous answer. Everything resets once the last account logs out.
|
|
589
|
+
useEffect(() => {
|
|
590
|
+
const loggedIn = PROVIDERS.filter(({ id }) => hasAccount(statuses[id])).map(({ id }) => id);
|
|
591
|
+
const signature = modelDefaultsSignature(statuses);
|
|
592
|
+
if (loggedIn.length > 0) {
|
|
593
|
+
const open = PROVIDERS.filter(({ id }) => modelDefaultsOpen[id] === true).map(({ id }) => id);
|
|
594
|
+
if (shouldFetchModelDefaults({
|
|
595
|
+
loggedIn,
|
|
596
|
+
open,
|
|
597
|
+
loadedFor: modelDefaultsLoadedFor,
|
|
598
|
+
signature,
|
|
599
|
+
failed: modelDefaultsLoadError !== undefined,
|
|
600
|
+
})) {
|
|
601
|
+
void loadModelDefaultsData(signature);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
else if (modelDefaultsLoadedFor !== undefined
|
|
605
|
+
|| Object.keys(modelDefaults).length > 0
|
|
606
|
+
|| Object.keys(modelDefaultsOpen).length > 0) {
|
|
607
|
+
setModelDefaults({});
|
|
608
|
+
setModelDefaultsSaveErrors({});
|
|
609
|
+
setModelDefaultsLoadError(undefined);
|
|
610
|
+
setModelDefaultsLoadedFor(undefined);
|
|
611
|
+
setModelDefaultsLoading(false);
|
|
612
|
+
setModelDefaultsOpen({});
|
|
613
|
+
setModelDefaultsFilters({});
|
|
614
|
+
}
|
|
615
|
+
}, [
|
|
616
|
+
statuses,
|
|
617
|
+
modelDefaults,
|
|
618
|
+
modelDefaultsOpen,
|
|
619
|
+
modelDefaultsLoadError,
|
|
620
|
+
modelDefaultsLoadedFor,
|
|
621
|
+
loadModelDefaultsData,
|
|
622
|
+
]);
|
|
623
|
+
/** Open or close one provider's default-effort disclosure. */
|
|
624
|
+
const toggleModelDefaults = useCallback((provider) => {
|
|
625
|
+
setModelDefaultsOpen(prev => ({ ...prev, [provider]: prev[provider] !== true }));
|
|
626
|
+
}, []);
|
|
627
|
+
const setModelDefault = useCallback(async (provider, model, effort) => {
|
|
628
|
+
if (rpc === undefined)
|
|
629
|
+
return;
|
|
630
|
+
const key = `${provider}/${model}`;
|
|
631
|
+
setModelDefaultsSaving(key);
|
|
632
|
+
// Hold the picked level locally for the duration of the save: the select
|
|
633
|
+
// is controlled by server state, which only updates after the round trip,
|
|
634
|
+
// so without this the row visibly snaps back to "Follow provider" (greyed
|
|
635
|
+
// out) mid-save and reads as a rejected change.
|
|
636
|
+
setModelDefaultsPending(prev => ({ ...prev, [key]: effort ?? '' }));
|
|
637
|
+
setModelDefaultsSaveErrors((prev) => {
|
|
638
|
+
const next = { ...prev };
|
|
639
|
+
delete next[key];
|
|
640
|
+
return next;
|
|
641
|
+
});
|
|
642
|
+
try {
|
|
643
|
+
await callSubscriptionsAuth(rpc, 'setModelDefault', {
|
|
644
|
+
provider,
|
|
645
|
+
model,
|
|
646
|
+
...(effort === undefined ? {} : { effort }),
|
|
647
|
+
});
|
|
648
|
+
if (!mountedRef.current)
|
|
649
|
+
return;
|
|
650
|
+
setModelDefaults((prev) => {
|
|
651
|
+
const section = prev[provider];
|
|
652
|
+
if (section === undefined)
|
|
653
|
+
return prev;
|
|
654
|
+
return {
|
|
655
|
+
...prev,
|
|
656
|
+
[provider]: {
|
|
657
|
+
...section,
|
|
658
|
+
models: section.models.map((entry) => {
|
|
659
|
+
if (entry.id !== model)
|
|
660
|
+
return entry;
|
|
661
|
+
if (effort !== undefined)
|
|
662
|
+
return { ...entry, configured: effort };
|
|
663
|
+
// Cleared: drop the key rather than keep the stale level, or the
|
|
664
|
+
// select would snap back and the header would keep counting it.
|
|
665
|
+
const { configured: _cleared, ...rest } = entry;
|
|
666
|
+
return rest;
|
|
667
|
+
}),
|
|
668
|
+
},
|
|
669
|
+
};
|
|
670
|
+
});
|
|
671
|
+
}
|
|
672
|
+
catch (error) {
|
|
673
|
+
if (mountedRef.current)
|
|
674
|
+
setModelDefaultsSaveErrors((prev) => ({ ...prev, [key]: messageOf(error) }));
|
|
675
|
+
}
|
|
676
|
+
finally {
|
|
677
|
+
if (mountedRef.current) {
|
|
678
|
+
setModelDefaultsSaving(current => current === key ? undefined : current);
|
|
679
|
+
// Drop the optimistic value: on success the server state now carries
|
|
680
|
+
// it, on failure the row must fall back to the real stored level
|
|
681
|
+
// rather than keep showing a change that did not land.
|
|
682
|
+
setModelDefaultsPending((prev) => {
|
|
683
|
+
const next = { ...prev };
|
|
684
|
+
delete next[key];
|
|
685
|
+
return next;
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
}, [rpc]);
|
|
412
690
|
const login = useCallback(async (provider, method) => {
|
|
413
691
|
if (rpc === undefined)
|
|
414
692
|
return;
|
|
@@ -625,12 +903,55 @@ export function SubscriptionsSection(props) {
|
|
|
625
903
|
return (_jsxs("div", { style: styles.accountRow, children: [_jsxs("div", { style: styles.accountHeader, children: [_jsx("button", { type: "button", style: styles.starButton, title: account.isDefault ? t('defaultBadge') : t('setDefault'), onClick: () => {
|
|
626
904
|
if (!account.isDefault)
|
|
627
905
|
void setDefault(id, account.key);
|
|
628
|
-
}, children: account.isDefault ? '★' : '☆' }), _jsx("span", { style: styles.accountName, children: display }), account.plan !== undefined && (_jsx("span", { style: styles.usagePlan, children: account.plan })), account.expiresAt !== undefined && (_jsx("span", { style: styles.statusLine, children: t('accountExpires', { date: new Date(account.expiresAt).toLocaleString() }) })), _jsx("button", { type: "button", style: { ...styles.button, marginLeft: 'auto', flexShrink: 0 }, onClick: () => { void logout(id, account.key, display, name); }, children: t('logout') })] }), showUsage && (_jsxs("div", { style: styles.usage, children: [_jsxs("div", { style: styles.usageHeader, children: [_jsx("span", { style: styles.usageTitle, children: t('usageTitle') }), usage?.plan !== undefined && (_jsx("span", { style: styles.usagePlan, children: t('usagePlan', { plan: usage.plan }) })), _jsx("button", { type: "button", style: { ...styles.usageRefresh, ...usageLoading[usageKey] === true ? { opacity: 0.5, cursor: 'default' } : {} }, disabled: usageLoading[usageKey] === true, onClick: () => { void loadUsage(id, account.key); }, children: t('usageRefresh') })] }), usage === undefined && usageError === undefined && (_jsx("p", { style: styles.statusLine, children: t('usageLoading') })), usageError !== undefined && (_jsx("p", { style: styles.errorLine, children: t('usageError', { message: usageError }) })), usage?.windows !== undefined && usage.windows.length === 0 && (_jsx("p", { style: styles.statusLine, children: t('usageEmpty') })), (usage?.windows ?? []).map((window, index) => {
|
|
906
|
+
}, children: account.isDefault ? '★' : '☆' }), _jsx("span", { style: styles.accountName, children: display }), account.plan !== undefined && (_jsx("span", { style: styles.usagePlan, children: account.plan })), account.expiresAt !== undefined && (_jsx("span", { style: styles.statusLine, children: t('accountExpires', { date: new Date(account.expiresAt).toLocaleString() }) })), _jsx("button", { type: "button", style: { ...styles.button, marginLeft: 'auto', flexShrink: 0 }, onClick: () => { void logout(id, account.key, display, name); }, children: t('logout') })] }), showUsage && (_jsxs("div", { style: styles.usage, children: [_jsxs("div", { style: styles.usageHeader, children: [_jsx("span", { style: styles.usageTitle, children: t('usageTitle') }), usage?.plan !== undefined && (_jsx("span", { style: styles.usagePlan, children: t('usagePlan', { plan: usage.plan }) })), _jsx("button", { type: "button", style: { ...styles.usageRefresh, ...usageLoading[usageKey] === true ? { opacity: 0.5, cursor: 'default' } : {} }, disabled: usageLoading[usageKey] === true, onClick: () => { void loadUsage(id, account.key, true); }, children: t('usageRefresh') })] }), usage === undefined && usageError === undefined && (_jsx("p", { style: styles.statusLine, children: t('usageLoading') })), usageError !== undefined && (_jsx("p", { style: styles.errorLine, children: t('usageError', { message: usageError }) })), usage?.windows !== undefined && usage.windows.length === 0 && (_jsx("p", { style: styles.statusLine, children: t('usageEmpty') })), (usage?.windows ?? []).map((window, index) => {
|
|
629
907
|
const percent = Math.min(100, Math.max(0, window.usedPercent));
|
|
630
908
|
return (_jsxs("div", { style: styles.usageRow, children: [_jsxs("div", { style: styles.usageMeta, children: [_jsx("span", { children: usageWindowLabel(t, window) }), _jsxs("span", { children: [`${String(Math.round(percent))}%`, window.resetsAt !== undefined
|
|
631
909
|
&& ` · ${t('usageResets', { date: new Date(window.resetsAt).toLocaleString() })}`] })] }), _jsx("div", { style: styles.usageTrack, children: _jsx("div", { style: { ...styles.usageFill, width: `${String(percent)}%`, background: usageBarColor(percent) } }) })] }, index));
|
|
632
910
|
})] }))] }, account.key));
|
|
633
|
-
}), _jsxs("div", { style: styles.actions, children: [!busy && accounts.length === 0 && (_jsx("button", { type: "button", style: styles.button, onClick: () => { void login(id); }, children: t('login') })), !busy && accounts.length > 0 && id === 'claude' && (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", style: styles.button, onClick: () => { void login(id, 'oauth'); }, children: t('addAccountOAuth') }), _jsx("button", { type: "button", style: styles.button, onClick: () => { void login(id, 'keychain'); }, children: t('addAccountKeychain') })] })), !busy && accounts.length > 0 && id !== 'claude' && (_jsx("button", { type: "button", style: styles.button, onClick: () => { void login(id); }, children: t('addAccount') })), busy && (_jsx("button", { type: "button", style: styles.button, onClick: () => { void cancel(id); }, children: t('cancel') }))] }), !busy && accounts.length > 0 && (_jsx("p", { style: styles.statusLine, children: t('addAccountHint') })),
|
|
911
|
+
}), _jsxs("div", { style: styles.actions, children: [!busy && accounts.length === 0 && (_jsx("button", { type: "button", style: styles.button, onClick: () => { void login(id); }, children: t('login') })), !busy && accounts.length > 0 && id === 'claude' && (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", style: styles.button, onClick: () => { void login(id, 'oauth'); }, children: t('addAccountOAuth') }), _jsx("button", { type: "button", style: styles.button, onClick: () => { void login(id, 'keychain'); }, children: t('addAccountKeychain') })] })), !busy && accounts.length > 0 && id !== 'claude' && (_jsx("button", { type: "button", style: styles.button, onClick: () => { void login(id); }, children: t('addAccount') })), busy && (_jsx("button", { type: "button", style: styles.button, onClick: () => { void cancel(id); }, children: t('cancel') }))] }), !busy && accounts.length > 0 && (_jsx("p", { style: styles.statusLine, children: t('addAccountHint') })), accounts.length > 0 && (() => {
|
|
912
|
+
// Collapsed by default: providers with a large catalog (Copilot
|
|
913
|
+
// lists dozens of models) must not push the page down. The
|
|
914
|
+
// header carries the summary so the collapsed state still says
|
|
915
|
+
// how many models are overridden. The section is per provider,
|
|
916
|
+
// not per account: the override keys off the model id, which the
|
|
917
|
+
// pool shares across a provider's accounts.
|
|
918
|
+
const open = modelDefaultsOpen[id] === true;
|
|
919
|
+
const catalog = modelDefaults[id];
|
|
920
|
+
const filter = modelDefaultsFilters[id] ?? '';
|
|
921
|
+
const view = deriveModelDefaultsView(catalog?.models, filter);
|
|
922
|
+
const saveErrors = Object.entries(modelDefaultsSaveErrors).filter(([key]) => key.startsWith(`${id}/`));
|
|
923
|
+
return (_jsxs("div", { style: styles.defaultEffort, children: [_jsxs("button", { type: "button", style: styles.defaultEffortToggle, "aria-expanded": open, onClick: () => { toggleModelDefaults(id); }, children: [_jsx("span", { style: styles.usageTitle, children: t('modelDefaultsTitle') }), _jsx("span", { style: styles.usagePlan, children: catalog === undefined
|
|
924
|
+
? (modelDefaultsLoading ? t('modelDefaultsLoading') : '')
|
|
925
|
+
: view.total === 0
|
|
926
|
+
? t('modelDefaultsSummaryEmpty')
|
|
927
|
+
: view.overridden === 0
|
|
928
|
+
? t('modelDefaultsSummaryNone', { total: view.total })
|
|
929
|
+
: t('modelDefaultsSummary', { total: view.total, configured: view.overridden }) }), _jsx("span", { style: styles.defaultEffortChevron, "aria-hidden": "true", children: open ? '▲' : '▼' })] }), saveErrors.map(([key, message]) => (
|
|
930
|
+
// Named: the failing row may be scrolled out of the
|
|
931
|
+
// bounded list, and several anonymous "Save failed" lines
|
|
932
|
+
// cannot be told apart.
|
|
933
|
+
_jsx("p", { style: styles.errorLine, role: "alert", children: t('modelDefaultsSaveFailedNamed', {
|
|
934
|
+
model: catalog?.models.find(entry => `${id}/${entry.id}` === key)?.name
|
|
935
|
+
?? key.slice(id.length + 1),
|
|
936
|
+
message,
|
|
937
|
+
}) }, key))), open && (_jsxs(_Fragment, { children: [_jsx("p", { style: styles.statusLine, children: t('modelDefaultsHint') }), modelDefaultsLoadError !== undefined && (_jsxs(_Fragment, { children: [_jsx("p", { style: styles.errorLine, children: t('modelDefaultsLoadFailed', { message: modelDefaultsLoadError }) }), _jsx("div", { style: styles.actions, children: _jsx("button", { type: "button", style: styles.button, onClick: () => {
|
|
938
|
+
// Clearing the latch lets the effect pick the
|
|
939
|
+
// fetch back up on the next render.
|
|
940
|
+
setModelDefaultsLoadError(undefined);
|
|
941
|
+
}, children: t('modelDefaultsRetry') }) })] })), modelDefaultsLoadError === undefined && catalog === undefined && (_jsx("p", { style: styles.statusLine, children: t('modelDefaultsLoading') })), view.showFilter && (_jsx("input", { style: styles.defaultEffortFilter, value: filter, placeholder: t('modelDefaultsFilterPlaceholder'), "aria-label": t('modelDefaultsFilterPlaceholder'), onChange: (event) => {
|
|
942
|
+
setModelDefaultsFilters(prev => ({ ...prev, [id]: event.target.value }));
|
|
943
|
+
} })), view.shown.length > 0 && (_jsx("div", { style: styles.defaultEffortList, children: view.shown.map((model) => {
|
|
944
|
+
const rowKey = `${id}/${model.id}`;
|
|
945
|
+
const saving = modelDefaultsSaving === rowKey;
|
|
946
|
+
// Ids carry the provider: a model id alone repeats
|
|
947
|
+
// across cards, and duplicate ids break the label
|
|
948
|
+
// association the select relies on.
|
|
949
|
+
const labelId = `dsh-model-default-${id}-${model.id}`;
|
|
950
|
+
return (_jsxs("div", { style: styles.defaultEffortRow, children: [_jsx("span", { id: labelId, style: styles.defaultEffortName, title: model.id, children: model.name }), saving && (_jsx("span", { style: styles.defaultEffortSaving, children: t('modelDefaultsSaving') })), _jsxs("select", { style: styles.defaultEffortSelect, "aria-labelledby": labelId, value: modelDefaultsPending[rowKey] ?? model.configured ?? '', disabled: saving, onChange: (event) => {
|
|
951
|
+
void setModelDefault(id, model.id, event.target.value === '' ? undefined : event.target.value);
|
|
952
|
+
}, children: [_jsx("option", { value: "", children: t('modelDefaultsFollowProvider') }), model.efforts.map(effort => (_jsx("option", { value: effort.id, children: effort.name }, effort.id)))] })] }, model.id));
|
|
953
|
+
}) })), catalog !== undefined && view.shown.length === 0 && filter.trim() !== '' && (_jsx("p", { style: styles.statusLine, children: t('modelDefaultsFilterEmpty', { query: filter.trim() }) })), view.withoutEfforts > 0 && (_jsx("p", { style: styles.statusLine, children: t('modelDefaultsNoLevels', { count: view.withoutEfforts }) }))] }))] }));
|
|
954
|
+
})(), busy && deviceCode !== undefined && (_jsxs("div", { style: styles.deviceCode, children: [_jsx("span", { style: styles.statusLine, children: t('deviceCodePrompt') }), _jsx("span", { style: styles.deviceCodeText, children: deviceCode.userCode }), _jsxs("div", { style: styles.actions, children: [_jsx("button", { type: "button", style: styles.button, onClick: () => { copyDeviceCode(id, deviceCode.userCode); }, children: copiedCode === id ? t('deviceCodeCopied') : t('deviceCodeCopy') }), _jsx("button", { type: "button", style: styles.button, onClick: () => { window.open(deviceCode.verificationUrl, '_blank', 'noopener'); }, children: t('deviceCodeOpenPage') })] })] })), busy && deviceCode === undefined && (_jsxs("details", { style: styles.manual, children: [_jsx("summary", { children: t('manualSummary') }), _jsxs("div", { style: styles.manualRow, children: [_jsx("input", { style: styles.manualInput, value: manualDrafts[id], placeholder: t('manualPlaceholder'), onChange: event => setManualDrafts(prev => ({ ...prev, [id]: event.target.value })) }), _jsx("button", { type: "button", style: styles.button, onClick: () => { void submitManual(id); }, children: t('submit') })] })] }))] }, id));
|
|
634
955
|
}), proxyOpen && (_jsx("div", { style: styles.modalOverlay, onClick: () => setProxyOpen(false), children: _jsxs("div", { style: styles.modal, onClick: event => event.stopPropagation(), children: [_jsxs("div", { style: styles.modalHeader, children: [_jsx("span", { style: styles.modalTitle, children: t('proxyDialogTitle') }), _jsx("button", { type: "button", style: { ...styles.button, marginLeft: 'auto' }, onClick: () => setProxyOpen(false), children: t('proxyDialogClose') })] }), _jsxs("label", { style: styles.proxyCheck, children: [_jsx("input", { type: "checkbox", checked: proxyEnabled, onChange: event => setProxyEnabled(event.target.checked) }), _jsx("span", { children: t('proxyEnabled') })] }), _jsxs("label", { style: styles.proxyField, children: [_jsx("span", { style: styles.proxyLabel, children: t('proxyUrl') }), _jsx("input", { style: styles.proxyInput, value: proxyUrl, placeholder: t('proxyUrlPlaceholder'), onChange: event => setProxyUrl(event.target.value) }), _jsx("p", { style: styles.proxyHint, children: t('proxyUrlHint') })] }), _jsxs("label", { style: styles.proxyField, children: [_jsx("span", { style: styles.proxyLabel, children: t('proxyUsername') }), _jsx("input", { style: styles.proxyInput, value: proxyUsername, placeholder: t('proxyUsernamePlaceholder'), onChange: event => setProxyUsername(event.target.value) })] }), _jsxs("div", { style: styles.proxyField, children: [_jsx("span", { style: styles.proxyLabel, children: t('proxyPassword') }), _jsx("input", { type: "password", style: styles.proxyInput, value: proxyPassword, placeholder: t('proxyPasswordPlaceholder'), onChange: event => setProxyPassword(event.target.value) }), _jsxs("label", { style: styles.proxyCheck, children: [_jsx("input", { type: "checkbox", checked: proxyClearPassword, onChange: event => setProxyClearPassword(event.target.checked) }), _jsx("span", { children: t('proxyClearPassword') })] })] }), _jsxs("label", { style: styles.proxyField, children: [_jsx("span", { style: styles.proxyLabel, children: t('proxyBypass') }), _jsx("input", { style: styles.proxyInput, value: proxyBypass, placeholder: t('proxyBypassPlaceholder'), onChange: event => setProxyBypass(event.target.value) }), _jsx("p", { style: styles.proxyHint, children: t('proxyBypassHint') })] }), _jsx("p", { style: styles.proxyHint, children: t('proxyNote') }), proxyMessage !== undefined && (_jsx("p", { style: { ...styles.proxyMessage, color: messageColor(proxyMessage.tone) }, children: proxyMessage.text })), proxyTestResult !== undefined && (_jsx("p", { style: {
|
|
635
956
|
...styles.proxyMessage,
|
|
636
957
|
color: proxyTestResult.ok
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ConnectionHandle } from '@deepseek-ai/dsh-api-remotes/client';
|
|
2
|
-
import type { ToolCallBlock } from '@deepseek-ai/dsh-client-
|
|
2
|
+
import type { ToolCallBlock } from '@deepseek-ai/dsh-client-ui-conversation/client';
|
|
3
3
|
import type { SubscriptionsKey } from './locales.js';
|
|
4
4
|
/** Mirror of ui-tool's ToolCallOwnerProps (see ImageGenerateToolview). */
|
|
5
5
|
interface ToolCallOwnerProps {
|
package/lib/client/index.d.ts
CHANGED
|
@@ -1,12 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* Subscription OAuth login page, browser half. Registers the Subscriptions
|
|
3
|
-
* settings section; every login state fact arrives through the node half's
|
|
4
|
-
* `/subscriptions-auth` RPC channel — this plugin holds no credential state of its
|
|
5
|
-
* own. Section copy rides the client locale service: one 'settings.subscriptions'
|
|
6
|
-
* namespace with zh/en dictionaries, rebound per read so the nav label and
|
|
7
|
-
* page text follow the active locale.
|
|
8
|
-
*/
|
|
9
|
-
import type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client';
|
|
1
|
+
import type { Context as ClientContext } from '@deepseek-ai/cordis';
|
|
10
2
|
import type { SubscriptionsKey } from './locales.js';
|
|
11
3
|
export type { SubscriptionsSectionInjected, SubscriptionsSectionProps } from './SubscriptionsSection.js';
|
|
12
4
|
export type { ImageGenerateToolviewInjected, ImageGenerateToolviewProps } from './ImageGenerateToolview.js';
|
package/lib/client/index.js
CHANGED
|
@@ -33,8 +33,8 @@ export function apply(ctx) {
|
|
|
33
33
|
document.head.appendChild(style);
|
|
34
34
|
return () => style.remove();
|
|
35
35
|
}, 'dsh-plugin-subscriptions: settings panel breathing room');
|
|
36
|
-
// The
|
|
37
|
-
//
|
|
36
|
+
// The shell's Context merge types `connection` as the host handle; in the
|
|
37
|
+
// browser shell the same key holds the full client ConnectionHandle.
|
|
38
38
|
const connection = ctx.get('connection');
|
|
39
39
|
const t = ctx.locale.bind(NS);
|
|
40
40
|
const injected = () => ({ rpc: connection.rpc, t });
|
|
@@ -68,13 +68,16 @@ export function apply(ctx) {
|
|
|
68
68
|
// The composer Speed toggle (codex fast tier) sits in the right tool row,
|
|
69
69
|
// just left of the model selector; the framework synthesizes its `t` seat
|
|
70
70
|
// from `locale: NS`, and the inject face binds each session's RPC calls.
|
|
71
|
+
// The current-model read rides ui-model-selection's `modelDirectories`
|
|
72
|
+
// service, resolved lazily so registration order never matters.
|
|
73
|
+
const models = () => ctx.get('modelDirectories');
|
|
71
74
|
ctx.slots.inject('conversation.input.right', () => ctx.slots.register({
|
|
72
75
|
name: 'conversation.input.right',
|
|
73
76
|
id: 'codex-speed',
|
|
74
77
|
order: 0,
|
|
75
78
|
locale: NS,
|
|
76
79
|
inject: (sessionId) => ({
|
|
77
|
-
loadSpeed: createSpeedLoader(connection, sessionId),
|
|
80
|
+
loadSpeed: createSpeedLoader(connection, models, sessionId),
|
|
78
81
|
setSpeed: createSpeedSetter(connection, sessionId),
|
|
79
82
|
}),
|
|
80
83
|
}, SpeedSelect));
|
|
@@ -92,7 +95,7 @@ export function apply(ctx) {
|
|
|
92
95
|
ui: {
|
|
93
96
|
kind: 'popupSelect',
|
|
94
97
|
options: async (session) => {
|
|
95
|
-
const state = await createSpeedLoader(connection, session.sessionId)();
|
|
98
|
+
const state = await createSpeedLoader(connection, models, session.sessionId)();
|
|
96
99
|
if (!state.visible)
|
|
97
100
|
throw new Error(t('commandFastUnavailable'));
|
|
98
101
|
return [
|
package/lib/client/locales.d.ts
CHANGED
|
@@ -37,6 +37,20 @@ export declare const en: {
|
|
|
37
37
|
usageWindow: string;
|
|
38
38
|
usageResets: string;
|
|
39
39
|
usagePlan: string;
|
|
40
|
+
modelDefaultsTitle: string;
|
|
41
|
+
modelDefaultsHint: string;
|
|
42
|
+
modelDefaultsFollowProvider: string;
|
|
43
|
+
modelDefaultsNoLevels: string;
|
|
44
|
+
modelDefaultsLoading: string;
|
|
45
|
+
modelDefaultsLoadFailed: string;
|
|
46
|
+
modelDefaultsRetry: string;
|
|
47
|
+
modelDefaultsSaving: string;
|
|
48
|
+
modelDefaultsSaveFailedNamed: string;
|
|
49
|
+
modelDefaultsSummary: string;
|
|
50
|
+
modelDefaultsSummaryNone: string;
|
|
51
|
+
modelDefaultsSummaryEmpty: string;
|
|
52
|
+
modelDefaultsFilterPlaceholder: string;
|
|
53
|
+
modelDefaultsFilterEmpty: string;
|
|
40
54
|
generating: string;
|
|
41
55
|
image: string;
|
|
42
56
|
viewImage: string;
|
|
@@ -126,6 +140,20 @@ export declare const zh: {
|
|
|
126
140
|
usageWindow: string;
|
|
127
141
|
usageResets: string;
|
|
128
142
|
usagePlan: string;
|
|
143
|
+
modelDefaultsTitle: string;
|
|
144
|
+
modelDefaultsHint: string;
|
|
145
|
+
modelDefaultsFollowProvider: string;
|
|
146
|
+
modelDefaultsNoLevels: string;
|
|
147
|
+
modelDefaultsLoading: string;
|
|
148
|
+
modelDefaultsLoadFailed: string;
|
|
149
|
+
modelDefaultsRetry: string;
|
|
150
|
+
modelDefaultsSaving: string;
|
|
151
|
+
modelDefaultsSaveFailedNamed: string;
|
|
152
|
+
modelDefaultsSummary: string;
|
|
153
|
+
modelDefaultsSummaryNone: string;
|
|
154
|
+
modelDefaultsSummaryEmpty: string;
|
|
155
|
+
modelDefaultsFilterPlaceholder: string;
|
|
156
|
+
modelDefaultsFilterEmpty: string;
|
|
129
157
|
generating: string;
|
|
130
158
|
image: string;
|
|
131
159
|
viewImage: string;
|
package/lib/client/locales.js
CHANGED
|
@@ -37,6 +37,20 @@ export const en = {
|
|
|
37
37
|
usageWindow: 'Window',
|
|
38
38
|
usageResets: 'resets {date}',
|
|
39
39
|
usagePlan: 'Plan: {plan}',
|
|
40
|
+
modelDefaultsTitle: 'Default reasoning effort',
|
|
41
|
+
modelDefaultsHint: 'The model picker preselects this level when you switch to the model; models without levels always follow the provider.',
|
|
42
|
+
modelDefaultsFollowProvider: 'Follow provider',
|
|
43
|
+
modelDefaultsNoLevels: '{count} model(s) advertise no reasoning levels and always follow the provider.',
|
|
44
|
+
modelDefaultsLoading: 'Loading models…',
|
|
45
|
+
modelDefaultsLoadFailed: 'Failed to load models: {message}',
|
|
46
|
+
modelDefaultsRetry: 'Retry',
|
|
47
|
+
modelDefaultsSaving: 'Saving…',
|
|
48
|
+
modelDefaultsSaveFailedNamed: 'Save failed for {model}: {message}',
|
|
49
|
+
modelDefaultsSummary: '{total} model(s) · {configured} overridden',
|
|
50
|
+
modelDefaultsSummaryNone: '{total} model(s) · following the provider',
|
|
51
|
+
modelDefaultsSummaryEmpty: 'No model advertises reasoning levels',
|
|
52
|
+
modelDefaultsFilterPlaceholder: 'Filter models',
|
|
53
|
+
modelDefaultsFilterEmpty: 'No model matches “{query}”.',
|
|
40
54
|
generating: 'Generating image…',
|
|
41
55
|
image: 'image',
|
|
42
56
|
viewImage: 'View image',
|
|
@@ -126,6 +140,20 @@ export const zh = {
|
|
|
126
140
|
usageWindow: '窗口',
|
|
127
141
|
usageResets: '{date} 重置',
|
|
128
142
|
usagePlan: '计划:{plan}',
|
|
143
|
+
modelDefaultsTitle: '默认推理档',
|
|
144
|
+
modelDefaultsHint: '切换到该模型时,模型选择器会预选此档位;没有推理档的模型始终跟随服务商默认。',
|
|
145
|
+
modelDefaultsFollowProvider: '跟随服务商',
|
|
146
|
+
modelDefaultsNoLevels: '{count} 个模型未声明推理档,始终跟随服务商默认。',
|
|
147
|
+
modelDefaultsLoading: '加载模型中…',
|
|
148
|
+
modelDefaultsLoadFailed: '模型加载失败:{message}',
|
|
149
|
+
modelDefaultsRetry: '重试',
|
|
150
|
+
modelDefaultsSaving: '保存中…',
|
|
151
|
+
modelDefaultsSaveFailedNamed: '{model} 保存失败:{message}',
|
|
152
|
+
modelDefaultsSummary: '{total} 个模型 · {configured} 个已覆盖',
|
|
153
|
+
modelDefaultsSummaryNone: '{total} 个模型 · 全部跟随服务商',
|
|
154
|
+
modelDefaultsSummaryEmpty: '没有模型声明推理档',
|
|
155
|
+
modelDefaultsFilterPlaceholder: '筛选模型',
|
|
156
|
+
modelDefaultsFilterEmpty: '没有匹配「{query}」的模型。',
|
|
129
157
|
generating: '正在生成图片…',
|
|
130
158
|
image: '图片',
|
|
131
159
|
viewImage: '查看图片',
|