dsh-model-think-level 1.1.0 → 1.1.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/lib/client/HeadersEditor.d.ts +42 -2
- package/lib/client/injection/add-card.d.ts +72 -0
- package/lib/client/injection/create-card-pane.d.ts +53 -0
- package/lib/client/injection/editor-tabs.d.ts +17 -0
- package/lib/client/injection/models-page-editor.d.ts +68 -4
- package/lib/client/injection/saved-notice.d.ts +28 -0
- package/lib/client/injection/staged-headers.d.ts +128 -0
- package/lib/client/ops.d.ts +16 -0
- package/lib/client/styles.d.ts +1 -1
- package/lib/client.js +59 -14
- package/lib/client.js.map +4 -4
- package/lib/index.js +6 -1
- package/lib/index.js.map +2 -2
- package/package.json +1 -1
|
@@ -14,6 +14,12 @@
|
|
|
14
14
|
* card's own fields. The headers land through `llm-pi-ai.providers.<route>.headers`,
|
|
15
15
|
* the one channel the adapter actually sends.
|
|
16
16
|
*
|
|
17
|
+
* Deferred mode (`deferred`) is this same section rendered where there is no
|
|
18
|
+
* route to write TO yet — the create-provider card's 高级配置 pane, whose route
|
|
19
|
+
* the card's own 保存 declares. There the draft is reported instead of written,
|
|
20
|
+
* and the whole-dict discipline is what makes the two commit seams the same
|
|
21
|
+
* write: see {@link DeferredHeaders}.
|
|
22
|
+
*
|
|
17
23
|
* @module dsh-model-think-level/HeadersEditor
|
|
18
24
|
*/
|
|
19
25
|
import type { ReactNode } from 'react';
|
|
@@ -54,6 +60,22 @@ export interface HeaderEnvironment {
|
|
|
54
60
|
preferOfficialLayer: boolean;
|
|
55
61
|
};
|
|
56
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* Deferred mode: how a pane that has no route yet reports its draft.
|
|
65
|
+
*
|
|
66
|
+
* The create-provider card's 高级配置 pane edits an id the settings document
|
|
67
|
+
* does not declare — the card's own 保存 is what declares it — so that pane has
|
|
68
|
+
* nothing to write and no button to press. It hands the editor this instead:
|
|
69
|
+
* every change to the draft goes out as the complete dict its Save would write,
|
|
70
|
+
* and a Cancel reports that the draft is gone. Same component, same whole-dict
|
|
71
|
+
* discipline; only the commit seam changes, from a write to a report.
|
|
72
|
+
*/
|
|
73
|
+
export interface DeferredHeaders {
|
|
74
|
+
/** Every change to the draft, as the dict the editor's Save would write. */
|
|
75
|
+
onDraft: (headers: Record<string, string>) => void;
|
|
76
|
+
/** The user cleared the draft: whatever was staged for it must go. */
|
|
77
|
+
onCancel: () => void;
|
|
78
|
+
}
|
|
57
79
|
/** Props of {@link HeadersEditor}. */
|
|
58
80
|
export interface HeadersEditorProps {
|
|
59
81
|
/** The provider route this card edits. */
|
|
@@ -62,6 +84,24 @@ export interface HeadersEditorProps {
|
|
|
62
84
|
api: RemoteApi;
|
|
63
85
|
/** Localized copy. */
|
|
64
86
|
t: (key: string, params?: Record<string, string | number>) => string;
|
|
87
|
+
/**
|
|
88
|
+
* Close the whole provider card, as the official footer's `取消` does.
|
|
89
|
+
*
|
|
90
|
+
* Only the mount that owns the card's DOM can press that button, so the
|
|
91
|
+
* editor takes the action as a prop instead of reaching for the DOM itself
|
|
92
|
+
* (this component needs no injection pass — see the module doc). Called by
|
|
93
|
+
* {@link cancel} in pane mode only; omitted, cancel stays a draft reset.
|
|
94
|
+
*/
|
|
95
|
+
onDismiss?: () => void;
|
|
96
|
+
/**
|
|
97
|
+
* Deferred mode: report the draft instead of writing it.
|
|
98
|
+
*
|
|
99
|
+
* Set by the create-provider card's pane, whose route does not exist yet. The
|
|
100
|
+
* editor then edits in place — no disclosure, no Edit step — and has no Save
|
|
101
|
+
* of its own: its copy states that the draft lands with the card's own 保存,
|
|
102
|
+
* which is the write that declares the route the draft is addressed to.
|
|
103
|
+
*/
|
|
104
|
+
deferred?: DeferredHeaders;
|
|
65
105
|
}
|
|
66
106
|
/** One editable header row; a stable `id` keeps React keys off the name. */
|
|
67
107
|
interface DraftRow {
|
|
@@ -79,8 +119,8 @@ interface Draft {
|
|
|
79
119
|
export declare function headersFromDraft(draft: Draft): Record<string, string>;
|
|
80
120
|
/**
|
|
81
121
|
* Render the provider card's request-header section.
|
|
82
|
-
* @param props - route identity, write seam and
|
|
122
|
+
* @param props - route identity, write seam, copy and the card dismiss seam.
|
|
83
123
|
* @returns the section.
|
|
84
124
|
*/
|
|
85
|
-
export declare function HeadersEditor({ route, api, t }: HeadersEditorProps): ReactNode;
|
|
125
|
+
export declare function HeadersEditor({ route, api, t, onDismiss, deferred }: HeadersEditorProps): ReactNode;
|
|
86
126
|
export {};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The add-provider card: the custom-API form on its own, carrying the tab bar.
|
|
3
|
+
*
|
|
4
|
+
* The official card offers two ways to add a provider — adopt one from the
|
|
5
|
+
* built-in catalogue, or declare a custom API endpoint — behind a segmented
|
|
6
|
+
* "How to add" control. The catalogue half only duplicates the configured
|
|
7
|
+
* provider rows above it, so this pass reduces the card to the custom half: it
|
|
8
|
+
* selects that mode, hides the switch, and gives the form the same three panes
|
|
9
|
+
* its sibling editor cards carry — the bar, and the 高级配置 section the card
|
|
10
|
+
* has nowhere else to put ({@link CreateCardPane}).
|
|
11
|
+
*
|
|
12
|
+
* The card is the SAME card the tab takeover dresses (`bre-tabbed`), and that
|
|
13
|
+
* takeover scopes its regions to the visible half of the form
|
|
14
|
+
* ({@link activeScopeOf}). What it cannot do is hang the bar: the official card
|
|
15
|
+
* renders the provider-card slot only inside the catalogue half, so the custom
|
|
16
|
+
* form has no plugin mount to render a bar into. The mount is created here
|
|
17
|
+
* instead, at the top of the visible form, and it is the ONLY node this module
|
|
18
|
+
* inserts. Everything else is attributes on nodes the official page owns.
|
|
19
|
+
*
|
|
20
|
+
* Degradation is total: a card with no custom half (no API protocol to declare,
|
|
21
|
+
* or no `llm-pi-ai` namespace to declare one into) is left exactly as the
|
|
22
|
+
* official page built it, switch and all, and a card whose dressing throws is
|
|
23
|
+
* rolled back to that same state.
|
|
24
|
+
*
|
|
25
|
+
* @module dsh-model-think-level/client/injection/add-card
|
|
26
|
+
*/
|
|
27
|
+
import type { ReactNode } from 'react';
|
|
28
|
+
import type { Translate } from '@deepseek-ai/dsh-client-ui-slots';
|
|
29
|
+
import { type ForeignMount } from './mount.js';
|
|
30
|
+
import type { StagedHeadersState } from './staged-headers.js';
|
|
31
|
+
import type { RemoteApi } from '../types.js';
|
|
32
|
+
export interface AddCardDeps {
|
|
33
|
+
/** The plugin's own translator, for the tab labels and the pane's copy. */
|
|
34
|
+
t: Translate;
|
|
35
|
+
/** The write seam the pane's header editor reads the settings document with. */
|
|
36
|
+
api: RemoteApi;
|
|
37
|
+
/** The official labels of the form's provider-id field, by locale. */
|
|
38
|
+
routeIdLabels: () => readonly string[];
|
|
39
|
+
/** The session's deferred-header ledger (see `staged-headers`). */
|
|
40
|
+
staged: StagedHeadersState;
|
|
41
|
+
/** The plugin's locale-refresh seam, so the pane's copy follows a language
|
|
42
|
+
* switch exactly as the seats the official slot mounts do. */
|
|
43
|
+
refreshed: (children: () => ReactNode) => ReactNode;
|
|
44
|
+
}
|
|
45
|
+
/** One card's mount, remembered by the card it belongs to. */
|
|
46
|
+
interface AddCardHost {
|
|
47
|
+
/** The node the root renders into, so a re-created form can be told from a
|
|
48
|
+
* settled one. */
|
|
49
|
+
readonly host: HTMLElement;
|
|
50
|
+
readonly mount: ForeignMount;
|
|
51
|
+
}
|
|
52
|
+
export interface AddCardState {
|
|
53
|
+
/** Cards this module mounted a bar into. The mount goes with the card, so a
|
|
54
|
+
* card the page has dropped is dropped here on the next visit. */
|
|
55
|
+
readonly hosts: Map<HTMLElement, AddCardHost>;
|
|
56
|
+
}
|
|
57
|
+
export declare function createAddCardState(): AddCardState;
|
|
58
|
+
/**
|
|
59
|
+
* Reduce every add-provider card on the page to its custom form.
|
|
60
|
+
*
|
|
61
|
+
* Idempotent by construction: a card already showing the custom form with its
|
|
62
|
+
* bar up is left untouched, and switching the mode is driven by the official
|
|
63
|
+
* control's own selected state, so a re-visit writes nothing.
|
|
64
|
+
*/
|
|
65
|
+
export declare function reconcileAddCards(root: HTMLElement, deps: AddCardDeps, state: AddCardState): void;
|
|
66
|
+
/**
|
|
67
|
+
* Give every card back to the official page: the mounts go, and the switch
|
|
68
|
+
* comes out of hiding. The mode the card is left in is the official page's own
|
|
69
|
+
* state, and it is not this module's to reset — React unmounts the card whole.
|
|
70
|
+
*/
|
|
71
|
+
export declare function teardownAddCards(state: AddCardState): void;
|
|
72
|
+
export {};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The create-provider card's 高级配置 pane: the tab bar and the request-header
|
|
3
|
+
* editor, for a provider that does not exist yet.
|
|
4
|
+
*
|
|
5
|
+
* This is the half {@link add-card} used to leave out. On an existing provider
|
|
6
|
+
* the same editor arrives through the official `settings.models.provider-card`
|
|
7
|
+
* slot, which the official card renders only inside its CATALOGUE half — so the
|
|
8
|
+
* custom-only card this plugin reduces it to had a tab bar whose 高级配置 button
|
|
9
|
+
* could never light up: nothing in that half carried a `.bre-headers` section for
|
|
10
|
+
* the takeover to reveal. This pane is that section, mounted at the same seat
|
|
11
|
+
* the bar is.
|
|
12
|
+
*
|
|
13
|
+
* The route the pane edits is the id the user is TYPING, which is the one thing
|
|
14
|
+
* about this card the settings document does not have yet — the card's own 保存
|
|
15
|
+
* is what declares it. So the pane cannot write as the official card can:
|
|
16
|
+
*
|
|
17
|
+
* - the id is read off the live form (the panel's own label, never a position)
|
|
18
|
+
* and re-read on every keystroke, because the form's React state is not ours
|
|
19
|
+
* to subscribe to;
|
|
20
|
+
* - the rows are editable whether or not the id has been typed: a draft is held
|
|
21
|
+
* by the session ledger once it has a route to belong to, and until then it
|
|
22
|
+
* simply waits for the id — which the pane does NOT announce, because the id
|
|
23
|
+
* field is on the 供应商配置 tab of this same card and the official form
|
|
24
|
+
* already speaks for it;
|
|
25
|
+
* - once there is an id the editor runs in DEFERRED mode: it edits, and reports
|
|
26
|
+
* the draft to the session ledger, but the write happens when the route
|
|
27
|
+
* appears in the document — the proof the card's own create landed. The pane
|
|
28
|
+
* also asks, one keystroke in, whether that id was ALREADY declared, because
|
|
29
|
+
* only a route that was not can be this card's work. See
|
|
30
|
+
* {@link staged-headers} for why that is a gate and not a delay.
|
|
31
|
+
*
|
|
32
|
+
* @module dsh-model-think-level/client/injection/create-card-pane
|
|
33
|
+
*/
|
|
34
|
+
import type { ReactNode } from 'react';
|
|
35
|
+
import type { Translate } from '@deepseek-ai/dsh-client-ui-slots';
|
|
36
|
+
import type { RemoteApi } from '../types.js';
|
|
37
|
+
import { type StagedHeadersState } from './staged-headers.js';
|
|
38
|
+
export interface CreateCardPaneProps {
|
|
39
|
+
/** The plugin's own mount inside this card; the bar resolves the card from it. */
|
|
40
|
+
host: {
|
|
41
|
+
readonly current: HTMLDivElement | null;
|
|
42
|
+
};
|
|
43
|
+
/** The write seam, for the editor's own settings read. */
|
|
44
|
+
api: RemoteApi;
|
|
45
|
+
/** The plugin's translator. */
|
|
46
|
+
t: Translate;
|
|
47
|
+
/** The official labels of the form's provider-id field, by locale. */
|
|
48
|
+
routeIdLabels: () => readonly string[];
|
|
49
|
+
/** The session's deferred-header ledger. */
|
|
50
|
+
staged: StagedHeadersState;
|
|
51
|
+
}
|
|
52
|
+
/** One create card's pane: its bar, and the headers pane once there is a route. */
|
|
53
|
+
export declare function CreateCardPane({ host, api, t, routeIdLabels, staged }: CreateCardPaneProps): ReactNode;
|
|
@@ -75,6 +75,23 @@ export declare function createEditorTabsState(): EditorTabsState;
|
|
|
75
75
|
* other.
|
|
76
76
|
*/
|
|
77
77
|
export declare function editorCardOf(element: Element | null): HTMLElement | null;
|
|
78
|
+
/**
|
|
79
|
+
* The part of a card that presents its editor.
|
|
80
|
+
*
|
|
81
|
+
* A provider ROW has one editor and this returns the card itself. The
|
|
82
|
+
* create-provider card has TWO form halves — the built-in catalogue and the
|
|
83
|
+
* custom API declaration — and the official page mounts each once it is visited
|
|
84
|
+
* and hides the inactive one with `hidden`. Every lookup below is scoped to the
|
|
85
|
+
* visible half, because the hidden one carries a provider-card slot of its own:
|
|
86
|
+
* the plugin's mount inside it publishes a route, a tab bar and a headers
|
|
87
|
+
* section, and reading those would dress the visible form with the hidden
|
|
88
|
+
* form's provider.
|
|
89
|
+
*
|
|
90
|
+
* A card with both halves hidden (mid-swap, before React commits the show) and
|
|
91
|
+
* a card with none are both scoped to the card, which is the rule this module
|
|
92
|
+
* has always applied to a single-form card.
|
|
93
|
+
*/
|
|
94
|
+
export declare function activeScopeOf(card: HTMLElement): HTMLElement;
|
|
78
95
|
/**
|
|
79
96
|
* Switch one card to a tab. The card attribute is the single source of truth:
|
|
80
97
|
* the stylesheet swaps the pane on the attribute alone, and the bar's buttons
|
|
@@ -74,6 +74,14 @@ export interface HostLabels {
|
|
|
74
74
|
* offers this plugin no seat to stamp that name on.
|
|
75
75
|
*/
|
|
76
76
|
editProvider: readonly string[];
|
|
77
|
+
/**
|
|
78
|
+
* The official input-types control's image choice ("Image" / "图片"). When a
|
|
79
|
+
* row ships that control, it — not the plugin's ledger — is what tells the
|
|
80
|
+
* user (and the official Save) that a model takes images, so the copy is the
|
|
81
|
+
* anchor that finds the box a verdict belongs in: see
|
|
82
|
+
* {@link echoOfficialModality}.
|
|
83
|
+
*/
|
|
84
|
+
inputImage: readonly string[];
|
|
77
85
|
}
|
|
78
86
|
/** The join the injector renders from. */
|
|
79
87
|
export interface InjectorDeps {
|
|
@@ -391,6 +399,65 @@ export interface AutofillFootprint {
|
|
|
391
399
|
* position (the label's trailing number IS the position).
|
|
392
400
|
*/
|
|
393
401
|
export declare function inputValueByLabel(card: HTMLElement, labels: readonly string[]): string;
|
|
402
|
+
/**
|
|
403
|
+
* The official input-types checkbox an image verdict belongs to, or undefined
|
|
404
|
+
* when this row ships no such control (a legacy kernel) — or ships one whose
|
|
405
|
+
* boxes cannot be told apart.
|
|
406
|
+
*
|
|
407
|
+
* The official fieldset maps its two choices in a fixed order (`["text",
|
|
408
|
+
* "image"]`) and labels each one, so the image box is the one whose own label
|
|
409
|
+
* carries the image copy — matched like every other anchor, by copy, with the
|
|
410
|
+
* second box as the fallback for a host whose copy is in a language neither
|
|
411
|
+
* this plugin's locale nor its English floor knows: the order still identifies
|
|
412
|
+
* it.
|
|
413
|
+
*/
|
|
414
|
+
export declare function officialImageCheckboxOf(container: HTMLElement, imageLabels: readonly string[]): HTMLInputElement | undefined;
|
|
415
|
+
/**
|
|
416
|
+
* Print a verdict about images on the OFFICIAL input-types control, and report
|
|
417
|
+
* whether that control now owns the row's modality.
|
|
418
|
+
*
|
|
419
|
+
* The official checkbox renders the official card's own DRAFT, not the
|
|
420
|
+
* settings document, so a document write of the plugin's can never tick it
|
|
421
|
+
* before the card is saved and reopened — the gap the user hit. Clicking the
|
|
422
|
+
* box is a real user-equivalent edit: the official form holds the tick in its
|
|
423
|
+
* draft and its own Save is what persists it. That is also why a row reaching
|
|
424
|
+
* this path must keep the modality OUT of its own write (see
|
|
425
|
+
* {@link officialModalityIntent}): two writers for one field would let this
|
|
426
|
+
* plugin's held write land after that Save and revert whatever the user chose
|
|
427
|
+
* in the meantime.
|
|
428
|
+
*
|
|
429
|
+
* A missing box, or a DISABLED one (a read-only card, which will not carry the
|
|
430
|
+
* click anywhere), reports `false` so the caller keeps its own write — the
|
|
431
|
+
* only remaining way the verdict can reach the document.
|
|
432
|
+
*/
|
|
433
|
+
export declare function echoOfficialModality(container: HTMLElement, image: boolean, imageLabels: readonly string[]): boolean;
|
|
434
|
+
/**
|
|
435
|
+
* The modality intent a row's own write may carry, once the official
|
|
436
|
+
* input-types control has been offered the verdict.
|
|
437
|
+
*
|
|
438
|
+
* `undefined` out means the write states nothing about the modality: either it
|
|
439
|
+
* never carried one, or the official control took the verdict over. A row with
|
|
440
|
+
* no such control — a legacy kernel, or a row the user has left folded — hands
|
|
441
|
+
* its intent through unchanged, which keeps the document write as the way the
|
|
442
|
+
* verdict travels.
|
|
443
|
+
*
|
|
444
|
+
* `null` is the plugin's deliberate UNSET, which the official control cannot
|
|
445
|
+
* express at all (it knows only text and text+image): it passes through
|
|
446
|
+
* untouched.
|
|
447
|
+
*/
|
|
448
|
+
export declare function officialModalityIntent(container: HTMLElement | undefined, input: InputModalities | null | undefined, imageLabels: readonly string[]): InputModalities | null | undefined;
|
|
449
|
+
/**
|
|
450
|
+
* One model row a card shows on screen: what the walk needs to name it, plus
|
|
451
|
+
* the row element itself, so a verdict about its modality can be printed on
|
|
452
|
+
* the row's own official control (which lives inside the row, collapsed or
|
|
453
|
+
* not). The element is optional because a caller may know the rows only by
|
|
454
|
+
* their ids.
|
|
455
|
+
*/
|
|
456
|
+
export interface ScreenModel {
|
|
457
|
+
readonly id: string;
|
|
458
|
+
readonly name: string;
|
|
459
|
+
readonly row?: HTMLElement;
|
|
460
|
+
}
|
|
394
461
|
/**
|
|
395
462
|
* Adapt every model of one provider, the rows the user never unfolded
|
|
396
463
|
* included.
|
|
@@ -428,10 +495,7 @@ export declare function inputValueByLabel(card: HTMLElement, labels: readonly st
|
|
|
428
495
|
* click: how many models it held an adaptation for, how many had no
|
|
429
496
|
* suggestion, and -- when nothing could be adapted at all -- why.
|
|
430
497
|
*/
|
|
431
|
-
export declare function adaptEveryModel(state: ScanState, deps: InjectorDeps, route: string, visibleRows?:
|
|
432
|
-
id: string;
|
|
433
|
-
name: string;
|
|
434
|
-
}>): Promise<AutoAdaptReport>;
|
|
498
|
+
export declare function adaptEveryModel(state: ScanState, deps: InjectorDeps, route: string, visibleRows?: readonly ScreenModel[]): Promise<AutoAdaptReport>;
|
|
435
499
|
/**
|
|
436
500
|
* Hand a viewport delta back to whichever container actually moved the card.
|
|
437
501
|
*
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The official "Saved <provider>." line, taken out of the flow and
|
|
3
|
+
* answered by a bubble at the top of the window instead.
|
|
4
|
+
*
|
|
5
|
+
* The host confirms a save by inserting a paragraph above the provider list
|
|
6
|
+
* (`ModelsSection`'s `savedNotice`, `role="status"`) that stays there until the
|
|
7
|
+
* next edit, remove or add clears it. It is feedback, not state — the row it
|
|
8
|
+
* names is on screen and shows the save itself — but it holds a whole line of
|
|
9
|
+
* the panel's height the whole time the user works in the card, which is the
|
|
10
|
+
* "the hint eats space" report. So the line is hidden (never removed: React
|
|
11
|
+
* owns it, and it must survive the page's own re-renders) and the same words go
|
|
12
|
+
* up as the plugin's fixed top-centre bubble, which takes itself away after
|
|
13
|
+
* {@link NOTE_TTL_MS} — the treatment the auto-adapt seat's verdict already
|
|
14
|
+
* gets, and the one the host's own toast uses.
|
|
15
|
+
*
|
|
16
|
+
* The pass owns no wire and no state of its own: it is DOM-only, idempotent and
|
|
17
|
+
* compare-before-write, so a settled page emits no mutation of its own.
|
|
18
|
+
*
|
|
19
|
+
* @module dsh-model-think-level/client/injection/saved-notice
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* Hide every official saved line inside the panel, and put up a bubble for the
|
|
23
|
+
* one that just appeared.
|
|
24
|
+
* @param root - the Models panel the passes all decorate.
|
|
25
|
+
*/
|
|
26
|
+
export declare function reconcileSavedNotice(root: HTMLElement): void;
|
|
27
|
+
/** Put every official line back and take the bubble down (plugin dispose). */
|
|
28
|
+
export declare function teardownSavedNotice(): void;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deferred request headers: the create-provider card's 高级配置 draft, held
|
|
3
|
+
* until the provider it belongs to exists.
|
|
4
|
+
*
|
|
5
|
+
* The create card's 高级配置 pane edits a route the settings document does not
|
|
6
|
+
* declare — the card's own 保存 is what declares it. So there is nothing this
|
|
7
|
+
* plugin may write at the moment the user types: `writeProviderHeaders` refuses
|
|
8
|
+
* a route no raw layer declares, and for a good reason (the write would leave a
|
|
9
|
+
* stray profile carrying nothing but headers). The pane's draft therefore waits
|
|
10
|
+
* here as an INTENT, and lands on the first scan that finds the route really
|
|
11
|
+
* declared. That finding is also the proof the creation succeeded: it is the
|
|
12
|
+
* card's own commit, never this plugin's, that puts the route in the document.
|
|
13
|
+
*
|
|
14
|
+
* The wait is bounded, and deliberately so. A pane's unmount cannot tell "the
|
|
15
|
+
* create landed and the card closed" from "the user gave up", so an intent is
|
|
16
|
+
* RELEASED at unmount rather than dropped, and a released intent whose route
|
|
17
|
+
* never appears is retired after {@link RELEASE_GRACE_MS} — which is what keeps
|
|
18
|
+
* an abandoned card's headers from being inherited by a later provider that
|
|
19
|
+
* happens to take the same id.
|
|
20
|
+
*
|
|
21
|
+
* What decides whether the draft may be written is not the route appearing on its
|
|
22
|
+
* own — a card whose id names a provider that ALREADY exists sees its route
|
|
23
|
+
* declared from the first keystroke, and those headers are that provider's, not
|
|
24
|
+
* this draft's. So each route carries a BASELINE: whether it was already declared
|
|
25
|
+
* when this card's draft was staged. A route that was not, and now is, was
|
|
26
|
+
* declared by the card (that is the only thing that could have declared it) and
|
|
27
|
+
* the draft lands — on the very scan that reads it, whether or not the card has
|
|
28
|
+
* closed yet. A route that already was gets nothing, exactly as before.
|
|
29
|
+
*
|
|
30
|
+
* Nothing is persisted. A reload discards a held intent exactly as it discards
|
|
31
|
+
* the card's own unsaved fields, which is the contract the pane's copy states.
|
|
32
|
+
*
|
|
33
|
+
* @module dsh-model-think-level/client/injection/staged-headers
|
|
34
|
+
*/
|
|
35
|
+
import type { RemoteApi } from '../types.js';
|
|
36
|
+
/**
|
|
37
|
+
* How long a released intent keeps waiting for its route to appear.
|
|
38
|
+
*
|
|
39
|
+
* The window only has to cover the gap between the card's own 保存 landing and
|
|
40
|
+
* the next scan reading it — a mutation schedules that scan within the debounce
|
|
41
|
+
* window, so this is generous by two orders of magnitude. It is a window and not
|
|
42
|
+
* a deadline: a draft whose route never appears is dropped, because after that
|
|
43
|
+
* long the card it belonged to is not coming back.
|
|
44
|
+
*/
|
|
45
|
+
export declare const RELEASE_GRACE_MS = 15000;
|
|
46
|
+
/** One pane's held draft, keyed by the route it will be written to. */
|
|
47
|
+
export interface StagedHeaderIntent {
|
|
48
|
+
/** The complete dict the pane holds — the same whole-dict value the editor's
|
|
49
|
+
* own Save writes, so the two commit paths cannot write different shapes. */
|
|
50
|
+
headers: Record<string, string>;
|
|
51
|
+
/** Whether this route was already declared when the draft was staged; see the
|
|
52
|
+
* module doc. Left undefined while that read is in flight — the drain cannot
|
|
53
|
+
* tell a typed id from a created one until it knows, so it waits. */
|
|
54
|
+
declaredBefore?: boolean;
|
|
55
|
+
/** When the pane that staged this intent unmounted, if it has. */
|
|
56
|
+
releasedAt?: number;
|
|
57
|
+
/** Whether the drain has already said — once — that this route has no
|
|
58
|
+
* baseline, which is the one holding state that ends in silence. */
|
|
59
|
+
noted?: boolean;
|
|
60
|
+
}
|
|
61
|
+
/** The session's deferred-header ledger. */
|
|
62
|
+
export interface StagedHeadersState {
|
|
63
|
+
/** Held drafts, by route. Readable so a test (and the console) can see what
|
|
64
|
+
* the session is still holding. */
|
|
65
|
+
readonly intents: Map<string, StagedHeaderIntent>;
|
|
66
|
+
/** What each route's baseline read found, kept for the session so a draft
|
|
67
|
+
* restaged after the drain retired its intent does not have to ask again. */
|
|
68
|
+
readonly baseline: Map<string, boolean>;
|
|
69
|
+
/** One drain at a time: an unmount's release-triggered flush and a scan's
|
|
70
|
+
* drain must not both walk the ledger and write the same route. */
|
|
71
|
+
flushing: boolean;
|
|
72
|
+
}
|
|
73
|
+
/** A fresh, empty ledger. */
|
|
74
|
+
export declare function createStagedHeadersState(): StagedHeadersState;
|
|
75
|
+
/**
|
|
76
|
+
* Hold one pane's draft for a route that is not declared yet.
|
|
77
|
+
*
|
|
78
|
+
* An empty draft WITHDRAWS instead of staging: "no headers" is the same state
|
|
79
|
+
* the route is already in (a card that creates a provider with no headers), so
|
|
80
|
+
* staging it would only arm a write that changes nothing — and it would make the
|
|
81
|
+
* pane's first, still-blank report look like an edit.
|
|
82
|
+
* @param state - the session's ledger.
|
|
83
|
+
* @param route - the route the pane's id field currently reads.
|
|
84
|
+
* @param headers - the complete draft dict.
|
|
85
|
+
*/
|
|
86
|
+
export declare function stageRequestHeaders(state: StagedHeadersState, route: string, headers: Record<string, string>): void;
|
|
87
|
+
/** Drop the draft held for one route. */
|
|
88
|
+
export declare function withdrawRequestHeaders(state: StagedHeadersState, route: string): void;
|
|
89
|
+
/**
|
|
90
|
+
* Mark one route's draft as orphaned: the pane that staged it is gone and the
|
|
91
|
+
* card's own 保存 has already been pressed or will never be.
|
|
92
|
+
* @param state - the session's ledger.
|
|
93
|
+
* @param route - the route the pane last read.
|
|
94
|
+
*/
|
|
95
|
+
export declare function releaseRequestHeaders(state: StagedHeadersState, route: string): void;
|
|
96
|
+
/**
|
|
97
|
+
* Record whether `route` was already declared when this card's draft was staged.
|
|
98
|
+
*
|
|
99
|
+
* The pane reads this off the settings document as soon as it has a route to ask
|
|
100
|
+
* about, which is long before the card's own 保存 can declare anything — so the
|
|
101
|
+
* answer tells the drain whether the declaration it later sees is this card's
|
|
102
|
+
* doing or someone else's provider. Kept per session, so a draft restaged after
|
|
103
|
+
* the drain retired its intent does not ask twice.
|
|
104
|
+
* @param state - the session's ledger.
|
|
105
|
+
* @param route - the route to record.
|
|
106
|
+
* @param declared - whether the route was declared at that moment.
|
|
107
|
+
*/
|
|
108
|
+
export declare function markRouteBaseline(state: StagedHeadersState, route: string, declared: boolean): void;
|
|
109
|
+
/**
|
|
110
|
+
* Whether `route` still needs its baseline read, and has no draft to wait on.
|
|
111
|
+
* @param state - the session's ledger.
|
|
112
|
+
* @param route - the route the pane's id field currently reads.
|
|
113
|
+
* @returns True when the pane should ask the settings document.
|
|
114
|
+
*/
|
|
115
|
+
export declare function needsRouteBaseline(state: StagedHeadersState, route: string): boolean;
|
|
116
|
+
/**
|
|
117
|
+
* Write every held draft whose route is now declared.
|
|
118
|
+
*
|
|
119
|
+
* Runs on the debounced scan, which is the only place that already does wire
|
|
120
|
+
* work — the synchronous pass in the mutation microtask must stay wire-free.
|
|
121
|
+
* @param state - the session's ledger.
|
|
122
|
+
* @param api - the settings Remote.
|
|
123
|
+
* @returns How many RELEASED drafts are still waiting for their route. The
|
|
124
|
+
* caller keeps the scan coming while that is non-zero: the card's own 保存 may
|
|
125
|
+
* declare the route after the card — and its pane — are gone, and by then no
|
|
126
|
+
* further DOM mutation is left to trigger a scan.
|
|
127
|
+
*/
|
|
128
|
+
export declare function flushStagedRequestHeaders(state: StagedHeadersState, api: RemoteApi): Promise<number>;
|
package/lib/client/ops.d.ts
CHANGED
|
@@ -175,6 +175,22 @@ export declare function providerHeadersOf(namespace: SettingsNamespaceView | und
|
|
|
175
175
|
* @returns whether the write landed, and the refusal text when it did not.
|
|
176
176
|
*/
|
|
177
177
|
export declare function writeProviderHeaders(api: RemoteApi, route: string, headers: Record<string, string>): Promise<ProviderHeadersReply>;
|
|
178
|
+
/**
|
|
179
|
+
* Whether this document declares the route: its RESOLVED providers map, or
|
|
180
|
+
* either raw layer, holding the key itself (own key, never a prototype name).
|
|
181
|
+
*
|
|
182
|
+
* The resolved map is the one the models-page editor stages against, and it is
|
|
183
|
+
* the only one a settings join is guaranteed to carry — a mirror-backed join can
|
|
184
|
+
* hand back `value` with the raw layers absent or thin, and a check that reads
|
|
185
|
+
* only the raw layers then answers "not declared" for a provider that plainly
|
|
186
|
+
* exists. The raw layers are still asked first-class, because they are where the
|
|
187
|
+
* official card's own save lands.
|
|
188
|
+
*
|
|
189
|
+
* Exported for the deferred-header ledger, which asks the same question for the
|
|
190
|
+
* same reason `writeProviderHeaders` does: a route nothing declares is a route
|
|
191
|
+
* nothing may be written to yet.
|
|
192
|
+
*/
|
|
193
|
+
export declare function routeIsDeclared(namespace: SettingsNamespaceView | undefined, route: string): boolean;
|
|
178
194
|
/**
|
|
179
195
|
* Describe the pi-ai namespace plus writability.
|
|
180
196
|
*
|