@things-factory/figure-ui 10.1.15 → 10.1.17

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.
Files changed (78) hide show
  1. package/client/modeller/figure-animations.ts +12 -12
  2. package/client/modeller/figure-canvas.ts +32 -48
  3. package/client/modeller/figure-capabilities.ts +10 -10
  4. package/client/modeller/figure-inspector.ts +39 -28
  5. package/client/modeller/figure-parts.ts +1 -1
  6. package/client/modeller/figure-preview.ts +122 -25
  7. package/client/modeller/figure-report.ts +24 -6
  8. package/client/modeller/figure-settings.ts +45 -2
  9. package/client/modeller/figure-side.ts +6 -6
  10. package/client/modeller/figure-source.ts +84 -38
  11. package/client/modeller/figure-thumbnail.ts +5 -5
  12. package/client/modeller/figure-view.ts +53 -0
  13. package/client/modeller/part-edits.ts +36 -36
  14. package/client/modeller/proposal.ts +25 -4
  15. package/client/pages/figure-modeller-page.ts +142 -98
  16. package/dist-client/modeller/figure-animations.d.ts +4 -4
  17. package/dist-client/modeller/figure-animations.js +10 -10
  18. package/dist-client/modeller/figure-animations.js.map +1 -1
  19. package/dist-client/modeller/figure-canvas.d.ts +7 -7
  20. package/dist-client/modeller/figure-canvas.js +27 -40
  21. package/dist-client/modeller/figure-canvas.js.map +1 -1
  22. package/dist-client/modeller/figure-capabilities.d.ts +2 -2
  23. package/dist-client/modeller/figure-capabilities.js +9 -9
  24. package/dist-client/modeller/figure-capabilities.js.map +1 -1
  25. package/dist-client/modeller/figure-inspector.d.ts +16 -5
  26. package/dist-client/modeller/figure-inspector.js +38 -27
  27. package/dist-client/modeller/figure-inspector.js.map +1 -1
  28. package/dist-client/modeller/figure-parts.d.ts +1 -1
  29. package/dist-client/modeller/figure-parts.js +1 -1
  30. package/dist-client/modeller/figure-parts.js.map +1 -1
  31. package/dist-client/modeller/figure-preview.d.ts +39 -0
  32. package/dist-client/modeller/figure-preview.js +112 -23
  33. package/dist-client/modeller/figure-preview.js.map +1 -1
  34. package/dist-client/modeller/figure-report.d.ts +9 -0
  35. package/dist-client/modeller/figure-report.js +23 -5
  36. package/dist-client/modeller/figure-report.js.map +1 -1
  37. package/dist-client/modeller/figure-settings.js +42 -2
  38. package/dist-client/modeller/figure-settings.js.map +1 -1
  39. package/dist-client/modeller/figure-side.d.ts +2 -2
  40. package/dist-client/modeller/figure-side.js +5 -5
  41. package/dist-client/modeller/figure-side.js.map +1 -1
  42. package/dist-client/modeller/figure-source.d.ts +38 -23
  43. package/dist-client/modeller/figure-source.js +40 -21
  44. package/dist-client/modeller/figure-source.js.map +1 -1
  45. package/dist-client/modeller/figure-thumbnail.d.ts +2 -2
  46. package/dist-client/modeller/figure-thumbnail.js +3 -3
  47. package/dist-client/modeller/figure-thumbnail.js.map +1 -1
  48. package/dist-client/modeller/figure-view.d.ts +45 -0
  49. package/dist-client/modeller/figure-view.js +27 -0
  50. package/dist-client/modeller/figure-view.js.map +1 -1
  51. package/dist-client/modeller/part-edits.d.ts +16 -16
  52. package/dist-client/modeller/part-edits.js +35 -35
  53. package/dist-client/modeller/part-edits.js.map +1 -1
  54. package/dist-client/modeller/proposal.d.ts +1 -1
  55. package/dist-client/modeller/proposal.js +15 -3
  56. package/dist-client/modeller/proposal.js.map +1 -1
  57. package/dist-client/pages/figure-modeller-page.d.ts +44 -9
  58. package/dist-client/pages/figure-modeller-page.js +134 -92
  59. package/dist-client/pages/figure-modeller-page.js.map +1 -1
  60. package/dist-client/route.d.ts +1 -1
  61. package/dist-client/tsconfig.tsbuildinfo +1 -1
  62. package/dist-server/tsconfig.tsbuildinfo +1 -1
  63. package/package.json +4 -4
  64. package/test/ai-proposal-contract.test.ts +393 -12
  65. package/test/expect.ts +55 -0
  66. package/test/figure-source.test.ts +562 -0
  67. package/test/i18n-prefix-guard.test.ts +1 -1
  68. package/{client/modeller → test}/part-edits.test.ts +50 -45
  69. package/translations/en.json +31 -28
  70. package/translations/ja.json +16 -21
  71. package/translations/ko.json +41 -38
  72. package/translations/ms.json +16 -21
  73. package/translations/zh.json +16 -21
  74. package/client/modeller/figure-ask.ts +0 -265
  75. package/client/modeller/figure-source.test.ts +0 -352
  76. package/dist-client/modeller/figure-ask.d.ts +0 -40
  77. package/dist-client/modeller/figure-ask.js +0 -282
  78. package/dist-client/modeller/figure-ask.js.map +0 -1
