@things-factory/figure-ui 10.1.10 → 10.1.14

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 (32) hide show
  1. package/client/modeller/figure-ai-target.ts +11 -0
  2. package/client/modeller/figure-ask.ts +8 -5
  3. package/client/modeller/figure-canvas.ts +10 -4
  4. package/client/modeller/new-figure-entry.ts +4 -0
  5. package/client/pages/figure-modeller-page.ts +241 -120
  6. package/dist-client/modeller/figure-ai-target.d.ts +8 -0
  7. package/dist-client/modeller/figure-ai-target.js +12 -0
  8. package/dist-client/modeller/figure-ai-target.js.map +1 -0
  9. package/dist-client/modeller/figure-ask.d.ts +1 -0
  10. package/dist-client/modeller/figure-ask.js +12 -5
  11. package/dist-client/modeller/figure-ask.js.map +1 -1
  12. package/dist-client/modeller/figure-canvas.d.ts +1 -0
  13. package/dist-client/modeller/figure-canvas.js +10 -4
  14. package/dist-client/modeller/figure-canvas.js.map +1 -1
  15. package/dist-client/modeller/new-figure-entry.d.ts +2 -0
  16. package/dist-client/modeller/new-figure-entry.js +5 -0
  17. package/dist-client/modeller/new-figure-entry.js.map +1 -0
  18. package/dist-client/pages/figure-modeller-page.d.ts +14 -0
  19. package/dist-client/pages/figure-modeller-page.js +240 -120
  20. package/dist-client/pages/figure-modeller-page.js.map +1 -1
  21. package/dist-client/route.d.ts +1 -1
  22. package/dist-client/tsconfig.tsbuildinfo +1 -1
  23. package/package.json +3 -3
  24. package/test/ai-proposal-contract.test.ts +24 -3
  25. package/test/ai-target.test.ts +25 -0
  26. package/test/modeller-undo-redo.test.ts +33 -0
  27. package/test/new-figure-entry.test.ts +15 -0
  28. package/translations/en.json +2 -0
  29. package/translations/ja.json +2 -0
  30. package/translations/ko.json +2 -0
  31. package/translations/ms.json +2 -0
  32. package/translations/zh.json +2 -0
