@huanlin/dsh-plugin-preface-context 0.1.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 +113 -0
- package/cordis.patch.yml +11 -0
- package/lib/client.js +794 -0
- package/lib/client.js.map +1 -0
- package/lib/index.js +127 -0
- package/lib/types/client/bind-snapshot-selector.d.ts +7 -0
- package/lib/types/client/bind-snapshot-selector.js +19 -0
- package/lib/types/client/client/bind-snapshot-selector.d.ts +7 -0
- package/lib/types/client/client/bind-snapshot-selector.js +19 -0
- package/lib/types/client/client/index.d.ts +39 -0
- package/lib/types/client/client/index.js +50 -0
- package/lib/types/client/client/locales.d.ts +7 -0
- package/lib/types/client/client/locales.js +41 -0
- package/lib/types/client/client/preface-card-controller.d.ts +94 -0
- package/lib/types/client/client/preface-card-controller.js +181 -0
- package/lib/types/client/client/preface-card.css.d.ts +53 -0
- package/lib/types/client/client/preface-card.css.js +327 -0
- package/lib/types/client/client/preface-card.d.ts +27 -0
- package/lib/types/client/client/preface-card.js +41 -0
- package/lib/types/client/config.d.ts +41 -0
- package/lib/types/client/config.js +42 -0
- package/lib/types/client/index.d.ts +39 -0
- package/lib/types/client/index.js +50 -0
- package/lib/types/client/locales.d.ts +7 -0
- package/lib/types/client/locales.js +41 -0
- package/lib/types/client/preface-card-controller.d.ts +94 -0
- package/lib/types/client/preface-card-controller.js +181 -0
- package/lib/types/client/preface-card.css.d.ts +53 -0
- package/lib/types/client/preface-card.css.js +327 -0
- package/lib/types/client/preface-card.d.ts +27 -0
- package/lib/types/client/preface-card.js +41 -0
- package/lib/types/config.d.ts +40 -0
- package/lib/types/config.js +42 -0
- package/lib/types/index.d.ts +46 -0
- package/lib/types/index.js +64 -0
- package/lib/types/settings.d.ts +50 -0
- package/lib/types/settings.js +85 -0
- package/package.json +130 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","names":["clsx","IconChevronDownOutline14","en: Record<PrefaceLocaleKey, string>","zh: Record<PrefaceLocaleKey, string>"],"sources":["../node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/dist/clsx.mjs","../src/client/preface-card.css.ts","../src/client/preface-card.tsx","../src/client/preface-card-controller.ts","../src/client/locales.ts","../src/client/index.ts"],"sourcesContent":["function r(e){var t,f,n=\"\";if(\"string\"==typeof e||\"number\"==typeof e)n+=e;else if(\"object\"==typeof e)if(Array.isArray(e)){var o=e.length;for(t=0;t<o;t++)e[t]&&(f=r(e[t]))&&(n&&(n+=\" \"),n+=f)}else for(f in e)e[f]&&(n&&(n+=\" \"),n+=f);return n}export function clsx(){for(var e,t,f=0,n=\"\",o=arguments.length;f<o;f++)(e=arguments[f])&&(t=r(e))&&(n&&(n+=\" \"),n+=t);return n}export default clsx;","/**\n * Card styles + runtime style injection for the preface-context settings\n * card.\n *\n * The DSH client loader never serves `.css` artifacts — every style must be\n * injected inside the bundle's factory closure (the same contract the\n * upstream tsdown.client preset implements with lightningcss). A plain\n * template-string stylesheet installed once at plugin activation is the slim\n * equivalent for a hand-rolled bundle: static, prefixed class names\n * (`dsh-pfc-*`, unique in the document) replace the hashed CSS-modules map,\n * and the `<style data-plugin-css>` tag is removed when the plugin unloads.\n *\n * Colors resolve through `--dsw-alias-*` tokens so the card adapts to the\n * light/dark theme.\n *\n * @module @huanlin/dsh-plugin-preface-context/client/preface-card.css\n */\n\n/** Class-name map consumed by the card component (CSS-modules replacement). */\nexport const css = {\n card: 'dsh-pfc-card',\n cardOpen: 'dsh-pfc-card--open',\n header: 'dsh-pfc-header',\n headText: 'dsh-pfc-head-text',\n name: 'dsh-pfc-name',\n description: 'dsh-pfc-description',\n pending: 'dsh-pfc-pending',\n chevron: 'dsh-pfc-chevron',\n chevronOpen: 'dsh-pfc-chevron--open',\n body: 'dsh-pfc-body',\n readOnly: 'dsh-pfc-readonly',\n field: 'dsh-pfc-field',\n head: 'dsh-pfc-field-head',\n label: 'dsh-pfc-label',\n badges: 'dsh-pfc-badges',\n badge: 'dsh-pfc-badge',\n reset: 'dsh-pfc-reset',\n textarea: 'dsh-pfc-textarea',\n toggleRow: 'dsh-pfc-toggle-row',\n toggle: 'dsh-pfc-toggle',\n toggleOn: 'dsh-pfc-toggle--on',\n knob: 'dsh-pfc-knob',\n knobOn: 'dsh-pfc-knob--on',\n hint: 'dsh-pfc-hint',\n footer: 'dsh-pfc-footer',\n failed: 'dsh-pfc-failed',\n discard: 'dsh-pfc-discard',\n save: 'dsh-pfc-save',\n} as const\n\n/** `data-plugin-css` tag id of the injected stylesheet. */\nconst STYLE_TAG_ID = '@huanlin/dsh-plugin-preface-context/preface-card.css'\n\n/** The card stylesheet (injected as one tagged `<style>` at activation). */\nconst CSS = `\n.dsh-pfc-card {\n list-style: none;\n border: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.22));\n background: var(--dsw-alias-bg-layer-3, transparent);\n border-radius: 12px;\n transition: border-color 0.16s, background 0.16s;\n overflow: hidden;\n}\n\n.dsh-pfc-card--open {\n border-color: var(--dsw-alias-border-l3, rgba(128, 128, 128, 0.35));\n}\n\n.dsh-pfc-header {\n display: flex;\n width: 100%;\n align-items: center;\n gap: 12px;\n padding: 14px 16px;\n font: inherit;\n color: inherit;\n text-align: left;\n background: transparent;\n border: 0;\n cursor: pointer;\n}\n\n.dsh-pfc-head-text {\n display: flex;\n flex: 1 1 auto;\n flex-direction: column;\n gap: 4px;\n min-width: 0;\n}\n\n.dsh-pfc-name {\n font-size: 15px;\n font-weight: 600;\n line-height: 1.4;\n color: var(--dsw-alias-label-primary, inherit);\n}\n\n.dsh-pfc-description {\n font-size: 13px;\n line-height: 1.5;\n color: var(--dsw-alias-label-tertiary, rgba(128, 128, 128, 0.7));\n}\n\n.dsh-pfc-pending {\n flex: none;\n white-space: nowrap;\n font-size: 11px;\n font-weight: 500;\n line-height: 17px;\n padding: 1px 8px;\n border-radius: 999px;\n color: var(--dsw-alias-label-secondary, inherit);\n background: var(--dsw-alias-bg-module-platform, rgba(128, 128, 128, 0.12));\n}\n\n.dsh-pfc-chevron {\n flex: none;\n display: inline-flex;\n align-items: center;\n color: var(--dsw-alias-label-tertiary, inherit);\n transition: transform 0.16s;\n}\n\n.dsh-pfc-chevron--open {\n transform: rotate(180deg);\n}\n\n.dsh-pfc-body {\n margin: 0 16px;\n padding: 12px 0 4px;\n border-top: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.22));\n}\n\n.dsh-pfc-readonly {\n margin: 0 0 8px;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-tertiary, rgba(128, 128, 128, 0.7));\n}\n\n.dsh-pfc-field {\n display: flex;\n flex-direction: column;\n gap: 4px;\n margin: 0 0 12px;\n}\n\n.dsh-pfc-field-head {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.dsh-pfc-label {\n font-size: 13px;\n font-weight: 500;\n color: var(--dsw-alias-label-primary, inherit);\n}\n\n.dsh-pfc-badges {\n display: flex;\n align-items: center;\n gap: 6px;\n margin-left: auto;\n}\n\n.dsh-pfc-badge {\n font-size: 11px;\n font-weight: 500;\n line-height: 17px;\n padding: 1px 8px;\n border-radius: 999px;\n color: var(--dsw-alias-label-secondary, inherit);\n background: var(--dsw-alias-bg-module-platform, rgba(128, 128, 128, 0.12));\n}\n\n.dsh-pfc-reset {\n appearance: none;\n font: inherit;\n font-size: 12px;\n padding: 0;\n border: 0;\n background: transparent;\n cursor: pointer;\n color: var(--dsw-alias-brand-primary, #0a84ff);\n}\n\n.dsh-pfc-reset:disabled {\n cursor: not-allowed;\n opacity: 0.5;\n}\n\n.dsh-pfc-textarea {\n width: 100%;\n box-sizing: border-box;\n min-height: 96px;\n resize: vertical;\n padding: 6px 10px;\n font-family: var(--dsw-font-mono, ui-monospace, monospace);\n font-size: 13px;\n line-height: 1.5;\n color: var(--dsw-alias-label-primary, inherit);\n background: var(--dsw-alias-bg-layer-3, transparent);\n border: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.3));\n border-radius: 8px;\n}\n\n.dsh-pfc-textarea:focus {\n outline: none;\n border-color: var(--dsw-alias-brand-primary, #0a84ff);\n}\n\n.dsh-pfc-textarea:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n}\n\n.dsh-pfc-toggle-row {\n display: flex;\n align-items: center;\n gap: 10px;\n}\n\n.dsh-pfc-toggle {\n position: relative;\n width: 40px;\n height: 22px;\n flex: none;\n padding: 0;\n background: var(--dsw-alias-bg-module-platform, rgba(128, 128, 128, 0.12));\n border: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.3));\n border-radius: 999px;\n cursor: pointer;\n transition: background 0.16s, border-color 0.16s;\n}\n\n.dsh-pfc-toggle--on {\n background: var(--dsw-alias-brand-primary, #0a84ff);\n border-color: var(--dsw-alias-brand-primary, #0a84ff);\n}\n\n.dsh-pfc-knob {\n position: absolute;\n top: 2px;\n left: 2px;\n width: 16px;\n height: 16px;\n background: var(--dsw-alias-bg-layer-1, #fff);\n border-radius: 50%;\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);\n transition: transform 0.16s;\n}\n\n.dsh-pfc-knob--on {\n transform: translateX(18px);\n}\n\n.dsh-pfc-toggle:disabled {\n cursor: not-allowed;\n opacity: 0.5;\n}\n\n.dsh-pfc-hint {\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-tertiary, rgba(128, 128, 128, 0.6));\n}\n\n.dsh-pfc-footer {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n gap: 8px;\n padding: 12px 0 4px;\n border-top: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.22));\n}\n\n.dsh-pfc-failed {\n flex: 1 1 auto;\n min-width: 0;\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-error, #ff453a);\n}\n\n.dsh-pfc-discard,\n.dsh-pfc-save {\n appearance: none;\n font: inherit;\n font-size: 13px;\n font-weight: 500;\n line-height: 20px;\n padding: 5px 14px;\n border: 1px solid transparent;\n border-radius: 8px;\n cursor: pointer;\n color: var(--dsw-alias-label-primary, inherit);\n background: var(--dsw-alias-bg-module-platform, rgba(128, 128, 128, 0.12));\n transition: background 0.16s, opacity 0.16s;\n}\n\n.dsh-pfc-save {\n background: var(--dsw-alias-brand-primary, #0a84ff);\n color: var(--dsw-alias-bg-layer-1, #fff);\n}\n\n.dsh-pfc-discard:disabled,\n.dsh-pfc-save:disabled {\n cursor: not-allowed;\n opacity: 0.5;\n}\n`\n\n/**\n * Install the card stylesheet as one tagged `<style>` element.\n * @returns the disposer removing the tag (idempotent outside a document).\n */\nexport function installPrefaceCardStyles(): () => void {\n if (typeof document === 'undefined') return () => {}\n if (document.querySelector(`style[data-plugin-css='${STYLE_TAG_ID}']`) !== null) return () => {}\n const style = document.createElement('style')\n style.dataset.plugin = '@huanlin/dsh-plugin-preface-context'\n style.dataset.pluginCss = STYLE_TAG_ID\n style.textContent = CSS\n document.head.appendChild(style)\n return () => { style.remove() }\n}\n","/**\n * The preface-context settings card: a collapsible card on the \"插件配置\"\n * settings page. Renders an enable toggle and a multiline textarea for the\n * context text. Stages edits locally and writes them on save through the\n * bound {@link PrefaceCardController}.\n *\n * The chrome mirrors the upstream `PluginCard` shape (a header button\n * stacking name over description, a dirty pill, a rotating chevron; a body\n * with the form controls and a footer with Discard/Save) because the\n * upstream client value face exports no reusable card. Styles come from the\n * statically injected `preface-card.css` stylesheet (the loader never serves\n * `.css` artifacts); every color resolves through a `--dsw-alias-*` token so\n * the card adapts to the light/dark theme.\n */\n\nimport { useState, type ReactNode } from 'react'\nimport clsx from 'clsx'\nimport { IconChevronDownOutline14 } from '@deepseek-ai/dsh-client-ui-primitives'\nimport type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'\nimport { css } from './preface-card.css.ts'\nimport type { PrefaceCardFace, PrefaceCardActions } from './preface-card-controller.ts'\n\n/** Injected dependencies of the card (slot `inject`). */\nexport type PrefaceCardInjected = PrefaceCardFace & PrefaceCardActions\n\n/** Props the renderer binds for the card. */\nexport type PrefaceCardProps =\n PropsRuntime<'settings.plugin.item'>\n & PropsLocale<'settings.preface-context'>\n & InjectFace<PrefaceCardInjected>\n\n/**\n * Render the preface-context card.\n * @param props - locale copy, the card snapshot, and its form actions.\n * @returns the card, or nothing when the namespace is unavailable.\n */\nexport function PrefaceCard(props: PrefaceCardProps): ReactNode {\n const [open, setOpen] = useState(false)\n const { t } = props\n const state = props.usePrefaceCard(snapshot => snapshot)\n if (!state.available) return null\n const title = t('title')\n const blocked = !state.dirty || state.saving\n const enabledOn = state.enabled.text === 'true'\n return (\n <li className={clsx(css.card, open && css.cardOpen)}>\n <button\n type=\"button\"\n className={css.header}\n aria-expanded={open}\n aria-label={`${t(open ? 'collapse' : 'expand')}: ${title}`}\n onClick={() => { setOpen(!open) }}\n >\n <span className={css.headText}>\n <span className={css.name}>{title}</span>\n <span className={css.description}>{t('description')}</span>\n </span>\n {state.dirty ? <span className={css.pending}>{t('unsaved')}</span> : null}\n <IconChevronDownOutline14 className={clsx(css.chevron, open && css.chevronOpen)} />\n </button>\n {open\n ? (\n <div className={css.body}>\n {!state.writable ? <p className={css.readOnly} role=\"status\">{t('readOnly')}</p> : null}\n <div className={css.field}>\n <div className={css.head}>\n <label className={css.label}>{t('enabled')}</label>\n {state.enabled.overridden\n ? (\n <span className={css.badges}>\n <span className={css.badge}>{t('overridden')}</span>\n <button\n type=\"button\"\n className={css.reset}\n disabled={!state.writable}\n onClick={() => { props.resetField('enabled') }}\n >\n {t('reset')}\n </button>\n </span>\n )\n : null}\n </div>\n <div className={css.toggleRow}>\n <button\n type=\"button\"\n role=\"switch\"\n aria-checked={enabledOn}\n className={clsx(css.toggle, enabledOn && css.toggleOn)}\n disabled={!state.writable}\n onClick={() => { props.edit('enabled', enabledOn ? 'false' : 'true') }}\n >\n <span className={clsx(css.knob, enabledOn && css.knobOn)} />\n </button>\n </div>\n <p className={css.hint}>{t('enabledHint')}</p>\n </div>\n <div className={css.field}>\n <div className={css.head}>\n <label className={css.label}>{t('contextText')}</label>\n {state.contextText.overridden\n ? (\n <span className={css.badges}>\n <span className={css.badge}>{t('overridden')}</span>\n <button\n type=\"button\"\n className={css.reset}\n disabled={!state.writable}\n onClick={() => { props.resetField('contextText') }}\n >\n {t('reset')}\n </button>\n </span>\n )\n : null}\n </div>\n <textarea\n id=\"plugin-config-preface-context-text\"\n className={css.textarea}\n value={state.contextText.text}\n placeholder={t('contextTextPlaceholder')}\n disabled={!state.writable}\n onChange={(event) => { props.edit('contextText', event.target.value) }}\n />\n <p className={css.hint}>{t('contextTextHint')}</p>\n </div>\n <div className={css.footer}>\n {state.failed ? <p className={css.failed} role=\"status\">{t('saveFailed')}</p> : null}\n <button\n type=\"button\"\n className={css.discard}\n disabled={!state.dirty || state.saving}\n onClick={props.discard}\n >\n {t('discard')}\n </button>\n <button\n type=\"button\"\n className={css.save}\n disabled={blocked}\n onClick={props.save}\n >\n {t(state.saving ? 'saving' : 'save')}\n </button>\n </div>\n </div>\n )\n : null}\n </li>\n )\n}\n","/**\n * The preface-context card's staged form over the `preface-context` settings\n * namespace.\n *\n * The upstream `ui-settings-plugins` package owns a `CardForm` helper, but it\n * is an internal module (not re-exported from the package's `/client` public\n * face), so an external plugin cannot reuse it. This controller is a slim\n * equivalent: it stages the two fields (`enabled` boolean, `contextText`\n * multiline string) and writes them through the bound {@link SettingsScope}\n * on save, re-deriving its projection whenever the scope or a draft changes.\n *\n * Unlike the upstream `CardForm`, the `contextText` field is a multiline\n * textarea: its draft preserves internal whitespace and newlines (the\n * upstream `textField` helper trims, which would collapse a multi-paragraph\n * preface into one line).\n */\n\nimport type { SnapshotStore } from '@deepseek-ai/dsh-client-store'\nimport type { SettingsScope, SettingsScopeSnapshot } from '@deepseek-ai/dsh-client-ui-settings/client'\n\n/** A minimal snapshot store: a value + a listener set. Self-contained so the\n * controller does not pull `createSnapshotStore` (a browser-bundle symbol)\n * into its unit-test graph. */\nclass MiniSnapshotStore<T> implements SnapshotStore<T> {\n private snapshot: T\n private readonly listeners = new Set<() => void>()\n\n constructor(initial: T) {\n this.snapshot = initial\n }\n\n getSnapshot(): T {\n return this.snapshot\n }\n\n set(value: T): void {\n this.snapshot = value\n for (const listener of [...this.listeners]) listener()\n }\n\n update(mutator: (draft: T) => void): void {\n // Shallow-clone then mutate (the projections here are plain objects with\n // no nested mutation, so a deep immer draft is unnecessary).\n const draft = Array.isArray(this.snapshot) ? [...this.snapshot] as unknown as T : { ...this.snapshot as object } as T\n mutator(draft)\n this.set(draft)\n }\n\n subscribe(listener: () => void): () => void {\n this.listeners.add(listener)\n return () => { this.listeners.delete(listener) }\n }\n}\nimport type { PrefaceConfig } from '../config.ts'\n\n/** The two fields this card edits, as draft text. */\nexport interface PrefaceDraft {\n /** `enabled` draft — 'true' or 'false'. */\n enabled: string\n /** `contextText` draft — the raw textarea value, whitespace preserved. */\n contextText: string\n}\n\n/** One field's render state. */\nexport interface PrefaceFieldState {\n /** Draft text the control renders. */\n text: string\n /** Whether saving would leave a user-layer entry for this field. */\n overridden: boolean\n}\n\n/** Card-level state shared with the component. */\nexport interface PrefaceCardState {\n /** False while the namespace is not served to this client. */\n available: boolean\n /** Whether the Host document accepts writes. */\n writable: boolean\n /** Whether the form holds edits that a save would write. */\n dirty: boolean\n /** Whether a save is crossing the wire. */\n saving: boolean\n /** Whether the last save did not land as staged. */\n failed: boolean\n /** The `enabled` field state. */\n enabled: PrefaceFieldState\n /** The `contextText` field state. */\n contextText: PrefaceFieldState\n}\n\n/** The write actions the card's slot entry injects. */\nexport interface PrefaceCardActions {\n /** Stage draft text for one field. */\n edit: (field: keyof PrefaceDraft, text: string) => void\n /** Stage a clear, so saving lets the field re-inherit the composition layer. */\n resetField: (field: keyof PrefaceDraft) => void\n /** Write every staged edit, then re-seed from what the Host accepted. */\n save: () => void\n /** Drop every staged edit. */\n discard: () => void\n}\n\n/** The registration-side face the card's slot entry injects. */\nexport interface PrefaceCardFace {\n hooks: {\n /** Card snapshot bound by the renderer as usePrefaceCard. */\n prefaceCard: SnapshotStore<PrefaceCardState>\n }\n}\n\n/** Field names this card edits, keyed for the staged-edit map. */\ntype FieldName = keyof PrefaceDraft\n\n/** One staged edit. */\ninterface StagedEdit {\n /** Draft text the control renders. */\n text: string\n /** True when this edit clears the field. */\n clear: boolean\n}\n\n/** The default value for a field when the user-layer carries none. */\nfunction baseValue(snapshot: SettingsScopeSnapshot<PrefaceConfig>, field: FieldName): unknown {\n return (snapshot.base as Record<string, unknown> | undefined)?.[field]\n}\n\n/** The current resolved value of a field. */\nfunction sectionValue(snapshot: SettingsScopeSnapshot<PrefaceConfig>, field: FieldName): unknown {\n return (snapshot.value as Record<string, unknown> | undefined)?.[field]\n}\n\n/** Whether the user layer carries an entry for a field. */\nfunction stored(snapshot: SettingsScopeSnapshot<PrefaceConfig>, field: FieldName): boolean {\n const user = snapshot.user as Record<string, unknown> | undefined\n return user !== undefined && Object.hasOwn(user, field)\n}\n\n/** Format a stored value as draft text for a field. */\nfunction format(field: FieldName, value: unknown): string {\n if (field === 'enabled') return value === true ? 'true' : 'false'\n if (field === 'contextText') return typeof value === 'string' ? value : ''\n return ''\n}\n\n/** Parse draft text into a write value, or undefined when invalid. */\nfunction parse(field: FieldName, text: string): { value: unknown } | { clear: true } | undefined {\n if (field === 'enabled') {\n // The toggle is a boolean; the textarea is not used for this field.\n return { value: text === 'true' }\n }\n // contextText: empty draft clears the field; otherwise the raw text is the\n // value. Internal whitespace and newlines are preserved.\n const trimmed = text.trim()\n return trimmed === '' ? { clear: true } : { value: text }\n}\n\n/**\n * Bridges the `preface-context` scope onto the card's staged form.\n *\n * Publishes through a snapshot store because slot components read through a\n * snapshot selector, while both the scope and the local drafts change\n * underneath; every projection is rebuilt from the two together.\n */\nexport class PrefaceCardController {\n private readonly scope: SettingsScope<PrefaceConfig>\n private readonly store: SnapshotStore<PrefaceCardState>\n private readonly staged = new Map<FieldName, StagedEdit>()\n private saving = false\n private failed = false\n\n /** @param scope - the bound settings scope for the `preface-context` namespace. */\n constructor(scope: SettingsScope<PrefaceConfig>) {\n this.scope = scope\n this.store = new MiniSnapshotStore<PrefaceCardState>(this.project())\n scope.subscribe(() => { this.publish() })\n }\n\n /** @returns the store the card's component reads through its bound selector. */\n get snapshot(): SnapshotStore<PrefaceCardState> {\n return this.store\n }\n\n private project(): PrefaceCardState {\n const snapshot = this.scope.getSnapshot()\n const dirty = this.staged.size > 0\n return {\n available: snapshot.status === 'ready',\n writable: snapshot.writable,\n dirty,\n saving: this.saving,\n failed: this.failed,\n enabled: this.fieldState(snapshot, 'enabled'),\n contextText: this.fieldState(snapshot, 'contextText'),\n }\n }\n\n private fieldState(snapshot: SettingsScopeSnapshot<PrefaceConfig>, field: FieldName): PrefaceFieldState {\n const staged = this.staged.get(field)\n if (staged === undefined) {\n return { text: format(field, sectionValue(snapshot, field)), overridden: stored(snapshot, field) }\n }\n const write = staged.clear ? { clear: true as const } : parse(field, staged.text)\n return {\n text: staged.text,\n overridden: write !== undefined && 'value' in write,\n }\n }\n\n /** Build the face the card's slot registration injects. */\n inject(): PrefaceCardFace & PrefaceCardActions {\n return {\n hooks: { prefaceCard: this.store },\n edit: (field, text) => { this.stage(field, { text, clear: false }) },\n resetField: (field) => {\n this.stage(field, { text: format(field, baseValue(this.scope.getSnapshot(), field)), clear: true })\n },\n save: () => { void this.save() },\n discard: () => {\n if (this.staged.size === 0 && !this.failed) return\n this.staged.clear()\n this.failed = false\n this.publish()\n },\n }\n }\n\n private stage(field: FieldName, edit: StagedEdit): void {\n this.staged.set(field, edit)\n this.failed = false\n this.publish()\n }\n\n private publish(): void {\n this.store.set(this.project())\n }\n\n /** Write every staged edit, then re-seed from what the Host accepted. */\n private async save(): Promise<void> {\n if (this.staged.size === 0 || this.saving) return\n this.saving = true\n this.failed = false\n this.publish()\n let landed = true\n for (const [field, staged] of this.staged) {\n const write = staged.clear ? { clear: true as const } : parse(field, staged.text)\n if (write === undefined) continue\n try {\n if ('clear' in write) {\n await this.scope.unset(field)\n } else {\n await this.scope.set(field, write.value)\n }\n } catch {\n landed = false\n }\n }\n if (landed) this.staged.clear()\n this.saving = false\n this.failed = !landed\n this.publish()\n }\n}\n","/** Locale bundles for the preface-context settings card. */\n\n/** Locale keys the preface-context card renders. */\nexport type PrefaceLocaleKey =\n | 'title' | 'description'\n | 'enabled' | 'enabledHint'\n | 'contextText' | 'contextTextHint' | 'contextTextPlaceholder'\n | 'overridden' | 'reset'\n | 'save' | 'saving' | 'discard' | 'unsaved' | 'saveFailed'\n | 'readOnly' | 'expand' | 'collapse'\n\n/** English copy. */\nexport const en: Record<PrefaceLocaleKey, string> = {\n title: 'Preface context',\n description: 'Inject a text block as model-visible instructions at the start of every session.',\n enabled: 'Enabled',\n enabledHint: 'When off, no context is injected at session start.',\n contextText: 'Context text',\n contextTextHint: 'Injected once per session as instructions context, closest to the model\\u2019s first answer. Empty text injects nothing.',\n contextTextPlaceholder: 'Enter the text to inject at the start of every session\\u2026',\n overridden: 'Overridden',\n reset: 'Reset to default',\n save: 'Save',\n saving: 'Saving\\u2026',\n discard: 'Discard',\n unsaved: 'Unsaved',\n saveFailed: 'The deployment did not accept these values; they were left for you to correct.',\n readOnly: 'This deployment stores settings read-only.',\n expand: 'Show settings',\n collapse: 'Hide settings',\n}\n\n/** Simplified Chinese copy. */\nexport const zh: Record<PrefaceLocaleKey, string> = {\n title: '前言上下文',\n description: '在每次会话开头注入一段文本作为模型可见的指令上下文。',\n enabled: '启用',\n enabledHint: '关闭后,会话开始时不注入任何上下文。',\n contextText: '上下文文本',\n contextTextHint: '每次会话注入一次,作为指令上下文最贴近模型的首个回答。文本为空则不注入。',\n contextTextPlaceholder: '输入要在每次会话开头注入的文本\\u2026',\n overridden: '已覆盖',\n reset: '恢复默认',\n save: '保存',\n saving: '保存中\\u2026',\n discard: '放弃修改',\n unsaved: '未保存',\n saveFailed: '本部署没有接受这些值,已保留供你修改。',\n readOnly: '本部署的设置为只读。',\n expand: '展开设置',\n collapse: '收起设置',\n}\n","/**\n * Preface-context settings plugin, browser half. Registers the\n * `preface-context` card into the shell-declared `settings.plugin.item` slot\n * (the \"插件配置\" settings page), keyed by the `preface-context` settings\n * namespace. The card's controller binds the namespace through\n * `ctx.settingsScope.bind()`, which reads/writes through the standard\n * `settings.describe` / `settings.mutate` RPCs (the apiproxy accepts any\n * registered namespace — no allowlist gate on current upstream dsh).\n *\n * Export discipline: value-imports ONLY the frozen platform module table\n * (React / cordis / ui-primitives); every other `@deepseek-ai/*` import is\n * type-only (erased at build) — values arrive via cordis injection\n * (`ctx.settingsScope` is the read/write channel).\n */\n\nimport type { Context } from '@deepseek-ai/cordis'\n// Type-only: pulls the locale plugin's Context merge (ctx.locale).\nimport type {} from '@deepseek-ai/dsh-client-locale/client'\n// Type-only: pulls the settings domain's ctx.settingsScope Context merge.\nimport type {} from '@deepseek-ai/dsh-client-ui-settings/client'\n// Type-only: pulls the plugin-config card slot's SlotMap merge (the\n// 'settings.plugin.item' keyed entry — this half's registration target).\nimport type {} from '@deepseek-ai/dsh-client-ui-settings-plugins/client'\n// Type-only: pulls the renderer's ctx.slots Context merge (the composition\n// root service; moved here from the deleted dsh-client-runtime).\nimport type {} from '@deepseek-ai/dsh-client-ui-renderer/client'\nimport { PrefaceCard } from './preface-card.tsx'\nimport type { PrefaceCardInjected } from './preface-card.tsx'\nimport { installPrefaceCardStyles } from './preface-card.css.ts'\nimport { PrefaceCardController } from './preface-card-controller.ts'\nimport { en, zh, type PrefaceLocaleKey } from './locales.ts'\n\nexport type { PrefaceCardProps } from './preface-card.tsx'\nexport type { PrefaceLocaleKey } from './locales.ts'\n\ndeclare module '@deepseek-ai/dsh-client-ui-slots' {\n interface LocaleNamespaceMap {\n /** The preface-context settings card copy. */\n 'settings.preface-context': PrefaceLocaleKey\n }\n}\n\n/** Dictionary namespace owned by this plugin. */\nconst NS = 'settings.preface-context'\n\n/** Settings namespace the host half registers (must match the host constant). */\nconst SETTINGS_NAMESPACE = 'preface-context'\n\n/**\n * Required services (cordis fiber inject). `settingsScope` is provided by the\n * ui-settings base plugin; `locale` by the locale plugin; `slots` is the\n * composition root. The target slot is declared by ui-settings-plugins'\n * `configurable` tab, whose activation order relative to this one is\n * unconstrained; registration depends on the slot through `slots.inject()`.\n */\nexport const inject = ['slots', 'locale', 'settingsScope']\n\n/**\n * Register the preface-context card once the `settings.plugin.item`\n * declaration is on the ledger, wire its controller to the settings scope,\n * and keep it fresh on every pushed invalidation.\n * @param ctx - client root context.\n */\nexport function apply(ctx: Context): void {\n ctx.effect(installPrefaceCardStyles, 'preface-context: card styles')\n\n ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'preface-context: copy dictionaries')\n\n const settingsScope = ctx.settingsScope\n const controller = new PrefaceCardController(\n settingsScope.bind({ namespace: SETTINGS_NAMESPACE }),\n )\n\n ctx.slots.inject('settings.plugin.item', function* () {\n yield ctx.slots.register({\n name: 'settings.plugin.item',\n key: SETTINGS_NAMESPACE,\n locale: NS,\n inject: (): PrefaceCardInjected => controller.inject(),\n }, PrefaceCard)\n })\n}\n"],"x_google_ignoreList":[0],"mappings":"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"}
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
2
|
+
import z from "schemastery";
|
|
3
|
+
|
|
4
|
+
//#region src/config.ts
|
|
5
|
+
/**
|
|
6
|
+
* Schemastery schema for the `preface-context` settings namespace.
|
|
7
|
+
*
|
|
8
|
+
* Strict by construction: unknown keys fail validation here, even though the
|
|
9
|
+
* settings service itself is non-strict and would otherwise accept them.
|
|
10
|
+
*/
|
|
11
|
+
const Config = z.object({
|
|
12
|
+
enabled: z.boolean().default(true),
|
|
13
|
+
contextText: z.string().default("")
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
//#endregion
|
|
17
|
+
//#region src/settings.ts
|
|
18
|
+
/** The `preface-context` settings namespace (registered when a settings service exists). */
|
|
19
|
+
const PREFACE_SETTINGS_NAMESPACE = "preface-context";
|
|
20
|
+
/**
|
|
21
|
+
* Mirror of @deepseek-ai/dsh-settings' `isUnloading` guard (its
|
|
22
|
+
* `installSettingsSection` skips source/listener work while the plugin fiber
|
|
23
|
+
* is unloading or disposed). The library compares `ctx.fiber.state` against
|
|
24
|
+
* `FiberState.DISPOSED` / `FiberState.UNLOADING`; the const enum is erased at
|
|
25
|
+
* runtime, so the vendored numeric values (4 / 5) are mirrored here.
|
|
26
|
+
*/
|
|
27
|
+
function isUnloading(ctx) {
|
|
28
|
+
const state = ctx.fiber?.state;
|
|
29
|
+
return state === 4 || state === 5;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Install the `preface-context` settings namespace and wire the live source.
|
|
33
|
+
*
|
|
34
|
+
* Mirrors the dsh `installSettingsSection` pattern. The registration rides a
|
|
35
|
+
* conditional `ctx.inject(['settings'], ...)` child, so with no settings
|
|
36
|
+
* service the source stays the entry config. Multi-fiber dedupe: the host
|
|
37
|
+
* may compose several preface-context fibers, and `settings.register` fails
|
|
38
|
+
* loud on a duplicate namespace — the catch lets the first registration own
|
|
39
|
+
* the live namespace while later instances fall back to the entry source.
|
|
40
|
+
* @param ctx - consumer plugin context owning the wiring.
|
|
41
|
+
* @param entry - the composition entry config, used as `base`.
|
|
42
|
+
* @returns the bridge the runtime and gateway read through.
|
|
43
|
+
*/
|
|
44
|
+
function installPrefaceSettings(ctx, entry) {
|
|
45
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
46
|
+
let source = () => entry;
|
|
47
|
+
const notify = () => {
|
|
48
|
+
for (const listener of [...listeners]) listener();
|
|
49
|
+
};
|
|
50
|
+
ctx.inject(["settings"], (sctx) => {
|
|
51
|
+
let scope;
|
|
52
|
+
try {
|
|
53
|
+
scope = sctx.settings.register(PREFACE_SETTINGS_NAMESPACE, Config, { base: entry });
|
|
54
|
+
} catch (error) {
|
|
55
|
+
if (!(error instanceof Error) || !error.message.includes("already registered")) throw error;
|
|
56
|
+
ctx.logger("preface-context").debug("settings namespace already registered — entry-source fallback (multi-fiber dedupe)");
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
source = () => scope.get();
|
|
60
|
+
sctx.effect(() => () => {
|
|
61
|
+
if (isUnloading(ctx)) return;
|
|
62
|
+
source = () => entry;
|
|
63
|
+
notify();
|
|
64
|
+
});
|
|
65
|
+
notify();
|
|
66
|
+
scope.watch(() => {
|
|
67
|
+
if (isUnloading(ctx)) return;
|
|
68
|
+
notify();
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
return {
|
|
72
|
+
source: () => source(),
|
|
73
|
+
onChange: (callback) => {
|
|
74
|
+
listeners.add(callback);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
//#endregion
|
|
80
|
+
//#region src/index.ts
|
|
81
|
+
const name = "dsh-plugin-preface-context";
|
|
82
|
+
/** Services the plugin consumes; the row loads once all are available. */
|
|
83
|
+
const inject = ["agents", "llm"];
|
|
84
|
+
/** Plugin source tag carried by every injected message. */
|
|
85
|
+
const PLUGIN_SOURCE = {
|
|
86
|
+
kind: "plugin",
|
|
87
|
+
plugin: "dsh-plugin-preface-context",
|
|
88
|
+
form: "instructions"
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* Build the user-message to inject for one session start.
|
|
92
|
+
*
|
|
93
|
+
* The message carries the context text as a single text block and is tagged
|
|
94
|
+
* `source.kind: 'plugin'` with `form: 'instructions'` so the model and UI
|
|
95
|
+
* can present it as workspace instructions rather than a user prompt.
|
|
96
|
+
* @param config - the live composed config.
|
|
97
|
+
* @returns the message to inject, or `undefined` when injection is disabled
|
|
98
|
+
* or the text is empty/whitespace.
|
|
99
|
+
*/
|
|
100
|
+
function buildPrefaceMessage(config) {
|
|
101
|
+
if (!config.enabled) return void 0;
|
|
102
|
+
if (config.contextText.trim() === "") return void 0;
|
|
103
|
+
return createUserMessage({
|
|
104
|
+
content: [{
|
|
105
|
+
type: "text",
|
|
106
|
+
text: config.contextText
|
|
107
|
+
}],
|
|
108
|
+
source: PLUGIN_SOURCE
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Plugin entry: install the settings bridge, then inject the preface text on
|
|
113
|
+
* every session start.
|
|
114
|
+
* @param ctx - the plugin's cordis context.
|
|
115
|
+
* @param config - the composition entry config (base layer of the settings namespace).
|
|
116
|
+
*/
|
|
117
|
+
function apply(ctx, config) {
|
|
118
|
+
const bridge = installPrefaceSettings(ctx, config);
|
|
119
|
+
ctx.on("agent/session-start", ({ agent }) => {
|
|
120
|
+
const message = buildPrefaceMessage(bridge.source());
|
|
121
|
+
if (message === void 0) return;
|
|
122
|
+
agent.inject(message);
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
//#endregion
|
|
127
|
+
export { Config, apply, buildPrefaceMessage, inject, name };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { HostObservable, SnapshotSelectorHook } from '@deepseek-ai/dsh-client-ui-slots';
|
|
2
|
+
/**
|
|
3
|
+
* Bind a bare observable source to a typed uSES selector hook.
|
|
4
|
+
* @param w - snapshot source (engine store, controller store instance).
|
|
5
|
+
* @returns the selector hook.
|
|
6
|
+
*/
|
|
7
|
+
export declare function bindSnapshotSelector<T>(w: HostObservable<T>): SnapshotSelectorHook<T>;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* uSES bridge: turns any bare observable snapshot source into a typed
|
|
3
|
+
* selector hook. Inlined here because `@deepseek-ai/dsh-client-ui-renderer`
|
|
4
|
+
* does not re-export `bindSnapshotSelector` from its public `/client` face
|
|
5
|
+
* (rc.8+); business plugins must provide their own copy.
|
|
6
|
+
*/
|
|
7
|
+
import { useSyncExternalStoreWithSelector } from 'use-sync-external-store/shim/with-selector';
|
|
8
|
+
/**
|
|
9
|
+
* Bind a bare observable source to a typed uSES selector hook.
|
|
10
|
+
* @param w - snapshot source (engine store, controller store instance).
|
|
11
|
+
* @returns the selector hook.
|
|
12
|
+
*/
|
|
13
|
+
export function bindSnapshotSelector(w) {
|
|
14
|
+
const subscribe = (fn) => w.subscribe(fn);
|
|
15
|
+
const getSnapshot = () => w.getSnapshot();
|
|
16
|
+
return function useSelector(sel, eq) {
|
|
17
|
+
return useSyncExternalStoreWithSelector(subscribe, getSnapshot, undefined, sel, eq);
|
|
18
|
+
};
|
|
19
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { HostObservable, SnapshotSelectorHook } from '@deepseek-ai/dsh-client-ui-slots';
|
|
2
|
+
/**
|
|
3
|
+
* Bind a bare observable source to a typed uSES selector hook.
|
|
4
|
+
* @param w - snapshot source (engine store, controller store instance).
|
|
5
|
+
* @returns the selector hook.
|
|
6
|
+
*/
|
|
7
|
+
export declare function bindSnapshotSelector<T>(w: HostObservable<T>): SnapshotSelectorHook<T>;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* uSES bridge: turns any bare observable snapshot source into a typed
|
|
3
|
+
* selector hook. Inlined here because `@deepseek-ai/dsh-client-ui-renderer`
|
|
4
|
+
* does not re-export `bindSnapshotSelector` from its public `/client` face
|
|
5
|
+
* (rc.8+); business plugins must provide their own copy.
|
|
6
|
+
*/
|
|
7
|
+
import { useSyncExternalStoreWithSelector } from 'use-sync-external-store/shim/with-selector';
|
|
8
|
+
/**
|
|
9
|
+
* Bind a bare observable source to a typed uSES selector hook.
|
|
10
|
+
* @param w - snapshot source (engine store, controller store instance).
|
|
11
|
+
* @returns the selector hook.
|
|
12
|
+
*/
|
|
13
|
+
export function bindSnapshotSelector(w) {
|
|
14
|
+
const subscribe = (fn) => w.subscribe(fn);
|
|
15
|
+
const getSnapshot = () => w.getSnapshot();
|
|
16
|
+
return function useSelector(sel, eq) {
|
|
17
|
+
return useSyncExternalStoreWithSelector(subscribe, getSnapshot, undefined, sel, eq);
|
|
18
|
+
};
|
|
19
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Preface-context settings plugin, browser half. Registers the
|
|
3
|
+
* `preface-context` card into the shell-declared `settings.plugin.item` slot
|
|
4
|
+
* (the "插件配置" settings page), keyed by the `preface-context` settings
|
|
5
|
+
* namespace. The card's controller binds the namespace through
|
|
6
|
+
* `ctx.settingsScope.bind()`, which reads/writes through the standard
|
|
7
|
+
* `settings.describe` / `settings.mutate` RPCs (the apiproxy accepts any
|
|
8
|
+
* registered namespace — no allowlist gate on current upstream dsh).
|
|
9
|
+
*
|
|
10
|
+
* Export discipline: value-imports ONLY the frozen platform module table
|
|
11
|
+
* (React / cordis / ui-primitives); every other `@deepseek-ai/*` import is
|
|
12
|
+
* type-only (erased at build) — values arrive via cordis injection
|
|
13
|
+
* (`ctx.settingsScope` is the read/write channel).
|
|
14
|
+
*/
|
|
15
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
16
|
+
import { type PrefaceLocaleKey } from './locales.ts';
|
|
17
|
+
export type { PrefaceCardProps } from './preface-card.tsx';
|
|
18
|
+
export type { PrefaceLocaleKey } from './locales.ts';
|
|
19
|
+
declare module '@deepseek-ai/dsh-client-ui-slots' {
|
|
20
|
+
interface LocaleNamespaceMap {
|
|
21
|
+
/** The preface-context settings card copy. */
|
|
22
|
+
'settings.preface-context': PrefaceLocaleKey;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Required services (cordis fiber inject). `settingsScope` is provided by the
|
|
27
|
+
* ui-settings base plugin; `locale` by the locale plugin; `slots` is the
|
|
28
|
+
* composition root. The target slot is declared by ui-settings-plugins'
|
|
29
|
+
* `configurable` tab, whose activation order relative to this one is
|
|
30
|
+
* unconstrained; registration depends on the slot through `slots.inject()`.
|
|
31
|
+
*/
|
|
32
|
+
export declare const inject: string[];
|
|
33
|
+
/**
|
|
34
|
+
* Register the preface-context card once the `settings.plugin.item`
|
|
35
|
+
* declaration is on the ledger, wire its controller to the settings scope,
|
|
36
|
+
* and keep it fresh on every pushed invalidation.
|
|
37
|
+
* @param ctx - client root context.
|
|
38
|
+
*/
|
|
39
|
+
export declare function apply(ctx: Context): void;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Preface-context settings plugin, browser half. Registers the
|
|
3
|
+
* `preface-context` card into the shell-declared `settings.plugin.item` slot
|
|
4
|
+
* (the "插件配置" settings page), keyed by the `preface-context` settings
|
|
5
|
+
* namespace. The card's controller binds the namespace through
|
|
6
|
+
* `ctx.settingsScope.bind()`, which reads/writes through the standard
|
|
7
|
+
* `settings.describe` / `settings.mutate` RPCs (the apiproxy accepts any
|
|
8
|
+
* registered namespace — no allowlist gate on current upstream dsh).
|
|
9
|
+
*
|
|
10
|
+
* Export discipline: value-imports ONLY the frozen platform module table
|
|
11
|
+
* (React / cordis / ui-primitives); every other `@deepseek-ai/*` import is
|
|
12
|
+
* type-only (erased at build) — values arrive via cordis injection
|
|
13
|
+
* (`ctx.settingsScope` is the read/write channel).
|
|
14
|
+
*/
|
|
15
|
+
import { PrefaceCard } from "./preface-card.js";
|
|
16
|
+
import { installPrefaceCardStyles } from "./preface-card.css.js";
|
|
17
|
+
import { PrefaceCardController } from "./preface-card-controller.js";
|
|
18
|
+
import { en, zh } from "./locales.js";
|
|
19
|
+
/** Dictionary namespace owned by this plugin. */
|
|
20
|
+
const NS = 'settings.preface-context';
|
|
21
|
+
/** Settings namespace the host half registers (must match the host constant). */
|
|
22
|
+
const SETTINGS_NAMESPACE = 'preface-context';
|
|
23
|
+
/**
|
|
24
|
+
* Required services (cordis fiber inject). `settingsScope` is provided by the
|
|
25
|
+
* ui-settings base plugin; `locale` by the locale plugin; `slots` is the
|
|
26
|
+
* composition root. The target slot is declared by ui-settings-plugins'
|
|
27
|
+
* `configurable` tab, whose activation order relative to this one is
|
|
28
|
+
* unconstrained; registration depends on the slot through `slots.inject()`.
|
|
29
|
+
*/
|
|
30
|
+
export const inject = ['slots', 'locale', 'settingsScope'];
|
|
31
|
+
/**
|
|
32
|
+
* Register the preface-context card once the `settings.plugin.item`
|
|
33
|
+
* declaration is on the ledger, wire its controller to the settings scope,
|
|
34
|
+
* and keep it fresh on every pushed invalidation.
|
|
35
|
+
* @param ctx - client root context.
|
|
36
|
+
*/
|
|
37
|
+
export function apply(ctx) {
|
|
38
|
+
ctx.effect(installPrefaceCardStyles, 'preface-context: card styles');
|
|
39
|
+
ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'preface-context: copy dictionaries');
|
|
40
|
+
const settingsScope = ctx.settingsScope;
|
|
41
|
+
const controller = new PrefaceCardController(settingsScope.bind({ namespace: SETTINGS_NAMESPACE }));
|
|
42
|
+
ctx.slots.inject('settings.plugin.item', function* () {
|
|
43
|
+
yield ctx.slots.register({
|
|
44
|
+
name: 'settings.plugin.item',
|
|
45
|
+
key: SETTINGS_NAMESPACE,
|
|
46
|
+
locale: NS,
|
|
47
|
+
inject: () => controller.inject(),
|
|
48
|
+
}, PrefaceCard);
|
|
49
|
+
});
|
|
50
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Locale bundles for the preface-context settings card. */
|
|
2
|
+
/** Locale keys the preface-context card renders. */
|
|
3
|
+
export type PrefaceLocaleKey = 'title' | 'description' | 'enabled' | 'enabledHint' | 'contextText' | 'contextTextHint' | 'contextTextPlaceholder' | 'overridden' | 'reset' | 'save' | 'saving' | 'discard' | 'unsaved' | 'saveFailed' | 'readOnly' | 'expand' | 'collapse';
|
|
4
|
+
/** English copy. */
|
|
5
|
+
export declare const en: Record<PrefaceLocaleKey, string>;
|
|
6
|
+
/** Simplified Chinese copy. */
|
|
7
|
+
export declare const zh: Record<PrefaceLocaleKey, string>;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/** Locale bundles for the preface-context settings card. */
|
|
2
|
+
/** English copy. */
|
|
3
|
+
export const en = {
|
|
4
|
+
title: 'Preface context',
|
|
5
|
+
description: 'Inject a text block as model-visible instructions at the start of every session.',
|
|
6
|
+
enabled: 'Enabled',
|
|
7
|
+
enabledHint: 'When off, no context is injected at session start.',
|
|
8
|
+
contextText: 'Context text',
|
|
9
|
+
contextTextHint: 'Injected once per session as instructions context, closest to the model\u2019s first answer. Empty text injects nothing.',
|
|
10
|
+
contextTextPlaceholder: 'Enter the text to inject at the start of every session\u2026',
|
|
11
|
+
overridden: 'Overridden',
|
|
12
|
+
reset: 'Reset to default',
|
|
13
|
+
save: 'Save',
|
|
14
|
+
saving: 'Saving\u2026',
|
|
15
|
+
discard: 'Discard',
|
|
16
|
+
unsaved: 'Unsaved',
|
|
17
|
+
saveFailed: 'The deployment did not accept these values; they were left for you to correct.',
|
|
18
|
+
readOnly: 'This deployment stores settings read-only.',
|
|
19
|
+
expand: 'Show settings',
|
|
20
|
+
collapse: 'Hide settings',
|
|
21
|
+
};
|
|
22
|
+
/** Simplified Chinese copy. */
|
|
23
|
+
export const zh = {
|
|
24
|
+
title: '前言上下文',
|
|
25
|
+
description: '在每次会话开头注入一段文本作为模型可见的指令上下文。',
|
|
26
|
+
enabled: '启用',
|
|
27
|
+
enabledHint: '关闭后,会话开始时不注入任何上下文。',
|
|
28
|
+
contextText: '上下文文本',
|
|
29
|
+
contextTextHint: '每次会话注入一次,作为指令上下文最贴近模型的首个回答。文本为空则不注入。',
|
|
30
|
+
contextTextPlaceholder: '输入要在每次会话开头注入的文本\u2026',
|
|
31
|
+
overridden: '已覆盖',
|
|
32
|
+
reset: '恢复默认',
|
|
33
|
+
save: '保存',
|
|
34
|
+
saving: '保存中\u2026',
|
|
35
|
+
discard: '放弃修改',
|
|
36
|
+
unsaved: '未保存',
|
|
37
|
+
saveFailed: '本部署没有接受这些值,已保留供你修改。',
|
|
38
|
+
readOnly: '本部署的设置为只读。',
|
|
39
|
+
expand: '展开设置',
|
|
40
|
+
collapse: '收起设置',
|
|
41
|
+
};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The preface-context card's staged form over the `preface-context` settings
|
|
3
|
+
* namespace.
|
|
4
|
+
*
|
|
5
|
+
* The upstream `ui-settings-plugins` package owns a `CardForm` helper, but it
|
|
6
|
+
* is an internal module (not re-exported from the package's `/client` public
|
|
7
|
+
* face), so an external plugin cannot reuse it. This controller is a slim
|
|
8
|
+
* equivalent: it stages the two fields (`enabled` boolean, `contextText`
|
|
9
|
+
* multiline string) and writes them through the bound {@link SettingsScope}
|
|
10
|
+
* on save, re-deriving its projection whenever the scope or a draft changes.
|
|
11
|
+
*
|
|
12
|
+
* Unlike the upstream `CardForm`, the `contextText` field is a multiline
|
|
13
|
+
* textarea: its draft preserves internal whitespace and newlines (the
|
|
14
|
+
* upstream `textField` helper trims, which would collapse a multi-paragraph
|
|
15
|
+
* preface into one line).
|
|
16
|
+
*/
|
|
17
|
+
import type { SnapshotStore } from '@deepseek-ai/dsh-client-store';
|
|
18
|
+
import type { SettingsScope } from '@deepseek-ai/dsh-client-ui-settings/client';
|
|
19
|
+
import type { PrefaceConfig } from '../config.ts';
|
|
20
|
+
/** The two fields this card edits, as draft text. */
|
|
21
|
+
export interface PrefaceDraft {
|
|
22
|
+
/** `enabled` draft — 'true' or 'false'. */
|
|
23
|
+
enabled: string;
|
|
24
|
+
/** `contextText` draft — the raw textarea value, whitespace preserved. */
|
|
25
|
+
contextText: string;
|
|
26
|
+
}
|
|
27
|
+
/** One field's render state. */
|
|
28
|
+
export interface PrefaceFieldState {
|
|
29
|
+
/** Draft text the control renders. */
|
|
30
|
+
text: string;
|
|
31
|
+
/** Whether saving would leave a user-layer entry for this field. */
|
|
32
|
+
overridden: boolean;
|
|
33
|
+
}
|
|
34
|
+
/** Card-level state shared with the component. */
|
|
35
|
+
export interface PrefaceCardState {
|
|
36
|
+
/** False while the namespace is not served to this client. */
|
|
37
|
+
available: boolean;
|
|
38
|
+
/** Whether the Host document accepts writes. */
|
|
39
|
+
writable: boolean;
|
|
40
|
+
/** Whether the form holds edits that a save would write. */
|
|
41
|
+
dirty: boolean;
|
|
42
|
+
/** Whether a save is crossing the wire. */
|
|
43
|
+
saving: boolean;
|
|
44
|
+
/** Whether the last save did not land as staged. */
|
|
45
|
+
failed: boolean;
|
|
46
|
+
/** The `enabled` field state. */
|
|
47
|
+
enabled: PrefaceFieldState;
|
|
48
|
+
/** The `contextText` field state. */
|
|
49
|
+
contextText: PrefaceFieldState;
|
|
50
|
+
}
|
|
51
|
+
/** The write actions the card's slot entry injects. */
|
|
52
|
+
export interface PrefaceCardActions {
|
|
53
|
+
/** Stage draft text for one field. */
|
|
54
|
+
edit: (field: keyof PrefaceDraft, text: string) => void;
|
|
55
|
+
/** Stage a clear, so saving lets the field re-inherit the composition layer. */
|
|
56
|
+
resetField: (field: keyof PrefaceDraft) => void;
|
|
57
|
+
/** Write every staged edit, then re-seed from what the Host accepted. */
|
|
58
|
+
save: () => void;
|
|
59
|
+
/** Drop every staged edit. */
|
|
60
|
+
discard: () => void;
|
|
61
|
+
}
|
|
62
|
+
/** The registration-side face the card's slot entry injects. */
|
|
63
|
+
export interface PrefaceCardFace {
|
|
64
|
+
hooks: {
|
|
65
|
+
/** Card snapshot bound by the renderer as usePrefaceCard. */
|
|
66
|
+
prefaceCard: SnapshotStore<PrefaceCardState>;
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Bridges the `preface-context` scope onto the card's staged form.
|
|
71
|
+
*
|
|
72
|
+
* Publishes through a snapshot store because slot components read through a
|
|
73
|
+
* snapshot selector, while both the scope and the local drafts change
|
|
74
|
+
* underneath; every projection is rebuilt from the two together.
|
|
75
|
+
*/
|
|
76
|
+
export declare class PrefaceCardController {
|
|
77
|
+
private readonly scope;
|
|
78
|
+
private readonly store;
|
|
79
|
+
private readonly staged;
|
|
80
|
+
private saving;
|
|
81
|
+
private failed;
|
|
82
|
+
/** @param scope - the bound settings scope for the `preface-context` namespace. */
|
|
83
|
+
constructor(scope: SettingsScope<PrefaceConfig>);
|
|
84
|
+
/** @returns the store the card's component reads through its bound selector. */
|
|
85
|
+
get snapshot(): SnapshotStore<PrefaceCardState>;
|
|
86
|
+
private project;
|
|
87
|
+
private fieldState;
|
|
88
|
+
/** Build the face the card's slot registration injects. */
|
|
89
|
+
inject(): PrefaceCardFace & PrefaceCardActions;
|
|
90
|
+
private stage;
|
|
91
|
+
private publish;
|
|
92
|
+
/** Write every staged edit, then re-seed from what the Host accepted. */
|
|
93
|
+
private save;
|
|
94
|
+
}
|