@things-factory/figure-ui 10.1.63 → 10.1.65
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/client/figure-assistant.ts +26 -10
- package/client/graphql/index.ts +0 -58
- package/client/modeller/figure-v3-properties.ts +6 -1
- package/client/modeller/v3-ai-proposal.ts +111 -0
- package/client/modeller/v3-product-session.ts +10 -1
- package/client/pages/figure-v3-modeller-page.ts +39 -4
- package/client/types.ts +0 -39
- package/dist-client/figure-assistant.js +22 -10
- package/dist-client/figure-assistant.js.map +1 -1
- package/dist-client/graphql/index.d.ts +1 -19
- package/dist-client/graphql/index.js +0 -46
- package/dist-client/graphql/index.js.map +1 -1
- package/dist-client/modeller/figure-v3-properties.js +6 -1
- package/dist-client/modeller/figure-v3-properties.js.map +1 -1
- package/dist-client/modeller/v3-ai-proposal.d.ts +51 -0
- package/dist-client/modeller/v3-ai-proposal.js +64 -0
- package/dist-client/modeller/v3-ai-proposal.js.map +1 -0
- package/dist-client/modeller/v3-product-session.d.ts +35 -1
- package/dist-client/modeller/v3-product-session.js +8 -0
- package/dist-client/modeller/v3-product-session.js.map +1 -1
- package/dist-client/pages/figure-v3-modeller-page.d.ts +7 -1
- package/dist-client/pages/figure-v3-modeller-page.js +46 -5
- package/dist-client/pages/figure-v3-modeller-page.js.map +1 -1
- package/dist-client/route.d.ts +1 -1
- package/dist-client/tsconfig.fui.tsbuildinfo +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-client/types.d.ts +0 -44
- package/dist-client/types.js.map +1 -1
- package/package.json +5 -5
- package/test/figure-assistant-surface.test.ts +9 -6
- package/test/v3-ai-proposal.test.ts +112 -0
- package/test/v3-refusals.test.ts +5 -0
- package/translations/en.json +6 -1
- package/translations/ja.json +6 -1
- package/translations/ko.json +6 -1
- package/translations/ms.json +6 -1
- package/translations/zh.json +6 -1
- package/client/modeller/proposal-session.ts +0 -98
- package/client/modeller/proposal.ts +0 -171
- package/dist-client/modeller/proposal-session.d.ts +0 -28
- package/dist-client/modeller/proposal-session.js +0 -66
- package/dist-client/modeller/proposal-session.js.map +0 -1
- package/dist-client/modeller/proposal.d.ts +0 -45
- package/dist-client/modeller/proposal.js +0 -114
- package/dist-client/modeller/proposal.js.map +0 -1
- package/test/ai-proposal-contract.test.ts +0 -563
|
@@ -113,7 +113,12 @@ let FigureV3Properties = class FigureV3Properties extends localize(i18next)(LitE
|
|
|
113
113
|
<select
|
|
114
114
|
?disabled=${!facts.parts.length}
|
|
115
115
|
.value=${live(current)}
|
|
116
|
-
@change=${(event) =>
|
|
116
|
+
@change=${(event) => {
|
|
117
|
+
const select = event.target;
|
|
118
|
+
this.declare(select.value);
|
|
119
|
+
/* Refused, the figure is placed as it was, and the list says so; taken, the figure drawn again says the new one. */
|
|
120
|
+
select.value = current;
|
|
121
|
+
}}
|
|
117
122
|
>
|
|
118
123
|
${PLACEMENTS.map(one => html `<option value=${one} ?selected=${one === current}>${i18next.t('figure.label.placement-' + one)}</option>`)}
|
|
119
124
|
</select>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"figure-v3-properties.js","sourceRoot":"","sources":["../../client/modeller/figure-v3-properties.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\n\nimport { css, html, LitElement, nothing } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\nimport { live } from 'lit/directives/live.js'\n\nimport { i18next, localize } from '@operato/i18n'\nimport { V3_FACES } from '@hatiolab/figure-model'\nimport type { V3AuthoringAction, V3Face } from '@hatiolab/figure-model'\n\nimport { occupancyFromParts } from './v3-occupancy.js'\nimport { tagsOf, v3FigureFacts, type Placement, type V3FigureFacts } from './v3-figure-facts.js'\nimport { SidePanelStyles } from './side-panel-styles.js'\nimport { ScrollbarStyles } from '@operato/styles'\n\nconst PLACEMENTS: Placement[] = ['floor', 'ceiling', 'center', 'wall']\nconst FACES = Object.keys(V3_FACES) as V3Face[]\n\n/**\n * 속성 — the V3 modeller's figure properties (parity row ②).\n *\n * The V2 inspector opens with what the figure is: its name, the face it is placed by, the room it takes up, where\n * the catalogue files it. The V3 page had only the type. The same rows, in the same order, drawn the way the V2\n * inspector draws them.\n *\n * Two kinds of change leave this tab, and it does neither itself:\n * - `v3-command` carries an authoring command (rename, declare the room, the mounting face). The page runs it\n * through the session, so it is one step of the same undo history as every other edit, and is saved with it.\n * - `v3-catalog` carries a change to the row (category, tags). Those are not in the asset; the page writes them\n * to the saved row at once, so they need a saved figure.\n *\n * Not here yet, because V3 has no place for them: editing the declared room by hand (V3 declares it from the\n * parts, as graph expressions), and the detail level (the converter records it as lost). Both wait on a V3 ruling.\n */\n@customElement('figure-v3-properties')\nexport class FigureV3Properties extends localize(i18next)(LitElement) {\n static styles = [ScrollbarStyles, SidePanelStyles, css`\n :host {\n display: block;\n /* It stands inside the properties tab's panel, which gives the margin; a second one indented it past the type. */\n padding: 0;\n overflow: auto;\n }\n div[row] {\n display: flex;\n gap: 6px;\n align-items: center;\n }\n div[row] > select {\n flex: 1;\n }\n div[row] > button[act] {\n flex: 1;\n width: auto;\n margin-top: 0;\n }\n div[row] > span {\n flex: 1;\n }\n `]\n\n /** The source being edited. */\n @property({ type: String }) source?: string\n /** The saved row, if any. Category and tags live on it. */\n @property({ type: String }) figureId?: string\n @property({ type: String }) figureCategory?: string\n @property({ type: Array }) figureTags?: string[]\n /** The part and face picked for the mounting face, before 「이 면으로」 is pressed. */\n @state() private pickedPart?: string\n @state() private pickedFace?: V3Face\n\n private facts(): V3FigureFacts | undefined {\n try {\n return this.source ? v3FigureFacts(this.source) : undefined\n } catch {\n /* A source the format refuses has no facts to show; the verdict tab says why. */\n return undefined\n }\n }\n\n render() {\n const facts = this.facts()\n if (!facts) return nothing\n /* The figure's type is the page's (it lives on the row); it is slotted into the first section, as V2 had it. */\n return html`<section>\n <h3>${i18next.t('figure.label.group-figure')}</h3>\n <slot name=\"type\"></slot>\n ${this.renderName(facts)} ${this.renderPlacement(facts)}\n </section>\n <section>\n <h3>${i18next.t('figure.label.group-standing')}</h3>\n ${this.renderSpace(facts)} ${this.renderMounting(facts)}\n </section>\n <section>\n <h3>${i18next.t('figure.label.group-catalog')}</h3>\n ${this.renderCatalog()}\n </section>`\n }\n\n private renderName(facts: V3FigureFacts) {\n return html`\n <div field>\n <label>${i18next.t('figure.label.name')}</label>\n <input\n .value=${facts.name}\n placeholder=${i18next.t('figure.text.figure-display-name')}\n @change=${(event: Event) => {\n const input = event.target as HTMLInputElement\n if (!input.value.trim()) {\n /* The format refuses a blank name; put the old one back rather than send a command that fails. */\n input.value = facts.name\n return\n }\n if (input.value.trim() !== facts.name) this.command({ kind: 'rename', name: input.value })\n }}\n />\n </div>\n `\n }\n\n /**\n * The face the figure is placed by. In V3 it is part of the room declaration, so choosing one declares the room\n * again from the parts — the only way V3 declares it — with the new face. The hint says so.\n */\n private renderPlacement(facts: V3FigureFacts) {\n const current = facts.placement ?? 'floor'\n return html`\n <div field>\n <label>${i18next.t('figure.label.figure-placement')}</label>\n <select\n ?disabled=${!facts.parts.length}\n .value=${live(current)}\n @change=${(event: Event) => this.declare((event.target as HTMLSelectElement).value as Placement)}\n >\n ${PLACEMENTS.map(\n one => html`<option value=${one} ?selected=${one === current}>${i18next.t('figure.label.placement-' + one)}</option>`\n )}\n </select>\n <small>${i18next.t(facts.parts.length ? 'figure.text.placement-redeclares' : 'figure.text.fit-needs-parts')}</small>\n </div>\n `\n }\n\n private renderSpace(facts: V3FigureFacts) {\n return html`\n <div field>\n <label>${i18next.t('figure.label.declared-space')}</label>\n <div row>\n <span>${i18next.t(facts.declared ? 'figure.text.declared-space-declared' : 'figure.text.declared-space-none')}</span>\n <button act ?disabled=${!facts.parts.length} @click=${() => this.declare(facts.placement ?? 'floor')}>\n <md-icon>fit_screen</md-icon>${i18next.t('figure.button.fit-base-to-parts')}\n </button>\n </div>\n </div>\n `\n }\n\n private renderMounting(facts: V3FigureFacts) {\n const mounting = facts.mounting\n const part = [this.pickedPart, mounting?.part, facts.parts[0]].find(one => one && facts.parts.includes(one))\n const face: V3Face = this.pickedFace ?? mounting?.face ?? 'bottom'\n return html`\n <div field>\n <label>${i18next.t('figure.label.mounting-face')}</label>\n <div row>\n <select ?disabled=${!facts.declared} @change=${(e: Event) => (this.pickedPart = (e.target as HTMLSelectElement).value)}>\n ${facts.parts.map(one => html`<option value=${one} ?selected=${one === part}>${one}</option>`)}\n </select>\n <select ?disabled=${!facts.declared} @change=${(e: Event) => (this.pickedFace = (e.target as HTMLSelectElement).value as V3Face)}>\n ${FACES.map(one => html`<option value=${one} ?selected=${one === face}>${i18next.t('figure.label.face-' + one)}</option>`)}\n </select>\n </div>\n <div row>\n <button act ?disabled=${!facts.declared || !part} @click=${() => this.command({ kind: 'set-mounting-face', part: part!, face })}>\n ${i18next.t('figure.button.set-mounting-face')}\n </button>\n <button act ?disabled=${!mounting} @click=${() => this.command({ kind: 'clear-mounting-face' })}>\n ${i18next.t('figure.button.clear-mounting-face')}\n </button>\n </div>\n <small>\n ${facts.declared\n ? mounting\n ? `${mounting.part} · ${i18next.t('figure.label.face-' + mounting.face)}`\n : i18next.t('figure.label.mounting-face-none')\n : i18next.t('figure.text.mounting-face-needs-space')}\n </small>\n </div>\n `\n }\n\n private renderCatalog() {\n const saved = !!this.figureId\n return html`\n <div field>\n <label>${i18next.t('figure.label.category')}</label>\n <input\n .value=${this.figureCategory ?? ''}\n ?disabled=${!saved}\n placeholder=${i18next.t('figure.text.category-placeholder')}\n @change=${(e: Event) => this.catalog({ category: (e.target as HTMLInputElement).value.trim() })}\n />\n </div>\n <div field>\n <label>${i18next.t('figure.label.tags')}</label>\n <input\n .value=${(this.figureTags ?? []).join(', ')}\n ?disabled=${!saved}\n placeholder=${i18next.t('figure.text.tags-placeholder')}\n @change=${(e: Event) => this.catalog({ tags: tagsOf((e.target as HTMLInputElement).value) })}\n />\n ${saved ? nothing : html`<small>${i18next.t('figure.text.catalog-needs-a-save')}</small>`}\n </div>\n `\n }\n\n private declare(placement: Placement) {\n if (!this.source) return\n this.command(occupancyFromParts(this.source, placement))\n }\n\n private command(action: V3AuthoringAction) {\n this.dispatchEvent(new CustomEvent('v3-command', { detail: { action }, bubbles: true, composed: true }))\n }\n\n private catalog(patch: { category?: string; tags?: string[] }) {\n this.dispatchEvent(new CustomEvent('v3-catalog', { detail: patch, bubbles: true, composed: true }))\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"figure-v3-properties.js","sourceRoot":"","sources":["../../client/modeller/figure-v3-properties.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\n\nimport { css, html, LitElement, nothing } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\nimport { live } from 'lit/directives/live.js'\n\nimport { i18next, localize } from '@operato/i18n'\nimport { V3_FACES } from '@hatiolab/figure-model'\nimport type { V3AuthoringAction, V3Face } from '@hatiolab/figure-model'\n\nimport { occupancyFromParts } from './v3-occupancy.js'\nimport { tagsOf, v3FigureFacts, type Placement, type V3FigureFacts } from './v3-figure-facts.js'\nimport { SidePanelStyles } from './side-panel-styles.js'\nimport { ScrollbarStyles } from '@operato/styles'\n\nconst PLACEMENTS: Placement[] = ['floor', 'ceiling', 'center', 'wall']\nconst FACES = Object.keys(V3_FACES) as V3Face[]\n\n/**\n * 속성 — the V3 modeller's figure properties (parity row ②).\n *\n * The V2 inspector opens with what the figure is: its name, the face it is placed by, the room it takes up, where\n * the catalogue files it. The V3 page had only the type. The same rows, in the same order, drawn the way the V2\n * inspector draws them.\n *\n * Two kinds of change leave this tab, and it does neither itself:\n * - `v3-command` carries an authoring command (rename, declare the room, the mounting face). The page runs it\n * through the session, so it is one step of the same undo history as every other edit, and is saved with it.\n * - `v3-catalog` carries a change to the row (category, tags). Those are not in the asset; the page writes them\n * to the saved row at once, so they need a saved figure.\n *\n * Not here yet, because V3 has no place for them: editing the declared room by hand (V3 declares it from the\n * parts, as graph expressions), and the detail level (the converter records it as lost). Both wait on a V3 ruling.\n */\n@customElement('figure-v3-properties')\nexport class FigureV3Properties extends localize(i18next)(LitElement) {\n static styles = [ScrollbarStyles, SidePanelStyles, css`\n :host {\n display: block;\n /* It stands inside the properties tab's panel, which gives the margin; a second one indented it past the type. */\n padding: 0;\n overflow: auto;\n }\n div[row] {\n display: flex;\n gap: 6px;\n align-items: center;\n }\n div[row] > select {\n flex: 1;\n }\n div[row] > button[act] {\n flex: 1;\n width: auto;\n margin-top: 0;\n }\n div[row] > span {\n flex: 1;\n }\n `]\n\n /** The source being edited. */\n @property({ type: String }) source?: string\n /** The saved row, if any. Category and tags live on it. */\n @property({ type: String }) figureId?: string\n @property({ type: String }) figureCategory?: string\n @property({ type: Array }) figureTags?: string[]\n /** The part and face picked for the mounting face, before 「이 면으로」 is pressed. */\n @state() private pickedPart?: string\n @state() private pickedFace?: V3Face\n\n private facts(): V3FigureFacts | undefined {\n try {\n return this.source ? v3FigureFacts(this.source) : undefined\n } catch {\n /* A source the format refuses has no facts to show; the verdict tab says why. */\n return undefined\n }\n }\n\n render() {\n const facts = this.facts()\n if (!facts) return nothing\n /* The figure's type is the page's (it lives on the row); it is slotted into the first section, as V2 had it. */\n return html`<section>\n <h3>${i18next.t('figure.label.group-figure')}</h3>\n <slot name=\"type\"></slot>\n ${this.renderName(facts)} ${this.renderPlacement(facts)}\n </section>\n <section>\n <h3>${i18next.t('figure.label.group-standing')}</h3>\n ${this.renderSpace(facts)} ${this.renderMounting(facts)}\n </section>\n <section>\n <h3>${i18next.t('figure.label.group-catalog')}</h3>\n ${this.renderCatalog()}\n </section>`\n }\n\n private renderName(facts: V3FigureFacts) {\n return html`\n <div field>\n <label>${i18next.t('figure.label.name')}</label>\n <input\n .value=${facts.name}\n placeholder=${i18next.t('figure.text.figure-display-name')}\n @change=${(event: Event) => {\n const input = event.target as HTMLInputElement\n if (!input.value.trim()) {\n /* The format refuses a blank name; put the old one back rather than send a command that fails. */\n input.value = facts.name\n return\n }\n if (input.value.trim() !== facts.name) this.command({ kind: 'rename', name: input.value })\n }}\n />\n </div>\n `\n }\n\n /**\n * The face the figure is placed by. In V3 it is part of the room declaration, so choosing one declares the room\n * again from the parts — the only way V3 declares it — with the new face. The hint says so.\n */\n private renderPlacement(facts: V3FigureFacts) {\n const current = facts.placement ?? 'floor'\n return html`\n <div field>\n <label>${i18next.t('figure.label.figure-placement')}</label>\n <select\n ?disabled=${!facts.parts.length}\n .value=${live(current)}\n @change=${(event: Event) => {\n const select = event.target as HTMLSelectElement\n this.declare(select.value as Placement)\n /* Refused, the figure is placed as it was, and the list says so; taken, the figure drawn again says the new one. */\n select.value = current\n }}\n >\n ${PLACEMENTS.map(\n one => html`<option value=${one} ?selected=${one === current}>${i18next.t('figure.label.placement-' + one)}</option>`\n )}\n </select>\n <small>${i18next.t(facts.parts.length ? 'figure.text.placement-redeclares' : 'figure.text.fit-needs-parts')}</small>\n </div>\n `\n }\n\n private renderSpace(facts: V3FigureFacts) {\n return html`\n <div field>\n <label>${i18next.t('figure.label.declared-space')}</label>\n <div row>\n <span>${i18next.t(facts.declared ? 'figure.text.declared-space-declared' : 'figure.text.declared-space-none')}</span>\n <button act ?disabled=${!facts.parts.length} @click=${() => this.declare(facts.placement ?? 'floor')}>\n <md-icon>fit_screen</md-icon>${i18next.t('figure.button.fit-base-to-parts')}\n </button>\n </div>\n </div>\n `\n }\n\n private renderMounting(facts: V3FigureFacts) {\n const mounting = facts.mounting\n const part = [this.pickedPart, mounting?.part, facts.parts[0]].find(one => one && facts.parts.includes(one))\n const face: V3Face = this.pickedFace ?? mounting?.face ?? 'bottom'\n return html`\n <div field>\n <label>${i18next.t('figure.label.mounting-face')}</label>\n <div row>\n <select ?disabled=${!facts.declared} @change=${(e: Event) => (this.pickedPart = (e.target as HTMLSelectElement).value)}>\n ${facts.parts.map(one => html`<option value=${one} ?selected=${one === part}>${one}</option>`)}\n </select>\n <select ?disabled=${!facts.declared} @change=${(e: Event) => (this.pickedFace = (e.target as HTMLSelectElement).value as V3Face)}>\n ${FACES.map(one => html`<option value=${one} ?selected=${one === face}>${i18next.t('figure.label.face-' + one)}</option>`)}\n </select>\n </div>\n <div row>\n <button act ?disabled=${!facts.declared || !part} @click=${() => this.command({ kind: 'set-mounting-face', part: part!, face })}>\n ${i18next.t('figure.button.set-mounting-face')}\n </button>\n <button act ?disabled=${!mounting} @click=${() => this.command({ kind: 'clear-mounting-face' })}>\n ${i18next.t('figure.button.clear-mounting-face')}\n </button>\n </div>\n <small>\n ${facts.declared\n ? mounting\n ? `${mounting.part} · ${i18next.t('figure.label.face-' + mounting.face)}`\n : i18next.t('figure.label.mounting-face-none')\n : i18next.t('figure.text.mounting-face-needs-space')}\n </small>\n </div>\n `\n }\n\n private renderCatalog() {\n const saved = !!this.figureId\n return html`\n <div field>\n <label>${i18next.t('figure.label.category')}</label>\n <input\n .value=${this.figureCategory ?? ''}\n ?disabled=${!saved}\n placeholder=${i18next.t('figure.text.category-placeholder')}\n @change=${(e: Event) => this.catalog({ category: (e.target as HTMLInputElement).value.trim() })}\n />\n </div>\n <div field>\n <label>${i18next.t('figure.label.tags')}</label>\n <input\n .value=${(this.figureTags ?? []).join(', ')}\n ?disabled=${!saved}\n placeholder=${i18next.t('figure.text.tags-placeholder')}\n @change=${(e: Event) => this.catalog({ tags: tagsOf((e.target as HTMLInputElement).value) })}\n />\n ${saved ? nothing : html`<small>${i18next.t('figure.text.catalog-needs-a-save')}</small>`}\n </div>\n `\n }\n\n private declare(placement: Placement) {\n if (!this.source) return\n this.command(occupancyFromParts(this.source, placement))\n }\n\n private command(action: V3AuthoringAction) {\n this.dispatchEvent(new CustomEvent('v3-command', { detail: { action }, bubbles: true, composed: true }))\n }\n\n private catalog(patch: { category?: string; tags?: string[] }) {\n this.dispatchEvent(new CustomEvent('v3-catalog', { detail: patch, bubbles: true, composed: true }))\n }\n}\n"]}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { V3ProposalStep } from '@hatiolab/figure-model';
|
|
2
|
+
/** What the card carries, as `proposeFigureCandidate` returned it. */
|
|
3
|
+
export interface FigureProposalCard {
|
|
4
|
+
steps?: V3ProposalStep[];
|
|
5
|
+
create?: boolean;
|
|
6
|
+
figureId?: string;
|
|
7
|
+
context?: string;
|
|
8
|
+
revision?: number;
|
|
9
|
+
}
|
|
10
|
+
/** The `figure-v3-apply` event's detail. The page that takes it sets `applied`, and `error` when refused. */
|
|
11
|
+
export interface FigureApplyDetail {
|
|
12
|
+
steps: V3ProposalStep[];
|
|
13
|
+
create: boolean;
|
|
14
|
+
context?: string;
|
|
15
|
+
revision?: number;
|
|
16
|
+
applied?: boolean;
|
|
17
|
+
error?: string;
|
|
18
|
+
code?: string;
|
|
19
|
+
}
|
|
20
|
+
export type Say = (key: string, params?: Record<string, string>) => string;
|
|
21
|
+
export interface FigureProposalTarget {
|
|
22
|
+
/** Offers the proposal to a modeller on screen. It comes back untouched when none took it. */
|
|
23
|
+
offer(detail: FigureApplyDetail): FigureApplyDetail;
|
|
24
|
+
/** Opens a new figure in the modeller and applies the steps there; resolves once the page has, or has not. */
|
|
25
|
+
openNew(steps: V3ProposalStep[]): Promise<{
|
|
26
|
+
applied: boolean;
|
|
27
|
+
error?: string;
|
|
28
|
+
code?: string;
|
|
29
|
+
}>;
|
|
30
|
+
}
|
|
31
|
+
export interface FigureProposalOutcome {
|
|
32
|
+
verdict: 'accepted' | 'refused' | 'unavailable';
|
|
33
|
+
errorCode?: string;
|
|
34
|
+
applied?: number;
|
|
35
|
+
note?: string;
|
|
36
|
+
}
|
|
37
|
+
export declare function runFigureProposal(card: FigureProposalCard, target: FigureProposalTarget, say: Say): Promise<FigureProposalOutcome>;
|
|
38
|
+
export declare function pendNewFigure(steps: V3ProposalStep[], ms?: number): Promise<{
|
|
39
|
+
applied: boolean;
|
|
40
|
+
error?: string;
|
|
41
|
+
code?: string;
|
|
42
|
+
}>;
|
|
43
|
+
/** For the page, on a new figure it has just started: the steps waiting for it, and where to say how it went. */
|
|
44
|
+
export declare function takePendingNewFigure(): {
|
|
45
|
+
steps: V3ProposalStep[];
|
|
46
|
+
settle: (result: {
|
|
47
|
+
applied: boolean;
|
|
48
|
+
error?: string;
|
|
49
|
+
code?: string;
|
|
50
|
+
}) => void;
|
|
51
|
+
} | undefined;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/** The workspace refuses a request made on another version; said as what happened, not as the kernel's code. */
|
|
2
|
+
const STALE = new Set(['REVISION_CONFLICT', 'CONTEXT_CONFLICT']);
|
|
3
|
+
function refused(code, error, say) {
|
|
4
|
+
const note = code && STALE.has(code)
|
|
5
|
+
? say('figure.text.proposal-stale')
|
|
6
|
+
: code === 'NEW_FIGURE_UNSAVED'
|
|
7
|
+
? (error ?? '')
|
|
8
|
+
: say('figure.text.proposal-refused', { reason: error ?? '' });
|
|
9
|
+
return { verdict: 'refused', errorCode: code ?? 'refused', note };
|
|
10
|
+
}
|
|
11
|
+
export async function runFigureProposal(card, target, say) {
|
|
12
|
+
const steps = card?.steps;
|
|
13
|
+
if (!Array.isArray(steps) || !steps.length)
|
|
14
|
+
return { verdict: 'unavailable', errorCode: 'no-steps' };
|
|
15
|
+
const create = card.create === true;
|
|
16
|
+
const accepted = () => ({
|
|
17
|
+
verdict: 'accepted',
|
|
18
|
+
applied: steps.length,
|
|
19
|
+
note: say('figure.text.proposal-applied', { n: String(steps.length) })
|
|
20
|
+
});
|
|
21
|
+
const detail = target.offer({ steps, create, context: card.context, revision: card.revision });
|
|
22
|
+
if (detail.applied)
|
|
23
|
+
return accepted();
|
|
24
|
+
if (detail.applied === false)
|
|
25
|
+
return refused(detail.code, detail.error, say);
|
|
26
|
+
/* A revision belongs to the figure the model saw; with that modeller gone there is nowhere true to apply it. */
|
|
27
|
+
if (!create)
|
|
28
|
+
return { verdict: 'unavailable', errorCode: 'modeller-not-open', note: say('figure.text.proposal-needs-modeller') };
|
|
29
|
+
const opened = await target.openNew(steps);
|
|
30
|
+
if (opened.applied)
|
|
31
|
+
return accepted();
|
|
32
|
+
return opened.code === 'not-opened'
|
|
33
|
+
? { verdict: 'unavailable', errorCode: 'not-opened', note: say('figure.text.proposal-not-opened') }
|
|
34
|
+
: refused(opened.code, opened.error, say);
|
|
35
|
+
}
|
|
36
|
+
/*
|
|
37
|
+
A new figure's proposal handed to the modeller page across a navigation: the handler leaves it here and opens the
|
|
38
|
+
modeller on a new figure; the page takes it once it has started that figure. Left untaken, it lapses -- a later new
|
|
39
|
+
figure must not receive steps nobody is waiting for.
|
|
40
|
+
*/
|
|
41
|
+
let pending;
|
|
42
|
+
export function pendNewFigure(steps, ms = 10_000) {
|
|
43
|
+
pending?.settle({ applied: false, code: 'not-opened' });
|
|
44
|
+
return new Promise(resolve => {
|
|
45
|
+
const mine = {
|
|
46
|
+
steps,
|
|
47
|
+
settle: (result) => {
|
|
48
|
+
clearTimeout(timer);
|
|
49
|
+
if (pending === mine)
|
|
50
|
+
pending = undefined;
|
|
51
|
+
resolve(result);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
const timer = setTimeout(() => mine.settle({ applied: false, code: 'not-opened' }), ms);
|
|
55
|
+
pending = mine;
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
/** For the page, on a new figure it has just started: the steps waiting for it, and where to say how it went. */
|
|
59
|
+
export function takePendingNewFigure() {
|
|
60
|
+
const taken = pending;
|
|
61
|
+
pending = undefined;
|
|
62
|
+
return taken;
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=v3-ai-proposal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"v3-ai-proposal.js","sourceRoot":"","sources":["../../client/modeller/v3-ai-proposal.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * An AI proposal reaching the modeller (ADR-0094). The dock's card carries the modeller's own commands; running it\n * applies them as one step of the modeller's history, undone whole, on the version the model saw. Nothing half made\n * is applied, and every outcome is said: applied, refused with the reason, or not possible here.\n *\n * Kept free of Lit, i18n and the shell so node tests read it directly; the handler and the page pass in what they own.\n */\nimport type { V3ProposalStep } from '@hatiolab/figure-model'\n\n/** What the card carries, as `proposeFigureCandidate` returned it. */\nexport interface FigureProposalCard {\n steps?: V3ProposalStep[]\n create?: boolean\n figureId?: string\n context?: string\n revision?: number\n}\n\n/** The `figure-v3-apply` event's detail. The page that takes it sets `applied`, and `error` when refused. */\nexport interface FigureApplyDetail {\n steps: V3ProposalStep[]\n create: boolean\n context?: string\n revision?: number\n applied?: boolean\n error?: string\n code?: string\n}\n\nexport type Say = (key: string, params?: Record<string, string>) => string\n\nexport interface FigureProposalTarget {\n /** Offers the proposal to a modeller on screen. It comes back untouched when none took it. */\n offer(detail: FigureApplyDetail): FigureApplyDetail\n /** Opens a new figure in the modeller and applies the steps there; resolves once the page has, or has not. */\n openNew(steps: V3ProposalStep[]): Promise<{ applied: boolean; error?: string; code?: string }>\n}\n\nexport interface FigureProposalOutcome {\n verdict: 'accepted' | 'refused' | 'unavailable'\n errorCode?: string\n applied?: number\n note?: string\n}\n\n/** The workspace refuses a request made on another version; said as what happened, not as the kernel's code. */\nconst STALE = new Set(['REVISION_CONFLICT', 'CONTEXT_CONFLICT'])\n\nfunction refused(code: string | undefined, error: string | undefined, say: Say): FigureProposalOutcome {\n const note =\n code && STALE.has(code)\n ? say('figure.text.proposal-stale')\n : code === 'NEW_FIGURE_UNSAVED'\n ? (error ?? '')\n : say('figure.text.proposal-refused', { reason: error ?? '' })\n return { verdict: 'refused', errorCode: code ?? 'refused', note }\n}\n\nexport async function runFigureProposal(card: FigureProposalCard, target: FigureProposalTarget, say: Say): Promise<FigureProposalOutcome> {\n const steps = card?.steps\n if (!Array.isArray(steps) || !steps.length) return { verdict: 'unavailable', errorCode: 'no-steps' }\n const create = card.create === true\n const accepted = (): FigureProposalOutcome => ({\n verdict: 'accepted',\n applied: steps.length,\n note: say('figure.text.proposal-applied', { n: String(steps.length) })\n })\n\n const detail = target.offer({ steps, create, context: card.context, revision: card.revision })\n if (detail.applied) return accepted()\n if (detail.applied === false) return refused(detail.code, detail.error, say)\n\n /* A revision belongs to the figure the model saw; with that modeller gone there is nowhere true to apply it. */\n if (!create) return { verdict: 'unavailable', errorCode: 'modeller-not-open', note: say('figure.text.proposal-needs-modeller') }\n\n const opened = await target.openNew(steps)\n if (opened.applied) return accepted()\n return opened.code === 'not-opened'\n ? { verdict: 'unavailable', errorCode: 'not-opened', note: say('figure.text.proposal-not-opened') }\n : refused(opened.code, opened.error, say)\n}\n\n/*\n A new figure's proposal handed to the modeller page across a navigation: the handler leaves it here and opens the\n modeller on a new figure; the page takes it once it has started that figure. Left untaken, it lapses -- a later new\n figure must not receive steps nobody is waiting for.\n*/\nlet pending: { steps: V3ProposalStep[]; settle: (result: { applied: boolean; error?: string; code?: string }) => void } | undefined\n\nexport function pendNewFigure(steps: V3ProposalStep[], ms = 10_000): Promise<{ applied: boolean; error?: string; code?: string }> {\n pending?.settle({ applied: false, code: 'not-opened' })\n return new Promise(resolve => {\n const mine = {\n steps,\n settle: (result: { applied: boolean; error?: string; code?: string }) => {\n clearTimeout(timer)\n if (pending === mine) pending = undefined\n resolve(result)\n }\n }\n const timer = setTimeout(() => mine.settle({ applied: false, code: 'not-opened' }), ms)\n pending = mine\n })\n}\n\n/** For the page, on a new figure it has just started: the steps waiting for it, and where to say how it went. */\nexport function takePendingNewFigure(): { steps: V3ProposalStep[]; settle: (result: { applied: boolean; error?: string; code?: string }) => void } | undefined {\n const taken = pending\n pending = undefined\n return taken\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type V3InputChange, type V3PrimitiveCreation, type V3PartEdit, type V3AuthoringAction } from '@hatiolab/figure-model';
|
|
1
|
+
import { type V3InputChange, type V3PrimitiveCreation, type V3PartEdit, type V3AuthoringAction, type V3ProposalStep } from '@hatiolab/figure-model';
|
|
2
2
|
export interface V3ProductRecord {
|
|
3
3
|
id: string;
|
|
4
4
|
source: string;
|
|
@@ -207,6 +207,40 @@ export declare function createV3ProductSession(store: V3ProductStore): {
|
|
|
207
207
|
bindings: import("@hatiolab/figure-model").V3EvaluatedBinding[];
|
|
208
208
|
palette: Record<string, string>;
|
|
209
209
|
};
|
|
210
|
+
/**
|
|
211
|
+
* An AI proposal (ADR-0094): the modeller's commands, as one step of the history, on the version the model saw.
|
|
212
|
+
* Never on the current one instead -- a person's edit in between makes the proposal stale, and it is refused.
|
|
213
|
+
*/
|
|
214
|
+
applyProposal(steps: readonly V3ProposalStep[], expectedRevision: number, context: string): {
|
|
215
|
+
context: null;
|
|
216
|
+
name: null;
|
|
217
|
+
fields: never[];
|
|
218
|
+
geometry: never[];
|
|
219
|
+
bindings: never[];
|
|
220
|
+
dirty: boolean;
|
|
221
|
+
revision: null;
|
|
222
|
+
canUndo?: undefined;
|
|
223
|
+
canRedo?: undefined;
|
|
224
|
+
palette?: undefined;
|
|
225
|
+
} | {
|
|
226
|
+
context: string;
|
|
227
|
+
name: string;
|
|
228
|
+
revision: number;
|
|
229
|
+
dirty: boolean;
|
|
230
|
+
canUndo: boolean;
|
|
231
|
+
canRedo: boolean;
|
|
232
|
+
fields: {
|
|
233
|
+
value: number;
|
|
234
|
+
id: string;
|
|
235
|
+
unit: import("@hatiolab/figure-model").V3Unit;
|
|
236
|
+
min: number;
|
|
237
|
+
max: number;
|
|
238
|
+
role?: "design" | "state";
|
|
239
|
+
}[];
|
|
240
|
+
geometry: import("@hatiolab/figure-model").V3AssetGeometry[];
|
|
241
|
+
bindings: import("@hatiolab/figure-model").V3EvaluatedBinding[];
|
|
242
|
+
palette: Record<string, string>;
|
|
243
|
+
};
|
|
210
244
|
add(part: V3PrimitiveCreation): {
|
|
211
245
|
context: null;
|
|
212
246
|
name: null;
|
|
@@ -87,6 +87,14 @@ export function createV3ProductSession(store) {
|
|
|
87
87
|
changes
|
|
88
88
|
});
|
|
89
89
|
},
|
|
90
|
+
/**
|
|
91
|
+
* An AI proposal (ADR-0094): the modeller's commands, as one step of the history, on the version the model saw.
|
|
92
|
+
* Never on the current one instead -- a person's edit in between makes the proposal stale, and it is refused.
|
|
93
|
+
*/
|
|
94
|
+
applyProposal(steps, expectedRevision, context) {
|
|
95
|
+
writable();
|
|
96
|
+
return workspace.applyProposal({ origin: 'ai', context, expectedRevision, steps });
|
|
97
|
+
},
|
|
90
98
|
add(part) {
|
|
91
99
|
return workspace.addPrimitive({ ...writable(), part });
|
|
92
100
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"v3-product-session.js","sourceRoot":"","sources":["../../client/modeller/v3-product-session.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n createV3Asset,\n createV3EditorWorkspace,\n serializeV3Asset,\n parseV3Asset,\n type V3InputChange,\n type V3PrimitiveCreation,\n type V3PartEdit,\n type V3AuthoringAction\n} from '@hatiolab/figure-model'\n\nexport interface V3ProductRecord {\n id: string\n source: string\n}\n\n/** Persistence is injected; neither the model engine nor the renderer owns the product ID. */\nexport interface V3ProductStore {\n load(id: string): Promise<V3ProductRecord>\n save(request: {\n id?: string\n source: string\n expectedSource?: string\n creationId?: string\n /** The card's picture. Absent when the browser could not draw one; the source is saved either way. */\n thumbnail?: string\n /** The type name a new figure is created under (the first save asks for it). Not sent for an existing one. */\n type?: string\n }): Promise<V3ProductRecord>\n}\n\n/** Product lifecycle boundary. A late response can never change another editor's asset or ID. */\nexport function createV3ProductSession(store: V3ProductStore) {\n const workspace = createV3EditorWorkspace()\n let epoch = 0,\n id: string | undefined,\n baseline: string | undefined\n let loading = false,\n saving = false,\n error: string | undefined\n let creationId: string | undefined\n function read() {\n return { ...workspace.read(), id, loading, saving, error }\n }\n function writable() {\n const view = workspace.read()\n if (loading || error || !view.context || view.revision === null) throw Error('먼저 모델을 정상적으로 열어 주세요.')\n // Serializing writes also makes create retries safe: no second create while the first is pending.\n if (saving) throw Error('저장이 끝난 뒤 편집해 주세요.')\n return { context: view.context, expectedRevision: view.revision, origin: 'ui' as const }\n }\n function clear() {\n workspace.close({ discardDesignChanges: true })\n id = undefined\n baseline = undefined\n error = undefined\n saving = false\n creationId = undefined\n }\n function canLeave(discard: boolean) {\n if (saving) throw Error('저장이 끝난 뒤 모델을 전환해 주세요.')\n if (workspace.read().dirty && !discard) throw Error('저장하지 않은 변경이 있습니다.')\n }\n return {\n read,\n startNew({ discard = false } = {}) {\n canLeave(discard)\n ++epoch\n clear()\n loading = false\n workspace.open(serializeV3Asset(createV3Asset('새 Figure')))\n creationId = crypto.randomUUID()\n return read()\n },\n async open(nextId: string, { discard = false } = {}) {\n canLeave(discard)\n const requestEpoch = ++epoch\n clear()\n loading = true\n try {\n const record = await store.load(nextId)\n if (requestEpoch !== epoch) return read()\n if (record.id !== nextId) throw Error('요청한 Figure와 응답의 대상이 다릅니다.')\n workspace.open(record.source)\n id = nextId\n baseline = record.source\n } catch (cause) {\n if (requestEpoch === epoch) error = cause instanceof Error ? cause.message : String(cause)\n } finally {\n if (requestEpoch === epoch) loading = false\n }\n return read()\n },\n close({ discard = false } = {}) {\n canLeave(discard)\n ++epoch\n clear()\n loading = false\n return read()\n },\n apply(changes: V3InputChange[], origin: 'ui' | 'ai' = 'ui', expectedRevision?: number, context?: string) {\n const control = writable()\n // AI must supply the context AND revision captured when it started. Never substitute current values.\n if (origin === 'ai' && (context === undefined || expectedRevision === undefined))\n throw Error('AI 편집에는 요청 당시의 모델 문맥과 리비전이 필요합니다.')\n return workspace.apply({\n ...control,\n origin,\n context: context ?? control.context,\n expectedRevision: expectedRevision ?? control.expectedRevision,\n changes\n })\n },\n add(part: V3PrimitiveCreation) {\n return workspace.addPrimitive({ ...writable(), part })\n },\n edit(edit: V3PartEdit) {\n return workspace.editPart({ ...writable(), edit })\n },\n /** One authoring command — what a person means, not a graph edit — as one step of the same history. */\n command(action: V3AuthoringAction) {\n return workspace.command({ ...writable(), action })\n },\n undo() {\n return workspace.undo(writable())\n },\n redo() {\n return workspace.redo(writable())\n },\n source() {\n const { context } = writable()\n return workspace.export({ context }).text\n },\n /**\n * Saves the model, and the card's picture when the caller managed to take one.\n *\n * The picture is the caller's because taking it needs a canvas, and this session is the place that knows\n * nothing about drawing. A save with no picture is a save: a tab in the background cannot draw, and the\n * source matters more than the card (see `v3-thumbnail.ts`).\n */\n async save({ thumbnail, type }: { thumbnail?: string; type?: string } = {}) {\n const control = writable(),\n requestEpoch = epoch\n const exported = workspace.export(control)\n saving = true\n try {\n const record = await store.save({ id, source: exported.text, expectedSource: baseline, creationId, thumbnail, ...(id || !type ? {} : { type }) })\n if (requestEpoch !== epoch) return read()\n if (!record.id || (id !== undefined && record.id !== id)) throw Error('저장 응답의 Figure 대상이 다릅니다.')\n if (serializeV3Asset(parseV3Asset(record.source)) !== exported.text)\n throw Error('저장 응답이 보낸 모델과 다릅니다. 저장 상태를 다시 확인해 주세요.')\n workspace.acknowledgeSaved(exported)\n id = record.id\n baseline = record.source\n } finally {\n if (requestEpoch === epoch) saving = false\n }\n return read()\n }\n }\n}\nexport type V3ProductSession = ReturnType<typeof createV3ProductSession>\n"]}
|
|
1
|
+
{"version":3,"file":"v3-product-session.js","sourceRoot":"","sources":["../../client/modeller/v3-product-session.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n createV3Asset,\n createV3EditorWorkspace,\n serializeV3Asset,\n parseV3Asset,\n type V3InputChange,\n type V3PrimitiveCreation,\n type V3PartEdit,\n type V3AuthoringAction,\n type V3ProposalStep\n} from '@hatiolab/figure-model'\n\nexport interface V3ProductRecord {\n id: string\n source: string\n}\n\n/** Persistence is injected; neither the model engine nor the renderer owns the product ID. */\nexport interface V3ProductStore {\n load(id: string): Promise<V3ProductRecord>\n save(request: {\n id?: string\n source: string\n expectedSource?: string\n creationId?: string\n /** The card's picture. Absent when the browser could not draw one; the source is saved either way. */\n thumbnail?: string\n /** The type name a new figure is created under (the first save asks for it). Not sent for an existing one. */\n type?: string\n }): Promise<V3ProductRecord>\n}\n\n/** Product lifecycle boundary. A late response can never change another editor's asset or ID. */\nexport function createV3ProductSession(store: V3ProductStore) {\n const workspace = createV3EditorWorkspace()\n let epoch = 0,\n id: string | undefined,\n baseline: string | undefined\n let loading = false,\n saving = false,\n error: string | undefined\n let creationId: string | undefined\n function read() {\n return { ...workspace.read(), id, loading, saving, error }\n }\n function writable() {\n const view = workspace.read()\n if (loading || error || !view.context || view.revision === null) throw Error('먼저 모델을 정상적으로 열어 주세요.')\n // Serializing writes also makes create retries safe: no second create while the first is pending.\n if (saving) throw Error('저장이 끝난 뒤 편집해 주세요.')\n return { context: view.context, expectedRevision: view.revision, origin: 'ui' as const }\n }\n function clear() {\n workspace.close({ discardDesignChanges: true })\n id = undefined\n baseline = undefined\n error = undefined\n saving = false\n creationId = undefined\n }\n function canLeave(discard: boolean) {\n if (saving) throw Error('저장이 끝난 뒤 모델을 전환해 주세요.')\n if (workspace.read().dirty && !discard) throw Error('저장하지 않은 변경이 있습니다.')\n }\n return {\n read,\n startNew({ discard = false } = {}) {\n canLeave(discard)\n ++epoch\n clear()\n loading = false\n workspace.open(serializeV3Asset(createV3Asset('새 Figure')))\n creationId = crypto.randomUUID()\n return read()\n },\n async open(nextId: string, { discard = false } = {}) {\n canLeave(discard)\n const requestEpoch = ++epoch\n clear()\n loading = true\n try {\n const record = await store.load(nextId)\n if (requestEpoch !== epoch) return read()\n if (record.id !== nextId) throw Error('요청한 Figure와 응답의 대상이 다릅니다.')\n workspace.open(record.source)\n id = nextId\n baseline = record.source\n } catch (cause) {\n if (requestEpoch === epoch) error = cause instanceof Error ? cause.message : String(cause)\n } finally {\n if (requestEpoch === epoch) loading = false\n }\n return read()\n },\n close({ discard = false } = {}) {\n canLeave(discard)\n ++epoch\n clear()\n loading = false\n return read()\n },\n apply(changes: V3InputChange[], origin: 'ui' | 'ai' = 'ui', expectedRevision?: number, context?: string) {\n const control = writable()\n // AI must supply the context AND revision captured when it started. Never substitute current values.\n if (origin === 'ai' && (context === undefined || expectedRevision === undefined))\n throw Error('AI 편집에는 요청 당시의 모델 문맥과 리비전이 필요합니다.')\n return workspace.apply({\n ...control,\n origin,\n context: context ?? control.context,\n expectedRevision: expectedRevision ?? control.expectedRevision,\n changes\n })\n },\n /**\n * An AI proposal (ADR-0094): the modeller's commands, as one step of the history, on the version the model saw.\n * Never on the current one instead -- a person's edit in between makes the proposal stale, and it is refused.\n */\n applyProposal(steps: readonly V3ProposalStep[], expectedRevision: number, context: string) {\n writable()\n return workspace.applyProposal({ origin: 'ai', context, expectedRevision, steps })\n },\n add(part: V3PrimitiveCreation) {\n return workspace.addPrimitive({ ...writable(), part })\n },\n edit(edit: V3PartEdit) {\n return workspace.editPart({ ...writable(), edit })\n },\n /** One authoring command — what a person means, not a graph edit — as one step of the same history. */\n command(action: V3AuthoringAction) {\n return workspace.command({ ...writable(), action })\n },\n undo() {\n return workspace.undo(writable())\n },\n redo() {\n return workspace.redo(writable())\n },\n source() {\n const { context } = writable()\n return workspace.export({ context }).text\n },\n /**\n * Saves the model, and the card's picture when the caller managed to take one.\n *\n * The picture is the caller's because taking it needs a canvas, and this session is the place that knows\n * nothing about drawing. A save with no picture is a save: a tab in the background cannot draw, and the\n * source matters more than the card (see `v3-thumbnail.ts`).\n */\n async save({ thumbnail, type }: { thumbnail?: string; type?: string } = {}) {\n const control = writable(),\n requestEpoch = epoch\n const exported = workspace.export(control)\n saving = true\n try {\n const record = await store.save({ id, source: exported.text, expectedSource: baseline, creationId, thumbnail, ...(id || !type ? {} : { type }) })\n if (requestEpoch !== epoch) return read()\n if (!record.id || (id !== undefined && record.id !== id)) throw Error('저장 응답의 Figure 대상이 다릅니다.')\n if (serializeV3Asset(parseV3Asset(record.source)) !== exported.text)\n throw Error('저장 응답이 보낸 모델과 다릅니다. 저장 상태를 다시 확인해 주세요.')\n workspace.acknowledgeSaved(exported)\n id = record.id\n baseline = record.source\n } finally {\n if (requestEpoch === epoch) saving = false\n }\n return read()\n }\n }\n}\nexport type V3ProductSession = ReturnType<typeof createV3ProductSession>\n"]}
|
|
@@ -72,7 +72,11 @@ export declare class FigureV3ModellerPage extends PageView {
|
|
|
72
72
|
private released;
|
|
73
73
|
/** 과거 판으로 되돌렸으면 서버의 원본이 바뀐 것이다 — 다시 연다. 패널은 저장 안 된 것이 있으면 되돌리기를 막는다. */
|
|
74
74
|
private reverted;
|
|
75
|
-
/**
|
|
75
|
+
/**
|
|
76
|
+
* The dock's way in (`figure-assistant.ts`): input changes, or a proposal's commands as one step (ADR-0094). A
|
|
77
|
+
* revision is applied on the version the model saw; a new figure's proposal only on a new figure not yet touched. A
|
|
78
|
+
* page on a saved figure leaves a new figure's proposal untaken, and the handler opens a new figure for it.
|
|
79
|
+
*/
|
|
76
80
|
private applyAI;
|
|
77
81
|
connectedCallback(): void;
|
|
78
82
|
disconnectedCallback(): void;
|
|
@@ -80,6 +84,8 @@ export declare class FigureV3ModellerPage extends PageView {
|
|
|
80
84
|
pageUpdated(changes: Record<string, unknown>, lifecycle: {
|
|
81
85
|
resourceId?: string;
|
|
82
86
|
}): Promise<void>;
|
|
87
|
+
/** A new figure the dock opened for a proposal: its steps, applied as the new figure's first step. */
|
|
88
|
+
private takeNewFigureProposal;
|
|
83
89
|
/** The first save's question, over the names this domain already uses (V2 asked the same). */
|
|
84
90
|
private askFirstSave;
|
|
85
91
|
get context(): {
|
|
@@ -18,6 +18,8 @@ import { openPopup } from '@operato/layout';
|
|
|
18
18
|
import '../modeller/figure-v3-first-save.js';
|
|
19
19
|
import { activePalette } from '@hatiolab/things-scene';
|
|
20
20
|
import { clearAssistantSurface, notifyAssistantSurface, provideAssistantSurface } from '@things-factory/ai-assistant/dist-client/index.js';
|
|
21
|
+
import { placementRefusalOf } from '../modeller/v3-refusals.js';
|
|
22
|
+
import { takePendingNewFigure } from '../modeller/v3-ai-proposal.js';
|
|
21
23
|
/**
|
|
22
24
|
* The V3 product page has exactly one authoritative workspace for UI and AI input transactions.
|
|
23
25
|
*
|
|
@@ -77,6 +79,9 @@ let FigureV3ModellerPage = class FigureV3ModellerPage extends PageView {
|
|
|
77
79
|
editorMode: 'existing',
|
|
78
80
|
figureId: id,
|
|
79
81
|
draftSource: JSON.parse(this.session.source()),
|
|
82
|
+
/* The version the model sees; a proposal is applied on it, or refused if the figure moved on (ADR-0094). */
|
|
83
|
+
context: this.session.read().context,
|
|
84
|
+
revision: this.session.read().revision,
|
|
80
85
|
palette: Object.keys(activePalette())
|
|
81
86
|
})
|
|
82
87
|
});
|
|
@@ -183,18 +188,38 @@ let FigureV3ModellerPage = class FigureV3ModellerPage extends PageView {
|
|
|
183
188
|
this.changed();
|
|
184
189
|
this.readRow(id);
|
|
185
190
|
};
|
|
186
|
-
/**
|
|
191
|
+
/**
|
|
192
|
+
* The dock's way in (`figure-assistant.ts`): input changes, or a proposal's commands as one step (ADR-0094). A
|
|
193
|
+
* revision is applied on the version the model saw; a new figure's proposal only on a new figure not yet touched. A
|
|
194
|
+
* page on a saved figure leaves a new figure's proposal untaken, and the handler opens a new figure for it.
|
|
195
|
+
*/
|
|
187
196
|
this.applyAI = (event) => {
|
|
188
197
|
const detail = event.detail;
|
|
189
198
|
if (!this.active || !detail)
|
|
190
199
|
return;
|
|
200
|
+
const view = this.session.read();
|
|
201
|
+
if (detail.create && this.routeId)
|
|
202
|
+
return;
|
|
203
|
+
if (detail.create && view.dirty) {
|
|
204
|
+
/* A new figure already being made is the person's; a proposal does not go on top of it or replace it. */
|
|
205
|
+
detail.applied = false;
|
|
206
|
+
detail.code = 'NEW_FIGURE_UNSAVED';
|
|
207
|
+
detail.error = i18next.t('figure.text.proposal-new-figure-unsaved');
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
191
210
|
try {
|
|
192
|
-
|
|
211
|
+
if (detail.create)
|
|
212
|
+
this.session.applyProposal(detail.steps, view.revision, view.context);
|
|
213
|
+
else if (detail.steps)
|
|
214
|
+
this.session.applyProposal(detail.steps, detail.revision, detail.context);
|
|
215
|
+
else
|
|
216
|
+
this.session.apply(detail.changes, 'ai', detail.revision, detail.context);
|
|
193
217
|
detail.applied = true;
|
|
194
218
|
this.changed();
|
|
195
219
|
}
|
|
196
220
|
catch (error) {
|
|
197
221
|
detail.applied = false;
|
|
222
|
+
detail.code = error?.code;
|
|
198
223
|
detail.error = error instanceof Error ? error.message : String(error);
|
|
199
224
|
}
|
|
200
225
|
};
|
|
@@ -310,9 +335,10 @@ let FigureV3ModellerPage = class FigureV3ModellerPage extends PageView {
|
|
|
310
335
|
this.commandError =
|
|
311
336
|
error?.code === 'OCCUPANCY_NO_PARTS'
|
|
312
337
|
? i18next.t('figure.text.fit-needs-parts')
|
|
313
|
-
: error
|
|
314
|
-
|
|
315
|
-
|
|
338
|
+
: placementRefusalOf(error, (key, params) => i18next.t(key, params)) ??
|
|
339
|
+
(error instanceof Error
|
|
340
|
+
? error.message
|
|
341
|
+
: String(error));
|
|
316
342
|
}
|
|
317
343
|
}
|
|
318
344
|
connectedCallback() {
|
|
@@ -371,6 +397,7 @@ let FigureV3ModellerPage = class FigureV3ModellerPage extends PageView {
|
|
|
371
397
|
this.draft = undefined;
|
|
372
398
|
this.row = { version: 0 };
|
|
373
399
|
this.typeError = '';
|
|
400
|
+
this.takeNewFigureProposal();
|
|
374
401
|
}
|
|
375
402
|
this.routeId = id;
|
|
376
403
|
this.error = '';
|
|
@@ -380,6 +407,20 @@ let FigureV3ModellerPage = class FigureV3ModellerPage extends PageView {
|
|
|
380
407
|
this.error = error instanceof Error ? error.message : String(error);
|
|
381
408
|
}
|
|
382
409
|
}
|
|
410
|
+
/** A new figure the dock opened for a proposal: its steps, applied as the new figure's first step. */
|
|
411
|
+
takeNewFigureProposal() {
|
|
412
|
+
const waiting = takePendingNewFigure();
|
|
413
|
+
if (!waiting)
|
|
414
|
+
return;
|
|
415
|
+
const view = this.session.read();
|
|
416
|
+
try {
|
|
417
|
+
this.session.applyProposal(waiting.steps, view.revision, view.context);
|
|
418
|
+
waiting.settle({ applied: true });
|
|
419
|
+
}
|
|
420
|
+
catch (error) {
|
|
421
|
+
waiting.settle({ applied: false, code: error?.code, error: error instanceof Error ? error.message : String(error) });
|
|
422
|
+
}
|
|
423
|
+
}
|
|
383
424
|
get context() {
|
|
384
425
|
return {
|
|
385
426
|
title: this.session.read().name ?? 'Figure',
|