@@ -0,0 +1,11 @@
1
+ /** Explicit AI entry survives lazy creation of the app-level dock. */
2
+ let pending: { figureId?: string; source?: unknown } | undefined
3
+ export function requestFigureAI(target: { figureId?: string; source?: unknown }) {
4
+ pending = structuredClone(target)
5
+ window.dispatchEvent(new CustomEvent('figure-ai-target'))
6
+ }
7
+ export function takeFigureAITarget() {
8
+ const target = pending
9
+ pending = undefined
10
+ return target
11
+ }
@@ -58,6 +58,7 @@ export class FigureAsk extends localize(i18next)(LitElement) {
58
58
 
59
59
  form {
60
60
  display: flex;
61
+ flex-wrap: wrap;
61
62
  flex: 1;
62
63
  min-width: 0;
63
64
  align-items: center;
@@ -177,6 +178,7 @@ export class FigureAsk extends localize(i18next)(LitElement) {
177
178
  @property({ type: String }) type = ''
178
179
  /** 이 모델러 세션에서 사람이 명시적으로 남긴 이전 후보 반응. */
179
180
  @property({ type: Array }) feedback: ProposalFeedback[] = []
181
+ @property({ type: Boolean }) imageOnly = false
180
182
 
181
183
  @state() private asking = false
182
184
  @state() private failure = ''
@@ -186,7 +188,7 @@ export class FigureAsk extends localize(i18next)(LitElement) {
186
188
  const revising = !!this.source
187
189
 
188
190
  return html`
189
- <form @submit=${(e: Event) => this.submit(e)}>
191
+ <form title="이미지의 형태와 비율을 참고한 후보를 만듭니다. 저장은 하지 않습니다." @submit=${(e: Event) => this.submit(e)}>
190
192
  <md-icon lead>auto_awesome</md-icon>
191
193
  <span mode>${i18next.t(revising ? 'figure.text.ask-to-revise' : 'figure.text.ask-to-create')}</span>
192
194
  <input
@@ -201,7 +203,7 @@ export class FigureAsk extends localize(i18next)(LitElement) {
201
203
  : i18next.t('figure.button.attach-a-picture')}
202
204
  <input type="file" accept="image/*" @change=${(e: Event) => this.pick(e)} />
203
205
  </label>
204
- <button go ?disabled=${this.asking}>
206
+ <button go ?disabled=${this.asking || (this.imageOnly && !this.picture)}>
205
207
  ${this.asking ? i18next.t('figure.text.asking') : i18next.t('figure.button.ask')}
206
208
  </button>
207
209
  </form>
@@ -227,17 +229,18 @@ export class FigureAsk extends localize(i18next)(LitElement) {
227
229
 
228
230
  const input = this.renderRoot.querySelector('input[ask]') as HTMLInputElement
229
231
  const prompt = input?.value?.trim()
230
- if (!prompt || this.asking) {
232
+ if (!prompt || this.asking || (this.imageOnly && !this.picture)) {
231
233
  return
232
234
  }
233
235
 
234
236
  this.asking = true
235
237
  this.failure = ''
238
+ const baseSource = this.source ? JSON.stringify(this.source) : undefined
236
239
 
237
240
  try {
238
241
  const proposal = await proposeFigure({
239
242
  prompt,
240
- base: this.source ? JSON.stringify(this.source) : undefined,
243
+ base: baseSource,
241
244
  type: this.source ? undefined : this.type,
242
245
  // 그리는 쪽이 풀 수 있는 토큰만 보낸다. 서버는 이 목록 밖의 색을 거절한다.
243
246
  palette: Object.keys(activePalette() as Record<string, string>),
@@ -248,7 +251,7 @@ export class FigureAsk extends localize(i18next)(LitElement) {
248
251
  })
249
252
 
250
253
  this.dispatchEvent(
251
- new CustomEvent('proposed', { detail: { proposal }, bubbles: true, composed: true })
254
+ new CustomEvent('proposed', { detail: { proposal, baseSource }, bubbles: true, composed: true })
252
255
  )
253
256
  input.value = ''
254
257
  this.picture = undefined
@@ -70,6 +70,7 @@ interface SceneBoard {
70
70
  selected?: SceneComponent[]
71
71
  app?: unknown
72
72
  addComponent?: (component: unknown) => void
73
+ removeComponent?: (component: unknown) => void
73
74
  on?: (event: string, handler: (...args: unknown[]) => void) => void
74
75
  /** 판 위의 루트. 선택 알림이 여기서 난다. */
75
76
  root?: {
@@ -82,7 +83,8 @@ interface SceneComponent {
82
83
  state: Record<string, unknown>
83
84
  set: (props: Record<string, unknown> | string, value?: unknown) => void
84
85
  on?: (event: string, handler: (...args: unknown[]) => void) => void
85
- remove?: () => void
86
+ removeSelf?: (completely?: boolean) => void
87
+ dispose?: () => void
86
88
  }
87
89
 
88
90
  /**
@@ -181,6 +183,7 @@ export class FigureCanvas extends localize(i18next)(LitElement) {
181
183
  * 이미 씬 모델이므로, 그러면 값을 하나 고칠 때마다 왕복 변환이 한 번씩 일어난다.
182
184
  * 무손실이긴 하지만 하는 일이 없다 — 보드가 곧 보드이고 부품이 곧 컴포넌트다.
183
185
  */
186
+ @property({ type: String }) figureId?: string
184
187
  @property({ type: Object }) board?: BoardModel
185
188
  /** 보기 설정. 자산이 아니라 작업대다 — 저장되지 않는다(`figure-view.ts`). */
186
189
  @property({ type: Object }) view: ViewSettings = DEFAULT_VIEW
@@ -272,7 +275,7 @@ export class FigureCanvas extends localize(i18next)(LitElement) {
272
275
  willUpdate(changed: Map<string, unknown>) {
273
276
  if (!this.hasUpdated) return
274
277
 
275
- if (changed.has('board') || changed.has('parts')) this.standUp()
278
+ if (changed.has('board') || changed.has('parts') || changed.has('figureId')) this.standUp()
276
279
  if (changed.has('view')) this.applyView()
277
280
  if (changed.has('picked')) this.showPicked()
278
281
  }
@@ -314,7 +317,7 @@ export class FigureCanvas extends localize(i18next)(LitElement) {
314
317
  return
315
318
  }
316
319
 
317
- const signature = `${board.width}x${board.height}x${board.depth}`
320
+ const signature = `${this.figureId ?? ''}:${board.width}x${board.height}x${board.depth}`
318
321
  if (this.scene && this.standing === signature) {
319
322
  this.syncParts()
320
323
  return
@@ -455,7 +458,10 @@ export class FigureCanvas extends localize(i18next)(LitElement) {
455
458
 
456
459
  // 없어진 것을 제거한다
457
460
  for (const [name, component] of standing) {
458
- if (!wanted.has(name)) component.remove?.()
461
+ if (!wanted.has(name)) {
462
+ root.removeComponent?.(component)
463
+ component.dispose?.()
464
+ }
459
465
  }
460
466
 
461
467
  for (const part of this.parts) {
@@ -0,0 +1,4 @@
1
+ /** A route entry starts a new document; ordinary updates must preserve edits. */
2
+ export function shouldStartNewFigure(changes: Record<string, unknown>, hasFigure: boolean, savedId?: string): boolean {
3
+ return !hasFigure || !!savedId || changes.active === true || Object.prototype.hasOwnProperty.call(changes, 'resourceId')
4
+ }