dsh-task-time-cost 0.0.0-stage → 0.2.8-beta.2
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/CHANGELOG.md +29 -0
- package/COMPATIBILITY.md +26 -0
- package/CONTRIBUTING.md +11 -0
- package/LICENSE +21 -0
- package/README.en.md +60 -0
- package/README.md +59 -2
- package/THIRD_PARTY_NOTICES.md +113 -0
- package/cordis.patch.yml +4 -0
- package/data/baselines.json +12 -0
- package/data/defaults.json +21 -0
- package/data/models-dev-prices.json +1 -0
- package/data/prices.json +7 -0
- package/data/sources.json +15 -0
- package/docs/public/DETAIL.md +1 -0
- package/docs/public/DETAIL.zh.md +1 -0
- package/docs/public/acceptance.md +26 -0
- package/docs/public/guide.en.md +33 -0
- package/docs/public/guide.zh.md +35 -0
- package/docs/public/preview.svg +1 -0
- package/docs/public/privacy.md +13 -0
- package/docs/public/publishing.md +9 -0
- package/icon.svg +1 -0
- package/lib/client.js +175 -0
- package/lib/index.js +22948 -0
- package/lib/index.js.map +7 -0
- package/locale/en.json +7 -0
- package/locale/zh.json +7 -0
- package/package.json +122 -4
- package/scripts/import-public-prices.mjs +35 -0
- package/src/client/INTEGRATION.md +61 -0
- package/src/client/config.ts +24 -0
- package/src/client/details.tsx +126 -0
- package/src/client/format.ts +218 -0
- package/src/client/index.tsx +225 -0
- package/src/client/locale.ts +353 -0
- package/src/client/management.tsx +246 -0
- package/src/client/placement.ts +26 -0
- package/src/client/remote.ts +59 -0
- package/src/client/store.ts +210 -0
- package/src/client/styles.ts +94 -0
- package/src/client/use-panel-placement.ts +54 -0
- package/src/client-entry.ts +12 -0
- package/src/host/config-entry.ts +47 -0
- package/src/host/config.ts +85 -0
- package/src/host/engine.ts +580 -0
- package/src/host/facts.ts +170 -0
- package/src/host/forecast-cost.ts +16 -0
- package/src/host/index.ts +269 -0
- package/src/host/record-schema.ts +175 -0
- package/src/host/repository.ts +167 -0
- package/src/host/runtime.ts +99 -0
- package/src/shared/ai-plan.ts +45 -0
- package/src/shared/baseline-selection.ts +43 -0
- package/src/shared/baselines.ts +135 -0
- package/src/shared/call-size.ts +32 -0
- package/src/shared/config.ts +65 -0
- package/src/shared/forecast.ts +91 -0
- package/src/shared/index.ts +12 -0
- package/src/shared/ledger.ts +38 -0
- package/src/shared/money.ts +46 -0
- package/src/shared/performance.ts +216 -0
- package/src/shared/pricing.ts +293 -0
- package/src/shared/public-prices.ts +57 -0
- package/src/shared/remote-contract.ts +72 -0
- package/src/shared/retained-time.ts +14 -0
- package/src/shared/time.ts +207 -0
- package/src/shared/types.ts +66 -0
- package/src/shared/usage.ts +232 -0
- package/src/shared/wire.ts +134 -0
- package/src/shared/workload.ts +265 -0
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { createPortal } from 'react-dom';
|
|
3
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
4
|
+
import type { ChatSnapshot, TurnTailChatData } from '@deepseek-ai/dsh-client-ui-chat/client';
|
|
5
|
+
import type { EstimateSnapshot, EstimatorRemote } from '../shared/wire.ts';
|
|
6
|
+
import { EstimateDetails } from './details.tsx';
|
|
7
|
+
import { costSummary, costUnconfigured, formatDuration, formatEta, isTerminal, localTiming } from './format.ts';
|
|
8
|
+
import { en, zh, LOCALE_NAMESPACE } from './locale.ts';
|
|
9
|
+
import type { Translate } from './locale.ts';
|
|
10
|
+
import { Management } from './management.tsx';
|
|
11
|
+
import { errorText } from './remote.ts';
|
|
12
|
+
import { SnapshotStore } from './store.ts';
|
|
13
|
+
import { styles } from './styles.ts';
|
|
14
|
+
import { usePanelPlacement } from './use-panel-placement.ts';
|
|
15
|
+
|
|
16
|
+
// Narrow structural surfaces mirror inspected SDK contracts; only Cordis Context is imported.
|
|
17
|
+
interface ProjectionMarker { revision: number; currentTurn: number | null }
|
|
18
|
+
interface SlotProps {
|
|
19
|
+
sessionId: string;
|
|
20
|
+
useProjection: (key: string) => unknown;
|
|
21
|
+
turn?: { turn: number; status: 'open' | 'closed' | 'unknown' };
|
|
22
|
+
seq?: number;
|
|
23
|
+
messageId?: string;
|
|
24
|
+
useChat: <T>(selector: (snapshot: ChatSnapshot) => T) => T;
|
|
25
|
+
}
|
|
26
|
+
interface Slots {
|
|
27
|
+
inject(key: string, install: () => unknown): unknown;
|
|
28
|
+
register(options: { name: string; id?: string; order?: number }, component: React.ComponentType<SlotProps>): unknown;
|
|
29
|
+
}
|
|
30
|
+
interface LocaleSnapshot { active: string; revision: number }
|
|
31
|
+
interface LocaleService {
|
|
32
|
+
register(namespace: string, locale: string, dictionary: Record<string, string>): () => void;
|
|
33
|
+
bind(namespace: string): Translate;
|
|
34
|
+
subscribe(listener: () => void): () => void;
|
|
35
|
+
getSnapshot(): LocaleSnapshot;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function readMarker(value: unknown): ProjectionMarker | null {
|
|
39
|
+
if (!value || typeof value !== 'object') return null;
|
|
40
|
+
if (!('revision' in value) || typeof value.revision !== 'number') return null;
|
|
41
|
+
if (!('currentTurn' in value) || !(value.currentTurn === null || typeof value.currentTurn === 'number')) return null;
|
|
42
|
+
return { revision: value.revision, currentTurn: value.currentTurn };
|
|
43
|
+
}
|
|
44
|
+
const idleSubscribe = () => () => {};
|
|
45
|
+
|
|
46
|
+
/** Compact human token count, matching the host's own usage meta. */
|
|
47
|
+
function compactTokens(value: number | null | undefined): string | null {
|
|
48
|
+
if (value == null || !Number.isFinite(value) || value < 0) return null;
|
|
49
|
+
if (value < 1_000) return String(Math.round(value));
|
|
50
|
+
if (value < 1_000_000) {
|
|
51
|
+
const scaled = value / 1_000;
|
|
52
|
+
return `${scaled >= 100 ? Math.round(scaled) : Number(scaled.toFixed(1))}k`;
|
|
53
|
+
}
|
|
54
|
+
const scaled = value / 1_000_000;
|
|
55
|
+
return `${scaled >= 100 ? Math.round(scaled) : Number(scaled.toFixed(1))}M`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* The bare count, for places whose own label already says "tokens": a row
|
|
60
|
+
* reading `Observed tokens 46.2M tokens` repeats the unit the label supplied.
|
|
61
|
+
*/
|
|
62
|
+
function tokenValue(snapshot: EstimateSnapshot | null, _locale: string, t: Translate): string {
|
|
63
|
+
if (!snapshot) return t('unknown');
|
|
64
|
+
const observed = compactTokens(snapshot.observedTokens);
|
|
65
|
+
if (observed) return observed;
|
|
66
|
+
const estimate = snapshot.estimatedTotalTokens;
|
|
67
|
+
if (!estimate) return t('unknown');
|
|
68
|
+
const low = compactTokens(estimate.low) ?? t('unknown');
|
|
69
|
+
const high = compactTokens(estimate.high) ?? t('unknown');
|
|
70
|
+
return low === high ? low : t('range', { low, high });
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Small clock that carries the task state by colour instead of by text: the
|
|
75
|
+
* line itself must stay a single short ETA, so a dot plus a word would repeat
|
|
76
|
+
* what the conversation already prints above the composer.
|
|
77
|
+
*/
|
|
78
|
+
function ClockIcon({ state }: { state: string }) {
|
|
79
|
+
const live = state === 'running' || state === 'queued';
|
|
80
|
+
return <svg className="dte-icon" data-state={state} viewBox="0 0 16 16" width="13" height="13" aria-hidden="true" focusable="false">
|
|
81
|
+
<circle cx="8" cy="8" r="5.9" fill="none" stroke="currentColor" strokeWidth="1.6" />
|
|
82
|
+
<path d="M8 4.7v3.6l2.4 1.5" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" />
|
|
83
|
+
{live && <circle className="dte-icon-ring" cx="8" cy="8" r="5.9" fill="none" stroke="currentColor" strokeWidth="1.6" />}
|
|
84
|
+
</svg>;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export const inject = ['slots', 'locale', 'remote'];
|
|
88
|
+
|
|
89
|
+
/** Lead wrapper mounts the RPC contribution before calling this function. No remote access occurs at module evaluation. */
|
|
90
|
+
export function applyClient(ctx: Context): void {
|
|
91
|
+
const slots = ctx.get('slots') as Slots;
|
|
92
|
+
const locale = ctx.get('locale') as LocaleService;
|
|
93
|
+
// $mount creates an independent service. A traced root Remote property read
|
|
94
|
+
// requires an inject declaration for that namespace; this bootstrap owns its
|
|
95
|
+
// dynamic mount, so read the completed service explicitly instead.
|
|
96
|
+
const namespace = ctx.get('remote.taskEstimator') as EstimatorRemote | undefined;
|
|
97
|
+
if (!slots || !locale || !namespace) throw new Error('task-estimate: required Client services have not been mounted');
|
|
98
|
+
const remote = namespace;
|
|
99
|
+
const store = new SnapshotStore(remote);
|
|
100
|
+
ctx.effect(() => () => store.dispose());
|
|
101
|
+
ctx.effect(() => locale.register(LOCALE_NAMESPACE, 'en', en));
|
|
102
|
+
ctx.effect(() => locale.register(LOCALE_NAMESPACE, 'zh', zh));
|
|
103
|
+
const t = locale.bind(LOCALE_NAMESPACE);
|
|
104
|
+
const subscribeLocale = (listener: () => void) => locale.subscribe(listener);
|
|
105
|
+
const readLocale = () => locale.getSnapshot();
|
|
106
|
+
|
|
107
|
+
function EstimateWidget({ props, tail }: { props: SlotProps; tail: boolean }) {
|
|
108
|
+
const localeSnapshot = React.useSyncExternalStore(subscribeLocale, readLocale, readLocale);
|
|
109
|
+
const marker = readMarker(props.useProjection('taskEstimator'));
|
|
110
|
+
const targetTurn = tail ? props.turn?.turn ?? null : marker?.currentTurn ?? null;
|
|
111
|
+
const revision = tail && marker?.currentTurn !== targetTurn ? props.seq ?? 0 : marker?.revision;
|
|
112
|
+
const subscribe = React.useCallback((listener: () => void) => store.subscribe(props.sessionId, targetTurn, listener), [props.sessionId, targetTurn]);
|
|
113
|
+
const read = React.useCallback(() => store.getSnapshot(props.sessionId, targetTurn), [props.sessionId, targetTurn]);
|
|
114
|
+
const state = React.useSyncExternalStore(subscribe, read, read);
|
|
115
|
+
React.useEffect(() => { store.projectionChanged(props.sessionId, targetTurn, revision); }, [props.sessionId, targetTurn, revision]);
|
|
116
|
+
const snapshot = state.view?.snapshot ?? null;
|
|
117
|
+
const ticking = !!snapshot && !isTerminal(snapshot.state);
|
|
118
|
+
const frozenNow = React.useCallback(() => state.receivedAt, [state.receivedAt]);
|
|
119
|
+
const now = React.useSyncExternalStore(ticking ? store.clock.subscribe : idleSubscribe, ticking ? store.clock.getSnapshot : frozenNow, frozenNow);
|
|
120
|
+
const [expanded, setExpanded] = React.useState(false);
|
|
121
|
+
const [view, setView] = React.useState<'summary' | 'details' | 'settings'>('summary');
|
|
122
|
+
const summary = React.useRef<HTMLButtonElement>(null);
|
|
123
|
+
const panel = React.useRef<HTMLDivElement>(null);
|
|
124
|
+
const firstAction = React.useRef<HTMLButtonElement>(null);
|
|
125
|
+
const id = React.useId();
|
|
126
|
+
// The body portal keeps the native surface; owned rectangles keep tall
|
|
127
|
+
// management content inside the viewport as its size changes.
|
|
128
|
+
usePanelPlacement(expanded, summary, panel, firstAction);
|
|
129
|
+
const active = !!snapshot && !isTerminal(snapshot.state);
|
|
130
|
+
// A settled turn has no remaining work, so an ETA row would print a
|
|
131
|
+
// meaningless "5s" beside a finished duration.
|
|
132
|
+
const terminal = !!snapshot && !active;
|
|
133
|
+
const timing = snapshot ? localTiming(snapshot, state.receivedAt, now) : null;
|
|
134
|
+
const refresh = () => { void store.refresh(props.sessionId, targetTurn); };
|
|
135
|
+
// Escape closes the panel from anywhere inside it and returns focus to the
|
|
136
|
+
// line that opened it, so the popover never traps a keyboard user.
|
|
137
|
+
React.useEffect(() => {
|
|
138
|
+
if (!expanded) return;
|
|
139
|
+
const onKeyDown = (event: KeyboardEvent) => {
|
|
140
|
+
if (event.key !== 'Escape') return;
|
|
141
|
+
event.preventDefault();
|
|
142
|
+
setExpanded(false);
|
|
143
|
+
summary.current?.focus();
|
|
144
|
+
};
|
|
145
|
+
const onOutside = (event: Event) => {
|
|
146
|
+
const target = event.target as Node | null;
|
|
147
|
+
if (!target || summary.current?.contains(target) || panel.current?.contains(target)) return;
|
|
148
|
+
setExpanded(false);
|
|
149
|
+
};
|
|
150
|
+
document.addEventListener('keydown', onKeyDown);
|
|
151
|
+
document.addEventListener('pointerdown', onOutside, true);
|
|
152
|
+
document.addEventListener('click', onOutside, true);
|
|
153
|
+
return () => {
|
|
154
|
+
document.removeEventListener('keydown', onKeyDown);
|
|
155
|
+
document.removeEventListener('pointerdown', onOutside, true);
|
|
156
|
+
document.removeEventListener('click', onOutside, true);
|
|
157
|
+
};
|
|
158
|
+
}, [expanded]);
|
|
159
|
+
|
|
160
|
+
if (tail && !snapshot && !state.error && !state.pending) return null;
|
|
161
|
+
const sessionRemaining = snapshot?.sessionRemainingMs ?? snapshot?.remainingMs ?? null;
|
|
162
|
+
const elapsed = formatDuration(timing?.activeMs ?? snapshot?.elapsedActiveMs, t);
|
|
163
|
+
const retained = !tail && sessionRemaining === null && state.lastForecast
|
|
164
|
+
&& (!snapshot || state.lastForecast.taskKey === snapshot.taskKey) && (active || !snapshot && targetTurn !== null) ? state.lastForecast : null;
|
|
165
|
+
const eta = formatEta(sessionRemaining ?? retained?.remainingMs, t);
|
|
166
|
+
// Keep the last time reference while the new plan is unavailable. It is
|
|
167
|
+
// frozen and labelled in the dialog, never used as a current cost budget.
|
|
168
|
+
const line = tail ? t('doneElapsed', { elapsed }) : (active && sessionRemaining !== null || retained) ? t('etaEstimate', { value: eta }) : t('titleShort');
|
|
169
|
+
const unconfigured = !!snapshot && costUnconfigured(snapshot);
|
|
170
|
+
const referencePrice = snapshot?.reasons.includes('public-reference-price-not-provider-bill') ?? false;
|
|
171
|
+
return <div className="dte" data-slot={tail ? 'assistant-actions' : 'composer-dock'}>
|
|
172
|
+
<style>{styles}</style>
|
|
173
|
+
<button type="button" className={tail ? 'dte-line dte-line-tail' : 'dte-line'} aria-haspopup="dialog" aria-expanded={expanded} aria-controls={`${id}-panel`}
|
|
174
|
+
aria-label={line} onClick={() => setExpanded((value) => !value)} ref={summary}
|
|
175
|
+
title={terminal ? line : snapshot || retained ? t('etaFull', { value: eta }) : t('title')}>
|
|
176
|
+
<ClockIcon state={snapshot?.state ?? 'idle'} />
|
|
177
|
+
<span className="dte-line-text">{line}</span>
|
|
178
|
+
</button>
|
|
179
|
+
{expanded && createPortal(<div className="dte" style={{ display: 'contents' }}
|
|
180
|
+
onPointerDown={event => event.stopPropagation()} onMouseDown={event => event.stopPropagation()} onClick={event => event.stopPropagation()}><div id={`${id}-panel`} className="dte-pop" role="dialog"
|
|
181
|
+
aria-label={t(tail ? 'turnTimeTitle' : 'titleShort')} ref={panel}>
|
|
182
|
+
<div className="dte-pop-head">
|
|
183
|
+
<span className="dte-pop-title"><ClockIcon state={snapshot?.state ?? 'idle'} />{t(tail ? 'turnTimeTitle' : 'titleShort')}</span>
|
|
184
|
+
<span className="dte-pop-value dte-tabular">{terminal ? elapsed : eta}</span>
|
|
185
|
+
</div>
|
|
186
|
+
<div className="dte-pop-rule" aria-hidden="true" />
|
|
187
|
+
<dl className="dte-summary">
|
|
188
|
+
<dt>{t('activeTime')}</dt><dd>{snapshot ? elapsed : t('unknown')}</dd>
|
|
189
|
+
<dt>{t('estimatedTotal')}</dt><dd>{formatEta(snapshot?.estimatedTotalMs ?? retained?.estimatedTotalMs, t)}</dd>
|
|
190
|
+
<dt>{t('tokenTotal')}</dt><dd>{tokenValue(snapshot, localeSnapshot.active, t)}</dd>
|
|
191
|
+
<dt>{t(referencePrice ? 'referenceCost' : 'knownCost')}</dt><dd>{unconfigured ? t('unpriced') : snapshot ? costSummary(snapshot, localeSnapshot.active, t) : t('unknown')}</dd>
|
|
192
|
+
</dl>
|
|
193
|
+
{retained && <p className="dte-muted dte-small">{t('retainedForecastHint', { time: new Date(retained.updatedAt).toLocaleString(localeSnapshot.active) })}</p>}
|
|
194
|
+
{referencePrice && <p className="dte-muted dte-small">{t('referencePriceHint')}</p>}
|
|
195
|
+
{unconfigured && <p className="dte-muted dte-small">{t('unpricedHint')}</p>}
|
|
196
|
+
{!unconfigured && snapshot && (snapshot.observedCostStatus === 'partial' || snapshot.forecastCostStatus === 'partial') && <p className="dte-muted dte-small">{t('incompleteCostDetail')}</p>}
|
|
197
|
+
{state.error && <p className="dte-error dte-small" role="alert">{errorText(state.error, t)}</p>}
|
|
198
|
+
<div className="dte-pop-actions">
|
|
199
|
+
<button type="button" ref={firstAction} className={view === 'details' ? 'dte-link dte-link-active' : 'dte-link'} aria-pressed={view === 'details'} onClick={() => setView(view === 'details' ? 'summary' : 'details')}>{t('details')}</button>
|
|
200
|
+
<button type="button" className={view === 'settings' ? 'dte-link dte-link-active' : 'dte-link'} aria-pressed={view === 'settings'} onClick={() => setView(view === 'settings' ? 'summary' : 'settings')}>{t('settings')}</button>
|
|
201
|
+
<span className="dte-spacer" />
|
|
202
|
+
{view === 'details' && <button type="button" className="dte-link" disabled={state.pending} onClick={refresh}>{t('refresh')}</button>}
|
|
203
|
+
</div>
|
|
204
|
+
{view === 'details' && (snapshot ? <EstimateDetails snapshot={snapshot} receivedAt={state.receivedAt} now={now} locale={localeSnapshot.active} t={t} /> : <p className="dte-muted">{t(tail ? 'noSnapshot' : 'noTask')}</p>)}
|
|
205
|
+
{view === 'settings' && <Management remote={remote} store={store} t={t} locale={localeSnapshot.active} />}
|
|
206
|
+
</div></div>, document.body)}
|
|
207
|
+
</div>;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function Activity(props: SlotProps) { return <EstimateWidget key={props.sessionId} props={props} tail={false} />; }
|
|
211
|
+
function CompletedAction(props: SlotProps) {
|
|
212
|
+
// Public Chat data identifies the footer's durable closing message. Reading
|
|
213
|
+
// only its identity/Turn avoids attaching a full-Turn duration to each step.
|
|
214
|
+
const closing = props.useChat(snapshot => snapshot.nodes.values().find(node => node.kind === 'turn-tail'
|
|
215
|
+
&& (node.data as TurnTailChatData).closing?.finalNode.messageId === props.messageId));
|
|
216
|
+
if (closing?.kind !== 'turn-tail') return null;
|
|
217
|
+
const turn = closing.location.kind === 'turn' || closing.location.kind === 'step' ? closing.location.turn : undefined;
|
|
218
|
+
if (turn?.status !== 'closed') return null;
|
|
219
|
+
return <EstimateWidget key={`${props.sessionId}:${turn.turn}`} props={{ ...props, turn, seq: closing.anchorSeq }} tail />;
|
|
220
|
+
}
|
|
221
|
+
slots.inject('conversation.composer.dock', () => slots.register({ name: 'conversation.composer.dock', id: 'task-estimate', order: 2 }, Activity));
|
|
222
|
+
slots.inject('conversation.chat.assistant-actions', () => slots.register({ name: 'conversation.chat.assistant-actions', id: 'task-estimate', order: 20 }, CompletedAction));
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export const apply = applyClient;
|
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
export const LOCALE_NAMESPACE = 'task-estimate';
|
|
2
|
+
|
|
3
|
+
export const en = {
|
|
4
|
+
title: 'Task time and cost estimate',
|
|
5
|
+
titleShort: 'Estimates',
|
|
6
|
+
turnTimeTitle: 'Turn duration',
|
|
7
|
+
retainedForecastHint: 'Updating the estimate; using the last reference from {time}.',
|
|
8
|
+
tokenRemainingOutput: 'Estimated remaining output Tokens',
|
|
9
|
+
tokenRemainingNote: 'Visible output and tool arguments only; excludes hidden reasoning and repeated input.',
|
|
10
|
+
reassessingShort: 'Reassessing',
|
|
11
|
+
upTo: '≤{value}',
|
|
12
|
+
details: 'Estimate details',
|
|
13
|
+
settings: 'Settings and calibration',
|
|
14
|
+
close: 'Close',
|
|
15
|
+
refresh: 'Refresh',
|
|
16
|
+
retry: 'Retry',
|
|
17
|
+
loading: 'Loading…',
|
|
18
|
+
updating: 'Updating…',
|
|
19
|
+
unavailable: 'Estimate unavailable',
|
|
20
|
+
noTask: 'Estimates appear when a task starts.',
|
|
21
|
+
noSnapshot: 'No estimate for this turn.',
|
|
22
|
+
scope: 'Current turn only. Ranges are estimates, not guarantees.',
|
|
23
|
+
estimatedRemaining: 'Remaining {value}',
|
|
24
|
+
elapsed: 'Elapsed {value}',
|
|
25
|
+
activeTime: 'Active time',
|
|
26
|
+
pausedTime: 'Waiting for you',
|
|
27
|
+
wallTime: 'Wall time',
|
|
28
|
+
remaining: 'Estimated remaining',
|
|
29
|
+
estimatedTotal: 'Estimated total time',
|
|
30
|
+
estimatedCost: 'Estimated total cost',
|
|
31
|
+
knownCost: 'Known cost',
|
|
32
|
+
recordedCost: 'Recorded cost',
|
|
33
|
+
unknown: 'Unknown',
|
|
34
|
+
notAvailable: 'Not available',
|
|
35
|
+
free: 'Free',
|
|
36
|
+
incompleteCost: 'Unable to estimate the full cost',
|
|
37
|
+
incompleteCostDetail: 'Some calls have no price or forecast. The known amount is only a partial total.',
|
|
38
|
+
partialKnown: 'Partial: {value}',
|
|
39
|
+
referenceCost: 'Reference cost',
|
|
40
|
+
referencePriceHint: 'Automatic public list-price reference. Subscription charges, relay discounts and the provider invoice may differ.',
|
|
41
|
+
noPrice: 'Unpriced calls: {count}',
|
|
42
|
+
incompleteForecast: 'Units without a complete forecast: {count}',
|
|
43
|
+
observedCostStatus: 'Recorded cost coverage',
|
|
44
|
+
forecastCostStatus: 'Forecast coverage',
|
|
45
|
+
costComplete: 'Complete',
|
|
46
|
+
costPartial: 'Partial',
|
|
47
|
+
costUnpriced: 'Unpriced',
|
|
48
|
+
costReference: 'Reference',
|
|
49
|
+
costUnavailable: 'Unavailable',
|
|
50
|
+
stateQueued: 'Queued',
|
|
51
|
+
stateRunning: 'Running',
|
|
52
|
+
stateWaiting: 'Waiting for you',
|
|
53
|
+
stateCompleted: 'Completed',
|
|
54
|
+
stateFailed: 'Failed',
|
|
55
|
+
stateCancelled: 'Cancelled',
|
|
56
|
+
stateInterrupted: 'Interrupted',
|
|
57
|
+
durationMilliseconds: '{value}ms',
|
|
58
|
+
durationSeconds: '{value}s',
|
|
59
|
+
durationMinutes: '{value}m {seconds}s',
|
|
60
|
+
durationMinutesOnly: '{value}m',
|
|
61
|
+
durationHours: '{value}h {minutes}m',
|
|
62
|
+
durationHoursOnly: '{value}h',
|
|
63
|
+
range: '{low}–{high}',
|
|
64
|
+
money: '{currency} {value}',
|
|
65
|
+
moneyRange: '{currency} {low}–{high}',
|
|
66
|
+
listSeparator: ' · ',
|
|
67
|
+
labelSeparator: ': ',
|
|
68
|
+
timeAndCost: '{time} · {cost}',
|
|
69
|
+
count: '{value}',
|
|
70
|
+
tokenCount: '{value} tokens',
|
|
71
|
+
eta: 'ETA {value}', etaFull: 'Estimated {value} remaining', etaShort: 'ETA', etaEstimate: 'Est. {value}',
|
|
72
|
+
doneMeta: '{elapsed} · {tokens}',
|
|
73
|
+
doneElapsed: 'Elapsed {elapsed}',
|
|
74
|
+
sessionMeta: 'Session · {turns} turns',
|
|
75
|
+
sessionEta: 'Session ETA {value}',
|
|
76
|
+
unpriced: 'No matching price',
|
|
77
|
+
unpricedHint: 'No configured or public price matches this model. Time and tokens remain available; a model price can be added in Settings.',
|
|
78
|
+
advancedSettings: 'Advanced parameters',
|
|
79
|
+
tokenTotal: 'Observed tokens',
|
|
80
|
+
tokenInput: 'Input',
|
|
81
|
+
tokenCacheRead: 'Cache read',
|
|
82
|
+
tokenCacheWrite: 'Cache write',
|
|
83
|
+
tokenOutput: 'Output',
|
|
84
|
+
tokenReasoning: 'Reasoning',
|
|
85
|
+
tokenForecast: 'Estimated total tokens',
|
|
86
|
+
tokenCoverage: 'Token coverage',
|
|
87
|
+
tokenComplete: 'Complete',
|
|
88
|
+
tokenPartial: 'Partial',
|
|
89
|
+
tokenUnknown: 'Unknown',
|
|
90
|
+
tokensNote: 'Reasoning and cache tokens are provider-reported categories; they are not added a second time.',
|
|
91
|
+
basis: 'Estimate basis',
|
|
92
|
+
origin: 'Source',
|
|
93
|
+
originHeuristic: 'Heuristic assumptions',
|
|
94
|
+
originPublic: 'Public measurements',
|
|
95
|
+
originLocal: 'Local measurements',
|
|
96
|
+
originMixed: 'Mixed sources',
|
|
97
|
+
taskType: 'Task type',
|
|
98
|
+
taskQuestion: 'Question',
|
|
99
|
+
taskWriting: 'Writing',
|
|
100
|
+
taskFileAnalysis: 'File analysis',
|
|
101
|
+
taskCode: 'Code',
|
|
102
|
+
taskResearch: 'Research',
|
|
103
|
+
taskUnknown: 'Unclassified',
|
|
104
|
+
localSamples: 'Local performance samples',
|
|
105
|
+
workloadSamples: 'Workload samples',
|
|
106
|
+
calls: 'Model calls',
|
|
107
|
+
callCounts: '{completed} completed · {active} active · {failed} failed',
|
|
108
|
+
tools: 'Tools',
|
|
109
|
+
toolCounts: '{completed} completed · {active} active',
|
|
110
|
+
toolTime: 'Tool work time',
|
|
111
|
+
performance: 'Route performance',
|
|
112
|
+
performanceTrust: 'Performance evidence', performanceObserved: 'Observed; range remains a reference', performanceReference: 'Reference only',
|
|
113
|
+
firstContentSource: 'First content source', firstTextSource: 'First text source', speedSource: 'Generation speed source', totalSource: 'Total call time source',
|
|
114
|
+
provider: 'Provider',
|
|
115
|
+
model: 'Model',
|
|
116
|
+
endpoint: 'Endpoint fingerprint',
|
|
117
|
+
requestMode: 'Request mode',
|
|
118
|
+
reasoningEffort: 'Reasoning effort',
|
|
119
|
+
serviceTier: 'Service tier',
|
|
120
|
+
firstContent: 'First content latency',
|
|
121
|
+
firstText: 'First text latency',
|
|
122
|
+
visibleSpeed: 'Visible generation speed',
|
|
123
|
+
tokensPerSecond: '{value} tokens/s',
|
|
124
|
+
callDuration: 'Total call time',
|
|
125
|
+
staleProfile: 'This performance profile is stale; treat the estimate cautiously.',
|
|
126
|
+
priceSources: 'Price sources',
|
|
127
|
+
noPriceSources: 'No price source available.',
|
|
128
|
+
initialComparison: 'Initial estimate and current observation',
|
|
129
|
+
initialTime: 'Initial total time',
|
|
130
|
+
initialCost: 'Initial total cost',
|
|
131
|
+
reasons: 'Reasons and limits',
|
|
132
|
+
noReasons: 'No additional reasons reported.',
|
|
133
|
+
algorithm: 'Algorithm version',
|
|
134
|
+
turn: 'Turn {value}',
|
|
135
|
+
settingsHint: 'Calibration settings apply to this plugin. Save to apply changes.',
|
|
136
|
+
storageUnavailable: 'Storage is unavailable. Configuration and calibration changes may not persist.',
|
|
137
|
+
config: 'Configuration',
|
|
138
|
+
performanceAlpha: 'Performance smoothing α',
|
|
139
|
+
workloadAlpha: 'Workload smoothing α',
|
|
140
|
+
predictionRefreshMs: 'Prediction refresh (ms)',
|
|
141
|
+
timerRefreshMs: 'Local timer refresh (ms)',
|
|
142
|
+
historyRetentionDays: 'History retention (days)',
|
|
143
|
+
profileStaleDays: 'Profile stale age (days)',
|
|
144
|
+
minConditionalSamples: 'Minimum conditional samples',
|
|
145
|
+
heuristicFirstTextMs: 'Fallback first text latency (ms)',
|
|
146
|
+
heuristicVisibleTps: 'Fallback visible tokens/s',
|
|
147
|
+
heuristicTotalCallMs: 'Fallback total call time (ms)',
|
|
148
|
+
advancedJson: 'Advanced configuration (JSON)',
|
|
149
|
+
advancedHint: 'The JSON contains the complete configuration. Editing a field above updates this JSON.',
|
|
150
|
+
invalidConfig: 'Invalid configuration: {message}',
|
|
151
|
+
objectRequired: 'Enter a JSON object.',
|
|
152
|
+
fieldRequired: 'Enter a finite number for {field}.',
|
|
153
|
+
fieldMinimum: '{field} must be at least {value}.',
|
|
154
|
+
fieldMaximum: '{field} must be at most {value}.',
|
|
155
|
+
fieldInteger: '{field} must be a whole number.',
|
|
156
|
+
save: 'Save configuration',
|
|
157
|
+
saved: 'Configuration saved.',
|
|
158
|
+
reloaded: 'Settings reloaded.',
|
|
159
|
+
imported: 'Baseline data imported.',
|
|
160
|
+
exported: 'Export prepared. Check the save dialog or your downloads for the result.',
|
|
161
|
+
resetDone: 'Local calibration reset.',
|
|
162
|
+
operationFailed: 'Operation failed',
|
|
163
|
+
requestTimeout: 'The Host did not respond in time. Retry the request.',
|
|
164
|
+
requestCancelled: 'The request was cancelled.',
|
|
165
|
+
errorDetail: '{message} ({code})',
|
|
166
|
+
read: 'Reload settings',
|
|
167
|
+
data: 'Calibration data',
|
|
168
|
+
importLabel: 'Baseline JSON or CSV content',
|
|
169
|
+
importHint: 'Paste the complete file content. The Host detects JSON or CSV and validates it before importing.',
|
|
170
|
+
importPlaceholder: 'Paste JSON or CSV content…',
|
|
171
|
+
import: 'Import baseline data',
|
|
172
|
+
importRequired: 'Paste JSON or CSV content first.',
|
|
173
|
+
export: 'Export configuration and calibration',
|
|
174
|
+
reset: 'Reset local calibration',
|
|
175
|
+
resetWarning: 'Reset locally learned performance and workload calibration? Task history, configuration, imported baselines, and DSH sessions are kept.',
|
|
176
|
+
confirmReset: 'Confirm reset',
|
|
177
|
+
cancel: 'Cancel',
|
|
178
|
+
profiles: 'Performance profiles',
|
|
179
|
+
performanceProfiles: 'Per-route calibration',
|
|
180
|
+
profilesHint: 'Local measurements per route. A value without local samples is a reference prior, not a measurement.',
|
|
181
|
+
noProfiles: 'No local performance profiles yet.',
|
|
182
|
+
profileSamples: 'samples {value}',
|
|
183
|
+
profileMeasuredAt: 'last {value}',
|
|
184
|
+
profileNotMeasured: 'not measured yet',
|
|
185
|
+
referenceValue: 'reference {value}',
|
|
186
|
+
profilesShown: 'Showing the newest {shown} of {total}.',
|
|
187
|
+
mixedRouteShort: 'mixed upstream',
|
|
188
|
+
history: 'Recent task history',
|
|
189
|
+
noHistory: 'No recorded tasks.',
|
|
190
|
+
diagnostics: 'Diagnostics',
|
|
191
|
+
noDiagnostics: 'No diagnostics reported.',
|
|
192
|
+
timestamp: 'Time',
|
|
193
|
+
status: 'Status',
|
|
194
|
+
duration: 'Active time',
|
|
195
|
+
initialDifference: 'Difference from initial midpoint',
|
|
196
|
+
signedDuration: '{sign}{value}',
|
|
197
|
+
exportedFilename: 'task-estimate-data.json',
|
|
198
|
+
pendingRead: 'Reading settings…',
|
|
199
|
+
pendingSave: 'Saving configuration…',
|
|
200
|
+
pendingImport: 'Importing baseline data…',
|
|
201
|
+
pendingExport: 'Preparing export…',
|
|
202
|
+
pendingReset: 'Resetting calibration…',
|
|
203
|
+
snapshotError: 'Estimate error: {message}',
|
|
204
|
+
rawDetail: 'Host detail: {message}',
|
|
205
|
+
sourceDetail: 'Source: {value}',
|
|
206
|
+
unknownRemaining: 'Remaining time unknown',
|
|
207
|
+
unknownTime: 'Time estimate unavailable',
|
|
208
|
+
overdue: 'Estimate is being revised',
|
|
209
|
+
reasonHeuristic: 'Uses engineering assumptions until local measurements are available.',
|
|
210
|
+
reasonWorkloadTemplate: 'Workload comes from an engineering template.',
|
|
211
|
+
reasonLocalWorkload: 'Workload is smoothed from local completed tasks.',
|
|
212
|
+
reasonPublicEndpoint: 'Public endpoint measurements are a reference for this route.',
|
|
213
|
+
reasonReferenceRange: 'This is a reference range, not a calibrated confidence interval.',
|
|
214
|
+
reasonFewSamples: 'Too few comparable samples for a conditional remaining-time estimate.',
|
|
215
|
+
reasonAiPlan: 'The executing AI estimated the remaining work; timing uses the current route’s performance. This is a reference, not measured completion time.',
|
|
216
|
+
reasonContinuationHistory: 'Reference from completed local tasks of the same kind and route; this does not provide a future token or cost budget.',
|
|
217
|
+
reasonContinuationUnknown: 'The original plan has been exceeded and too few comparable completed tasks remain. Reassessing instead of counting down to a false finish.',
|
|
218
|
+
reasonReassessing: 'The reference range has elapsed; the estimate needs revision.',
|
|
219
|
+
reasonBudgetExhausted: 'The output budget has been exceeded while the task is still running.',
|
|
220
|
+
reasonWaitingModel: 'Waiting for a model response.',
|
|
221
|
+
reasonMixedRoute: 'The actual upstream endpoint is unknown or mixed.',
|
|
222
|
+
reasonRouteFallback: 'Uses another profile for the same provider and model.',
|
|
223
|
+
reasonSpeedPrior: 'Visible generation speed still uses a prior assumption.',
|
|
224
|
+
reasonStale: 'A stale profile is retained for comparison only.',
|
|
225
|
+
reasonEndpointUnknown: 'The endpoint identity is unknown, so this route stays a reference rather than a measured match.',
|
|
226
|
+
reasonReasoningUnknown: 'The reasoning mode is unknown.',
|
|
227
|
+
reasonNoPrice: 'No matching price is configured.',
|
|
228
|
+
reasonBillingUnknown: 'The billing model identity is unknown.',
|
|
229
|
+
reasonCacheUnknown: 'The split between future input and cache tokens is unknown.',
|
|
230
|
+
reasonFutureReasoning: 'The future reasoning-token budget is unknown.',
|
|
231
|
+
reasonReferencePrice: 'Public catalog pricing is a reference, not the provider invoice.',
|
|
232
|
+
reasonSameAs: 'Uses an explicit user-configured pricing alias.',
|
|
233
|
+
reasonPriceCondition: 'A price multiplier condition is unknown.',
|
|
234
|
+
reasonForecastScenario: 'Cost is based on a reference workload scenario.',
|
|
235
|
+
reasonNoRepeatWait: 'The first-text wait is not counted again after text generation starts.',
|
|
236
|
+
reasonReasoningIncluded: 'Reasoning time is already included in the first-text wait.',
|
|
237
|
+
reasonFirstTextBody: 'Combines first-text latency and visible text generation time.',
|
|
238
|
+
reasonTotalFallback: 'Uses total call time for the same request mode as a fallback.',
|
|
239
|
+
reasonConditional: 'Uses observed remaining times from comparable local samples.',
|
|
240
|
+
reasonClassification: 'Task classification is heuristic.',
|
|
241
|
+
reasonClassificationRule: 'Matched task classification rule: {value}.',
|
|
242
|
+
reasonArtifactCount: 'Includes the requested artifact count.',
|
|
243
|
+
reasonPlanSteps: 'Includes the observed plan steps.',
|
|
244
|
+
reasonFileBatch: 'File batches use a workload assumption.',
|
|
245
|
+
reasonOutputBudget: 'Uses an explicit output-token budget.',
|
|
246
|
+
reasonLengthConversion: 'Requested length is converted to tokens heuristically.',
|
|
247
|
+
reasonAttachments: 'Task classification uses the visible attachment count.',
|
|
248
|
+
reasonNoClassification: 'No task classification rule matched.',
|
|
249
|
+
reasonScopeChanged: 'The task scope changed; it is excluded from workload calibration.',
|
|
250
|
+
reasonIncompleteWorkload: 'Incomplete task data is excluded from workload calibration.',
|
|
251
|
+
reasonRequestCompleted: 'The model call is complete.',
|
|
252
|
+
reasonCode: 'Reported reason: {value}',
|
|
253
|
+
} as const;
|
|
254
|
+
|
|
255
|
+
export type MessageKey = keyof typeof en;
|
|
256
|
+
export type Translate = (key: MessageKey, params?: Record<string, string | number>) => string;
|
|
257
|
+
|
|
258
|
+
export const zh: Record<MessageKey, string> = {
|
|
259
|
+
title: '任务耗时与费用预估', titleShort: '耗时预估', turnTimeTitle: '本轮用时',
|
|
260
|
+
retainedForecastHint: '正在更新,暂用 {time} 的预估参考。',
|
|
261
|
+
tokenRemainingOutput: '预计剩余输出 Token', tokenRemainingNote: '包含可见输出和工具参数,不含隐藏推理与重复输入。',
|
|
262
|
+
reassessingShort: '重新评估', upTo: '≤{value}', details: '预估详情', settings: '设置与校准', close: '关闭',
|
|
263
|
+
refresh: '刷新', retry: '重试', loading: '正在加载…', updating: '正在更新…',
|
|
264
|
+
unavailable: '暂时无法预估', noTask: '任务开始后显示预估。', noSnapshot: '本回合暂无预估。',
|
|
265
|
+
scope: '仅统计当前回合。区间为参考预估,不是保证。', estimatedRemaining: '预计剩余 {value}',
|
|
266
|
+
elapsed: '已用 {value}', activeTime: '实际工作时间', pausedTime: '等待用户时间', wallTime: '总经过时间',
|
|
267
|
+
remaining: '预计剩余', estimatedTotal: '预计总耗时', estimatedCost: '预计总费用',
|
|
268
|
+
knownCost: '已知费用', recordedCost: '已记录费用', unknown: '未知', notAvailable: '暂无数据', free: '免费',
|
|
269
|
+
incompleteCost: '无法完整估算费用', incompleteCostDetail: '部分调用缺少价格或预测;已知金额仅为部分费用。',
|
|
270
|
+
partialKnown: '已知部分:{value}', referenceCost: '参考费用', referencePriceHint: '自动匹配公共目录价,仅作费用参考;订阅费用、渠道折扣和实际账单可能不同。', noPrice: '未定价调用:{count}',
|
|
271
|
+
incompleteForecast: '预测不完整单元:{count}', observedCostStatus: '已记录费用覆盖', forecastCostStatus: '费用预测覆盖',
|
|
272
|
+
costComplete: '完整', costPartial: '不完整', costUnpriced: '未定价', costReference: '参考区间', costUnavailable: '不可用',
|
|
273
|
+
stateQueued: '排队中', stateRunning: '进行中', stateWaiting: '等待用户', stateCompleted: '已完成',
|
|
274
|
+
stateFailed: '失败', stateCancelled: '已取消', stateInterrupted: '已中断',
|
|
275
|
+
durationMilliseconds: '{value}ms', durationSeconds: '{value}s', durationMinutes: '{value}m {seconds}s', durationMinutesOnly: '{value}m',
|
|
276
|
+
durationHours: '{value}h {minutes}m', durationHoursOnly: '{value}h',
|
|
277
|
+
range: '{low}–{high}', money: '{currency} {value}', moneyRange: '{currency} {low}–{high}',
|
|
278
|
+
listSeparator: ' · ', labelSeparator: ':', timeAndCost: '{time} · {cost}', count: '{value}', tokenCount: '{value} Token',
|
|
279
|
+
eta: 'ETA {value}', etaFull: '预计还需 {value}', etaShort: 'ETA', etaEstimate: '预计 {value}',
|
|
280
|
+
doneMeta: '已用 {elapsed} · {tokens}',
|
|
281
|
+
doneElapsed: '用时 {elapsed}',
|
|
282
|
+
sessionMeta: '会话 {turns} 轮',
|
|
283
|
+
sessionEta: '会话 ETA {value}',
|
|
284
|
+
unpriced: '暂无匹配价格',
|
|
285
|
+
unpricedHint: '此模型暂未匹配到自定义或公共价格,仍显示时间与 Token;可在设置中补充模型价格。',
|
|
286
|
+
advancedSettings: '高级参数',
|
|
287
|
+
tokenTotal: '已观测 Token', tokenInput: '输入', tokenCacheRead: '缓存读取', tokenCacheWrite: '缓存写入',
|
|
288
|
+
tokenOutput: '输出', tokenReasoning: '推理', tokenForecast: '预计总 Token', tokenCoverage: 'Token 覆盖',
|
|
289
|
+
tokenComplete: '完整', tokenPartial: '部分', tokenUnknown: '未知',
|
|
290
|
+
tokensNote: '推理与缓存 Token 按供应商返回的分类展示,不重复累加。',
|
|
291
|
+
basis: '预估依据', origin: '来源', originHeuristic: '启发式假设', originPublic: '公开实测',
|
|
292
|
+
originLocal: '本地实测', originMixed: '混合来源', taskType: '任务类型', taskQuestion: '问答',
|
|
293
|
+
taskWriting: '写作', taskFileAnalysis: '文件分析', taskCode: '代码', taskResearch: '研究', taskUnknown: '未分类',
|
|
294
|
+
localSamples: '本地性能样本', workloadSamples: '工作量样本', calls: '模型调用',
|
|
295
|
+
callCounts: '已完成 {completed} · 活跃 {active} · 失败 {failed}', tools: '工具',
|
|
296
|
+
toolCounts: '已完成 {completed} · 活跃 {active}', toolTime: '工具工作时间', performance: '调用线路性能',
|
|
297
|
+
provider: '供应商', model: '模型', endpoint: '端点指纹', requestMode: '请求模式', reasoningEffort: '推理强度',
|
|
298
|
+
serviceTier: '服务层级', firstContent: '首内容延迟', firstText: '首文本延迟', visibleSpeed: '可见生成速度',
|
|
299
|
+
tokensPerSecond: '{value} Token/秒', callDuration: '调用总耗时', staleProfile: '性能画像已过期,请谨慎参考预估。',
|
|
300
|
+
performanceTrust: '性能证据', performanceObserved: '有观测数据;区间仍为参考', performanceReference: '仅供参考',
|
|
301
|
+
firstContentSource: '首内容来源', firstTextSource: '首文本来源', speedSource: '生成速度来源', totalSource: '调用总耗时来源',
|
|
302
|
+
priceSources: '价格来源', noPriceSources: '暂无价格来源。', initialComparison: '初始预估与当前观测',
|
|
303
|
+
initialTime: '初始总耗时预估', initialCost: '初始总费用预估', reasons: '原因与局限', noReasons: '暂无补充原因。',
|
|
304
|
+
algorithm: '算法版本', turn: '第 {value} 回合', settingsHint: '校准设置仅作用于本插件,保存后生效。',
|
|
305
|
+
storageUnavailable: '存储不可用,配置与校准更改可能无法持久保存。', config: '配置',
|
|
306
|
+
performanceAlpha: '性能平滑系数 α', workloadAlpha: '工作量平滑系数 α', predictionRefreshMs: '预估刷新间隔(毫秒)',
|
|
307
|
+
timerRefreshMs: '本地计时间隔(毫秒)', historyRetentionDays: '历史保留天数', profileStaleDays: '画像过期天数',
|
|
308
|
+
minConditionalSamples: '条件样本最小数量', heuristicFirstTextMs: '兜底首文本延迟(毫秒)',
|
|
309
|
+
heuristicVisibleTps: '兜底可见生成速度(Token/秒)', heuristicTotalCallMs: '兜底调用总耗时(毫秒)',
|
|
310
|
+
advancedJson: '高级配置(JSON)', advancedHint: 'JSON 包含完整配置,修改上方字段会同步更新此处。',
|
|
311
|
+
invalidConfig: '配置无效:{message}', objectRequired: '请输入 JSON 对象。', fieldRequired: '请为「{field}」输入有限数值。',
|
|
312
|
+
fieldMinimum: '「{field}」不得小于 {value}。', fieldMaximum: '「{field}」不得大于 {value}。', fieldInteger: '「{field}」必须是整数。',
|
|
313
|
+
save: '保存配置', saved: '配置已保存。', reloaded: '设置已重新加载。', imported: '基准数据已导入。',
|
|
314
|
+
exported: '已生成导出文件,请在保存窗口或下载列表中确认结果。', resetDone: '本地校准已重置。', operationFailed: '操作失败',
|
|
315
|
+
requestTimeout: 'Host 未及时响应,请重试。', requestCancelled: '请求已取消。',
|
|
316
|
+
errorDetail: '{message}({code})', read: '重新加载设置', data: '校准数据', importLabel: '基准 JSON 或 CSV 内容',
|
|
317
|
+
importHint: '粘贴完整文件内容,Host 自动识别 JSON 或 CSV,验证后导入。', importPlaceholder: '粘贴 JSON 或 CSV 内容…',
|
|
318
|
+
import: '导入基准数据', importRequired: '请先粘贴 JSON 或 CSV 内容。', export: '导出配置与校准数据',
|
|
319
|
+
reset: '重置本地校准', resetWarning: '重置本地学习的性能与工作量校准?任务历史、配置、导入基准和 DSH 会话均保留。',
|
|
320
|
+
confirmReset: '确认重置', cancel: '取消', profiles: '性能画像数量',
|
|
321
|
+
performanceProfiles: '按路由的本地校准', profilesHint: '按路由显示本地实测的延迟、速度与样本;没有本地样本的数值是参考先验,不是实测。',
|
|
322
|
+
noProfiles: '暂无本地性能画像。', profileSamples: '样本 {value}', profileMeasuredAt: '最近 {value}',
|
|
323
|
+
profileNotMeasured: '尚未测量', referenceValue: '参考 {value}', profilesShown: '显示最近 {shown} / {total} 条。', mixedRouteShort: '混合线路',
|
|
324
|
+
history: '近期任务历史', noHistory: '暂无任务记录。',
|
|
325
|
+
diagnostics: '诊断', noDiagnostics: '暂无诊断信息。', timestamp: '时间', status: '状态', duration: '工作时间',
|
|
326
|
+
initialDifference: '与初始预估中值之差', signedDuration: '{sign}{value}', exportedFilename: 'task-estimate-data.json',
|
|
327
|
+
pendingRead: '正在读取设置…', pendingSave: '正在保存配置…', pendingImport: '正在导入基准…',
|
|
328
|
+
pendingExport: '正在准备导出…', pendingReset: '正在重置校准…', snapshotError: '预估错误:{message}',
|
|
329
|
+
rawDetail: 'Host 详情:{message}', sourceDetail: '来源:{value}', unknownRemaining: '剩余时间未知',
|
|
330
|
+
unknownTime: '暂时无法预估耗时', overdue: '正在修正预估',
|
|
331
|
+
reasonHeuristic: '本地实测不足时使用工程假设。', reasonWorkloadTemplate: '工作量来自工程模板。',
|
|
332
|
+
reasonLocalWorkload: '工作量由本地已完成任务平滑更新。', reasonPublicEndpoint: '公开端点实测仅作为当前线路的参考。',
|
|
333
|
+
reasonReferenceRange: '这是参考区间,并非经过校准的置信区间。', reasonFewSamples: '相似样本不足,无法给出条件剩余时间。',
|
|
334
|
+
reasonAiPlan: '执行 AI 评估了剩余工作量,再结合当前路由性能换算耗时;这是参考预估,不是已测得的完成时间。',
|
|
335
|
+
reasonContinuationHistory: '参考同类、同路由的已完成本地任务耗时;这些历史耗时不提供未来 Token 或费用预算。',
|
|
336
|
+
reasonContinuationUnknown: '实际工作已超出原计划,且可比的已完成任务不足。正在重新评估,避免倒计时制造即将完成的错觉。',
|
|
337
|
+
reasonReassessing: '参考区间已耗尽,正在重新评估。', reasonBudgetExhausted: '输出预算已耗尽,但任务仍在进行。',
|
|
338
|
+
reasonWaitingModel: '正在等待模型响应。', reasonMixedRoute: '实际上游端点未知或线路混合。',
|
|
339
|
+
reasonRouteFallback: '使用同供应商、同模型的其他性能画像。', reasonSpeedPrior: '可见生成速度仍采用先验假设。',
|
|
340
|
+
reasonStale: '过期画像仅保留用于对照。', reasonEndpointUnknown: '端点身份未知,该线路的延迟与速度只作参考,不作为实测匹配。', reasonReasoningUnknown: '推理模式未知。', reasonNoPrice: '未找到匹配的价格配置。',
|
|
341
|
+
reasonBillingUnknown: '实际计费模型身份未知。', reasonCacheUnknown: '未来输入与缓存 Token 的拆分未知。',
|
|
342
|
+
reasonFutureReasoning: '未来推理 Token 预算未知。', reasonReferencePrice: '公开目录价格仅为参考,并非供应商账单。',
|
|
343
|
+
reasonSameAs: '使用用户明确配置的价格别名。', reasonPriceCondition: '价格倍率的适用条件未知。',
|
|
344
|
+
reasonForecastScenario: '费用基于参考工作量场景。', reasonNoRepeatWait: '文本生成开始后,不重复计算首文本等待。',
|
|
345
|
+
reasonReasoningIncluded: '推理耗时已包含在首文本等待中。', reasonFirstTextBody: '结合首文本延迟和可见文本生成耗时。',
|
|
346
|
+
reasonTotalFallback: '以相同请求模式的调用总耗时兜底。', reasonConditional: '采用相似本地样本的实际剩余时间。',
|
|
347
|
+
reasonClassification: '任务分类采用启发式规则。', reasonClassificationRule: '匹配任务分类规则:{value}。',
|
|
348
|
+
reasonArtifactCount: '已计入明确请求的产物数量。', reasonPlanSteps: '已计入可观察的计划步骤。', reasonFileBatch: '文件批次工作量采用假设。',
|
|
349
|
+
reasonOutputBudget: '采用明确的输出 Token 预算。', reasonLengthConversion: '请求篇幅通过启发式换算为 Token。',
|
|
350
|
+
reasonAttachments: '任务分类参考了附件数量。', reasonNoClassification: '未匹配任务分类规则。',
|
|
351
|
+
reasonScopeChanged: '任务范围发生变化,不纳入工作量校准。', reasonIncompleteWorkload: '任务数据不完整,不纳入工作量校准。',
|
|
352
|
+
reasonRequestCompleted: '模型调用已完成。', reasonCode: '返回的原因:{value}',
|
|
353
|
+
};
|