@@ -1,282 +0,0 @@
1
- import { __decorate, __metadata } from "tslib";
2
- import '@material/web/icon/icon.js';
3
- import { css, html, LitElement, nothing } from 'lit';
4
- import { customElement, property, state } from 'lit/decorators.js';
5
- import { i18next, localize } from '@operato/i18n';
6
- import { activePalette } from '@hatiolab/things-scene';
7
- import { proposeFigure } from '../graphql/index.js';
8
- /**
9
- * 말로 시키는 줄.
10
- *
11
- * ## 무엇을 시키는지가 화면에 보여야 한다
12
- *
13
- * 정본이 있으면 **고치는 것**이고, 없으면 **만드는 것**이다. 같은 칸으로 두 가지를
14
- * 하면 저작자가 「이게 지금 것을 덮어쓰나」를 매번 걱정한다. 그래서 무엇을 할지
15
- * 칸 옆에 적어 둔다.
16
- *
17
- * ## 후보를 여기서 받지 않는다
18
- *
19
- * 이 컴포넌트는 시키고 결과를 **알릴 뿐**이다. 받을지 버릴지는 저작면이 정한다 —
20
- * 정본은 한 벌이고 그 한 벌을 가진 자리가 정해야 한다.
21
- *
22
- * ## 팔레트를 함께 보낸다
23
- *
24
- * 서버는 팔레트를 모른다. 그리는 쪽이 어떤 토큰을 풀 수 있는지 아는 유일한 자리다.
25
- */
26
- let FigureAsk = class FigureAsk extends localize(i18next)(LitElement) {
27
- constructor() {
28
- super(...arguments);
29
- /** 새로 만들 때 쓸 타입 이름. */
30
- this.type = '';
31
- /** 이 모델러 세션에서 사람이 명시적으로 남긴 이전 후보 반응. */
32
- this.feedback = [];
33
- this.imageOnly = false;
34
- this.asking = false;
35
- this.failure = '';
36
- }
37
- static { this.styles = css `
38
- /*
39
- 이 부품은 **자기 줄을 갖지 않는다.**
40
-
41
- 전에는 테두리와 여백으로 스스로 한 줄이 되었다. 저작면의 머리줄 바로 아래에 놓이니
42
- 화면 위쪽이 두 줄이 되었고, 위 줄은 이름을 페이지 제목과 겹쳐 적고 있었다. 캔버스가
43
- 주인공인 화면에서 그 두 줄은 비싸다.
44
-
45
- 이제 머리줄 안에 들어가 그 줄의 남는 폭을 쓴다. 테두리·여백은 담는 줄이 갖는다.
46
- */
47
- :host {
48
- display: flex;
49
- flex: 1;
50
- min-width: 0;
51
- font: var(--label-font, inherit);
52
- /*
53
- 이 줄의 글자 크기를 여기서 정한다.
54
-
55
- 안 적어 두면 브라우저 기본 크기(16px)를 물려받는다. 아래 패널들은 전부 12px
56
- 언저리라 위 두 줄만 커 보이고, 단추가 화면 전체와 따로 논다.
57
-
58
- form 요소(input · button)는 글꼴을 물려받지 않으므로 각자 적어야 한다.
59
- */
60
- font-size: 0.76rem;
61
- }
62
-
63
- form {
64
- display: flex;
65
- flex-wrap: wrap;
66
- flex: 1;
67
- min-width: 0;
68
- align-items: center;
69
- gap: var(--spacing-medium, 8px);
70
- }
71
-
72
- md-icon[lead] {
73
- --md-icon-size: 18px;
74
- flex: none;
75
- color: var(--md-sys-color-primary);
76
- }
77
-
78
- input[ask] {
79
- flex: 1;
80
- min-width: 0;
81
- padding: 6px 10px;
82
- font: var(--input-field-font, inherit);
83
- font-size: 0.78rem;
84
- color: var(--md-sys-color-on-surface);
85
- background-color: var(--md-sys-color-surface-container-lowest);
86
- border: 1px solid var(--md-sys-color-outline-variant);
87
- border-radius: 6px;
88
- }
89
- input[ask]:focus {
90
- outline: 2px solid var(--md-sys-color-primary);
91
- outline-offset: -1px;
92
- }
93
- input[ask][disabled] {
94
- opacity: 0.5;
95
- }
96
-
97
- /* 무엇을 시키는 것인지 — 만드나 고치나 */
98
- span[mode] {
99
- flex: none;
100
- font-size: 0.72rem;
101
- color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));
102
- }
103
-
104
- /* 그림 붙이기. 붙인 것이 있으면 이름을 보여 준다 */
105
- label[picture] {
106
- display: flex;
107
- align-items: center;
108
- gap: 4px;
109
- flex: none;
110
- padding: 5px 9px;
111
- font-size: 0.74rem;
112
- color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));
113
- border: 1px solid var(--md-sys-color-outline-variant);
114
- border-radius: 6px;
115
- cursor: pointer;
116
- }
117
- label[picture]:hover {
118
- background-color: var(--md-sys-color-surface-container);
119
- }
120
- label[picture][has] {
121
- color: var(--md-sys-color-on-primary-container);
122
- background-color: var(--md-sys-color-primary-container);
123
- border-color: transparent;
124
- }
125
- label[picture] md-icon {
126
- --md-icon-size: 16px;
127
- }
128
- label[picture] input {
129
- display: none;
130
- }
131
- span[filename] {
132
- max-width: 8em;
133
- overflow: hidden;
134
- text-overflow: ellipsis;
135
- white-space: nowrap;
136
- }
137
-
138
- /*
139
- 채운 파랑 단추를 저장과 나눠 갖지 않는다.
140
-
141
- 머리줄의 저장이 이 화면의 주 동작이다. 바로 아래 줄에 같은 파랑 단추가 하나 더
142
- 있으면 어느 쪽이 주인지 눈이 못 고른다. 이쪽은 톤 단추로 한 단계 내린다.
143
- */
144
- button[go] {
145
- flex: none;
146
- padding: 6px 14px;
147
- font: var(--label-font, inherit);
148
- font-size: 0.76rem;
149
- font-weight: 600;
150
- color: var(--md-sys-color-on-primary-container);
151
- background-color: var(--md-sys-color-primary-container);
152
- border: none;
153
- border-radius: 6px;
154
- cursor: pointer;
155
- }
156
- button[go]:hover:not([disabled]) {
157
- background-color: color-mix(in srgb, var(--md-sys-color-primary) 24%, var(--md-sys-color-primary-container));
158
- }
159
- button[go][disabled] {
160
- opacity: 0.4;
161
- cursor: default;
162
- }
163
-
164
- /* 못 만들었을 때. 사유를 삼키지 않는다 — 말을 고쳐 다시 시킬 단서다 */
165
- div[failed] {
166
- display: flex;
167
- gap: 6px;
168
- padding: 0 var(--spacing-large, 12px) var(--spacing-medium, 8px) var(--spacing-large, 12px);
169
- font-size: 0.74rem;
170
- line-height: 1.5;
171
- color: var(--md-sys-color-error);
172
- }
173
- div[failed] md-icon {
174
- --md-icon-size: 16px;
175
- flex: none;
176
- }
177
- `; }
178
- render() {
179
- const revising = !!this.source;
180
- return html `
181
- <form title="이미지의 형태와 비율을 참고한 후보를 만듭니다. 저장은 하지 않습니다." @submit=${(e) => this.submit(e)}>
182
- <md-icon lead>auto_awesome</md-icon>
183
- <span mode>${i18next.t(revising ? 'figure.text.ask-to-revise' : 'figure.text.ask-to-create')}</span>
184
- <input
185
- ask
186
- ?disabled=${this.asking}
187
- placeholder=${i18next.t(revising ? 'figure.text.ask-revise-example' : 'figure.text.ask-create-example')}
188
- />
189
- <label picture ?has=${!!this.picture} title=${i18next.t('figure.text.picture-is-for-proportions')}>
190
- <md-icon>image</md-icon>
191
- ${this.picture
192
- ? html `<span filename>${this.picture.name}</span>`
193
- : i18next.t('figure.button.attach-a-picture')}
194
- <input type="file" accept="image/*" @change=${(e) => this.pick(e)} />
195
- </label>
196
- <button go ?disabled=${this.asking || (this.imageOnly && !this.picture)}>
197
- ${this.asking ? i18next.t('figure.text.asking') : i18next.t('figure.button.ask')}
198
- </button>
199
- </form>
200
-
201
- ${this.failure
202
- ? html `
203
- <div failed>
204
- <md-icon>error</md-icon>
205
- <span>${this.failure}</span>
206
- </div>
207
- `
208
- : nothing}
209
- `;
210
- }
211
- pick(e) {
212
- const input = e.target;
213
- this.picture = input.files?.[0];
214
- }
215
- async submit(e) {
216
- e.preventDefault();
217
- const input = this.renderRoot.querySelector('input[ask]');
218
- const prompt = input?.value?.trim();
219
- if (!prompt || this.asking || (this.imageOnly && !this.picture)) {
220
- return;
221
- }
222
- this.asking = true;
223
- this.failure = '';
224
- const baseSource = this.source ? JSON.stringify(this.source) : undefined;
225
- try {
226
- const proposal = await proposeFigure({
227
- prompt,
228
- base: baseSource,
229
- type: this.source ? undefined : this.type,
230
- // 그리는 쪽이 풀 수 있는 토큰만 보낸다. 서버는 이 목록 밖의 색을 거절한다.
231
- palette: Object.keys(activePalette()),
232
- // 구조적으로는 맞지만 품질 근거가 남으면 한 번 더 개선 후보를 시킨다.
233
- refine: true,
234
- feedback: this.feedback,
235
- image: this.picture
236
- });
237
- this.dispatchEvent(new CustomEvent('proposed', { detail: { proposal, baseSource }, bubbles: true, composed: true }));
238
- input.value = '';
239
- this.picture = undefined;
240
- }
241
- catch (err) {
242
- // 사유를 그대로 보여 준다. 「실패했습니다」로 뭉개면 말을 어떻게 고칠지 모른다.
243
- this.failure = err.message;
244
- }
245
- finally {
246
- this.asking = false;
247
- }
248
- }
249
- };
250
- __decorate([
251
- property({ type: Object }),
252
- __metadata("design:type", Object)
253
- ], FigureAsk.prototype, "source", void 0);
254
- __decorate([
255
- property({ type: String }),
256
- __metadata("design:type", Object)
257
- ], FigureAsk.prototype, "type", void 0);
258
- __decorate([
259
- property({ type: Array }),
260
- __metadata("design:type", Array)
261
- ], FigureAsk.prototype, "feedback", void 0);
262
- __decorate([
263
- property({ type: Boolean }),
264
- __metadata("design:type", Object)
265
- ], FigureAsk.prototype, "imageOnly", void 0);
266
- __decorate([
267
- state(),
268
- __metadata("design:type", Object)
269
- ], FigureAsk.prototype, "asking", void 0);
270
- __decorate([
271
- state(),
272
- __metadata("design:type", Object)
273
- ], FigureAsk.prototype, "failure", void 0);
274
- __decorate([
275
- state(),
276
- __metadata("design:type", File)
277
- ], FigureAsk.prototype, "picture", void 0);
278
- FigureAsk = __decorate([
279
- customElement('figure-ask')
280
- ], FigureAsk);
281
- export { FigureAsk };
282
- //# sourceMappingURL=figure-ask.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"figure-ask.js","sourceRoot":"","sources":["../../client/modeller/figure-ask.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'\n\nimport { i18next, localize } from '@operato/i18n'\nimport type { FigureSource } from '@hatiolab/figure-model'\nimport { activePalette } from '@hatiolab/things-scene'\n\nimport { proposeFigure } from '../graphql/index.js'\nimport type { FigureProposal, ProposalFeedback } from '../types.js'\n\n/**\n * 말로 시키는 줄.\n *\n * ## 무엇을 시키는지가 화면에 보여야 한다\n *\n * 정본이 있으면 **고치는 것**이고, 없으면 **만드는 것**이다. 같은 칸으로 두 가지를\n * 하면 저작자가 「이게 지금 것을 덮어쓰나」를 매번 걱정한다. 그래서 무엇을 할지\n * 칸 옆에 적어 둔다.\n *\n * ## 후보를 여기서 받지 않는다\n *\n * 이 컴포넌트는 시키고 결과를 **알릴 뿐**이다. 받을지 버릴지는 저작면이 정한다 —\n * 정본은 한 벌이고 그 한 벌을 가진 자리가 정해야 한다.\n *\n * ## 팔레트를 함께 보낸다\n *\n * 서버는 팔레트를 모른다. 그리는 쪽이 어떤 토큰을 풀 수 있는지 아는 유일한 자리다.\n */\n@customElement('figure-ask')\nexport class FigureAsk extends localize(i18next)(LitElement) {\n static styles = css`\n /*\n 이 부품은 **자기 줄을 갖지 않는다.**\n\n 전에는 테두리와 여백으로 스스로 한 줄이 되었다. 저작면의 머리줄 바로 아래에 놓이니\n 화면 위쪽이 두 줄이 되었고, 위 줄은 이름을 페이지 제목과 겹쳐 적고 있었다. 캔버스가\n 주인공인 화면에서 그 두 줄은 비싸다.\n\n 이제 머리줄 안에 들어가 그 줄의 남는 폭을 쓴다. 테두리·여백은 담는 줄이 갖는다.\n */\n :host {\n display: flex;\n flex: 1;\n min-width: 0;\n font: var(--label-font, inherit);\n /*\n 이 줄의 글자 크기를 여기서 정한다.\n\n 안 적어 두면 브라우저 기본 크기(16px)를 물려받는다. 아래 패널들은 전부 12px\n 언저리라 위 두 줄만 커 보이고, 단추가 화면 전체와 따로 논다.\n\n form 요소(input · button)는 글꼴을 물려받지 않으므로 각자 적어야 한다.\n */\n font-size: 0.76rem;\n }\n\n form {\n display: flex;\n flex-wrap: wrap;\n flex: 1;\n min-width: 0;\n align-items: center;\n gap: var(--spacing-medium, 8px);\n }\n\n md-icon[lead] {\n --md-icon-size: 18px;\n flex: none;\n color: var(--md-sys-color-primary);\n }\n\n input[ask] {\n flex: 1;\n min-width: 0;\n padding: 6px 10px;\n font: var(--input-field-font, inherit);\n font-size: 0.78rem;\n color: var(--md-sys-color-on-surface);\n background-color: var(--md-sys-color-surface-container-lowest);\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 6px;\n }\n input[ask]:focus {\n outline: 2px solid var(--md-sys-color-primary);\n outline-offset: -1px;\n }\n input[ask][disabled] {\n opacity: 0.5;\n }\n\n /* 무엇을 시키는 것인지 — 만드나 고치나 */\n span[mode] {\n flex: none;\n font-size: 0.72rem;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n\n /* 그림 붙이기. 붙인 것이 있으면 이름을 보여 준다 */\n label[picture] {\n display: flex;\n align-items: center;\n gap: 4px;\n flex: none;\n padding: 5px 9px;\n font-size: 0.74rem;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 6px;\n cursor: pointer;\n }\n label[picture]:hover {\n background-color: var(--md-sys-color-surface-container);\n }\n label[picture][has] {\n color: var(--md-sys-color-on-primary-container);\n background-color: var(--md-sys-color-primary-container);\n border-color: transparent;\n }\n label[picture] md-icon {\n --md-icon-size: 16px;\n }\n label[picture] input {\n display: none;\n }\n span[filename] {\n max-width: 8em;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n /*\n 채운 파랑 단추를 저장과 나눠 갖지 않는다.\n\n 머리줄의 저장이 이 화면의 주 동작이다. 바로 아래 줄에 같은 파랑 단추가 하나 더\n 있으면 어느 쪽이 주인지 눈이 못 고른다. 이쪽은 톤 단추로 한 단계 내린다.\n */\n button[go] {\n flex: none;\n padding: 6px 14px;\n font: var(--label-font, inherit);\n font-size: 0.76rem;\n font-weight: 600;\n color: var(--md-sys-color-on-primary-container);\n background-color: var(--md-sys-color-primary-container);\n border: none;\n border-radius: 6px;\n cursor: pointer;\n }\n button[go]:hover:not([disabled]) {\n background-color: color-mix(in srgb, var(--md-sys-color-primary) 24%, var(--md-sys-color-primary-container));\n }\n button[go][disabled] {\n opacity: 0.4;\n cursor: default;\n }\n\n /* 못 만들었을 때. 사유를 삼키지 않는다 — 말을 고쳐 다시 시킬 단서다 */\n div[failed] {\n display: flex;\n gap: 6px;\n padding: 0 var(--spacing-large, 12px) var(--spacing-medium, 8px) var(--spacing-large, 12px);\n font-size: 0.74rem;\n line-height: 1.5;\n color: var(--md-sys-color-error);\n }\n div[failed] md-icon {\n --md-icon-size: 16px;\n flex: none;\n }\n `\n\n /** 지금 정본. 있으면 고치는 것이고 없으면 만드는 것이다. */\n @property({ type: Object }) source?: FigureSource\n /** 새로 만들 때 쓸 타입 이름. */\n @property({ type: String }) type = ''\n /** 이 모델러 세션에서 사람이 명시적으로 남긴 이전 후보 반응. */\n @property({ type: Array }) feedback: ProposalFeedback[] = []\n @property({ type: Boolean }) imageOnly = false\n\n @state() private asking = false\n @state() private failure = ''\n @state() private picture?: File\n\n render() {\n const revising = !!this.source\n\n return html`\n <form title=\"이미지의 형태와 비율을 참고한 후보를 만듭니다. 저장은 하지 않습니다.\" @submit=${(e: Event) => this.submit(e)}>\n <md-icon lead>auto_awesome</md-icon>\n <span mode>${i18next.t(revising ? 'figure.text.ask-to-revise' : 'figure.text.ask-to-create')}</span>\n <input\n ask\n ?disabled=${this.asking}\n placeholder=${i18next.t(revising ? 'figure.text.ask-revise-example' : 'figure.text.ask-create-example')}\n />\n <label picture ?has=${!!this.picture} title=${i18next.t('figure.text.picture-is-for-proportions')}>\n <md-icon>image</md-icon>\n ${this.picture\n ? html`<span filename>${this.picture.name}</span>`\n : i18next.t('figure.button.attach-a-picture')}\n <input type=\"file\" accept=\"image/*\" @change=${(e: Event) => this.pick(e)} />\n </label>\n <button go ?disabled=${this.asking || (this.imageOnly && !this.picture)}>\n ${this.asking ? i18next.t('figure.text.asking') : i18next.t('figure.button.ask')}\n </button>\n </form>\n\n ${this.failure\n ? html`\n <div failed>\n <md-icon>error</md-icon>\n <span>${this.failure}</span>\n </div>\n `\n : nothing}\n `\n }\n\n private pick(e: Event) {\n const input = e.target as HTMLInputElement\n this.picture = input.files?.[0]\n }\n\n private async submit(e: Event) {\n e.preventDefault()\n\n const input = this.renderRoot.querySelector('input[ask]') as HTMLInputElement\n const prompt = input?.value?.trim()\n if (!prompt || this.asking || (this.imageOnly && !this.picture)) {\n return\n }\n\n this.asking = true\n this.failure = ''\n const baseSource = this.source ? JSON.stringify(this.source) : undefined\n\n try {\n const proposal = await proposeFigure({\n prompt,\n base: baseSource,\n type: this.source ? undefined : this.type,\n // 그리는 쪽이 풀 수 있는 토큰만 보낸다. 서버는 이 목록 밖의 색을 거절한다.\n palette: Object.keys(activePalette() as Record<string, string>),\n // 구조적으로는 맞지만 품질 근거가 남으면 한 번 더 개선 후보를 시킨다.\n refine: true,\n feedback: this.feedback,\n image: this.picture\n })\n\n this.dispatchEvent(\n new CustomEvent('proposed', { detail: { proposal, baseSource }, bubbles: true, composed: true })\n )\n input.value = ''\n this.picture = undefined\n } catch (err) {\n // 사유를 그대로 보여 준다. 「실패했습니다」로 뭉개면 말을 어떻게 고칠지 모른다.\n this.failure = (err as Error).message\n } finally {\n this.asking = false\n }\n }\n}\n"]}