@things-factory/ai-assistant 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.
@@ -570,6 +570,44 @@ export class OxAssistantChat extends LitElement {
570
570
  @state()
571
571
  private sentProposals = new Set<string>()
572
572
 
573
+ /** Proposal keys the host has reported staged. Only the host knows; see `acknowledgeProposalStaged`. */
574
+ @state()
575
+ private stagedProposals = new Set<string>()
576
+
577
+ /**
578
+ * The host reports that it put this candidate somewhere the person can look at it.
579
+ *
580
+ * Staging happens outside this component — a dock catches `assistant-proposal-preview`, hands
581
+ * the candidate to a viewer, and only that host knows whether it worked, or whether it has a
582
+ * viewer at all. So the card says nothing until the host calls this, and it then says it in the
583
+ * words the host registered (`chatDefaults.stagedNoticeKey`).
584
+ *
585
+ * Safe to call for a candidate the card never showed; the key simply never matches.
586
+ */
587
+ acknowledgeProposalStaged(proposal: any): void {
588
+ /*
589
+ Silence has two meanings and they are not the same defect. A host that never calls this is
590
+ telling us it does not know, and drawing nothing is right. A host that calls it without
591
+ registering `stagedNoticeKey` meant to say something and left out the words -- staying quiet
592
+ about that reads to whoever wired it as "the badge just doesn't appear here".
593
+
594
+ The screen still shows nothing either way. There is no sentence to show, and inventing one is
595
+ what this whole correction removed.
596
+ */
597
+ if (!chatDefaults.stagedNoticeKey && !OxAssistantChat.warnedMissingStagedNotice) {
598
+ OxAssistantChat.warnedMissingStagedNotice = true
599
+ console.warn(
600
+ '[assistant-chat] a host reported a proposal staged but registered no `stagedNoticeKey`, ' +
601
+ 'so the card can say nothing. Pass one to `registerChatDefaults` from the host.'
602
+ )
603
+ }
604
+
605
+ this.stagedProposals = new Set(this.stagedProposals).add(this.proposalKey(proposal))
606
+ }
607
+
608
+ /** Once per page: the miss is a wiring mistake, and repeating it per candidate buries it. */
609
+ private static warnedMissingStagedNotice = false
610
+
573
611
  /**
574
612
  * 이 대화가 길어져 앞부분이 프롬프트에서 접혔다는 **사실**(서버 보고). 주제 이탈을 판정한 것이
575
613
  * 아니다 — 판정하면 오탐이 생기고, 오탐이 생기면 사용자는 안내 자체를 무시한다.
@@ -3199,13 +3237,12 @@ export class OxAssistantChat extends LitElement {
3199
3237
  ${items.map(p => {
3200
3238
  const key = this.proposalKey(p)
3201
3239
  const sent = this.sentProposals.has(key)
3202
- const rawChoices: any[] = Array.isArray(p.choices) ? p.choices : (p.source ? [
3203
- { id: 'save', action: 'save', label: i18next.t('label.save-as-draft', { defaultValue: '초안 저장' }), icon: 'save', primary: true },
3204
- { id: 'repropose', action: 'prompt', label: '다른 콘셉트 재제안', icon: 'refresh', prompt: '현재 제안과 완전히 다른 스타일과 콘셉트로 새로 제안해줘' },
3205
- { id: 'slim', action: 'prompt', label: '비율 슬림 조정', icon: 'aspect_ratio', prompt: '전체적인 가로세로 비율을 좀 더 슬림하고 날렵하게 조정해줘' },
3206
- { id: 'palette', action: 'prompt', label: '색상 테마 변경', icon: 'palette', prompt: '다른 팔레트 색상 테마로 부품 색상을 변경해줘' },
3207
- { id: 'detail', action: 'prompt', label: '디테일 보강', icon: 'extension', prompt: '주요 디테일과 센서 부품을 좀 더 풍부하게 보강해줘' }
3208
- ] : [])
3240
+ /*
3241
+ Follow-up chips are the host's words, never this component's. The fallback that used
3242
+ to live here named a 3D modeller's operations in hardcoded Korean, and it reached the
3243
+ plant and twin chats too.
3244
+ */
3245
+ const rawChoices: any[] = Array.isArray(p.choices) ? p.choices : (chatDefaults.proposalChoices ?? [])
3209
3246
 
3210
3247
  return html`
3211
3248
  <div class="proposal ${sent ? 'sent' : ''}">
@@ -3214,20 +3251,32 @@ export class OxAssistantChat extends LitElement {
3214
3251
  <span class="plabel">${p.label || p.command}</span>
3215
3252
  ${p.reason ? html`<span class="preason">${p.reason}</span>` : nothing}
3216
3253
  </span>
3217
- ${!rawChoices.length ? html`
3218
- <button
3219
- class="run"
3220
- ?disabled=${sent}
3221
- @click=${() => this.executeProposal(p, key)}>
3222
- ${sent ? i18next.t('ai-assistant.text.proposal-sent', { defaultValue: '실행 요청됨' })
3223
- : i18next.t('ai-assistant.button.run-proposal', { defaultValue: '실행' })}
3224
- </button>
3225
- ` : html`
3226
- <span class="staged-badge" title="3D 뷰어에 모델이 표시되었습니다">
3254
+ ${/*
3255
+ The run button and the follow-up chips answer different questions — "is there
3256
+ something to apply" and "what else could I ask for" — and they used to share one
3257
+ ternary on `rawChoices.length`. A proposal that carried chips lost its button, so
3258
+ the one action the card exists for became unreachable while the model kept
3259
+ telling people to press it.
3260
+ */ ''}
3261
+ <button
3262
+ class="run"
3263
+ ?disabled=${sent}
3264
+ @click=${() => this.executeProposal(p, key)}>
3265
+ ${sent ? i18next.t('ai-assistant.text.proposal-sent', { defaultValue: '실행 요청됨' })
3266
+ : i18next.t('ai-assistant.button.run-proposal', { defaultValue: '실행' })}
3267
+ </button>
3268
+ ${/*
3269
+ Drawn only where the host has said it staged this candidate, in the host's own
3270
+ words. Not knowing is not a claim: with no word from the host, nothing is drawn.
3271
+ This badge used to read "there are chips" as "the model is in the 3D viewer" and
3272
+ say so, in a component that plant and twin also use.
3273
+ */ ''}
3274
+ ${this.stagedProposals.has(key) && chatDefaults.stagedNoticeKey ? html`
3275
+ <span class="staged-badge">
3227
3276
  <md-icon>check_circle</md-icon>
3228
- <span>뷰어 반영됨</span>
3277
+ <span>${i18next.t(chatDefaults.stagedNoticeKey)}</span>
3229
3278
  </span>
3230
- `}
3279
+ ` : nothing}
3231
3280
  </div>
3232
3281
  ${rawChoices.length ? html`
3233
3282
  <div class="proposal-choices" role="group" aria-label="대화 선택지">
@@ -28,6 +28,28 @@ export interface ChatDefaults {
28
28
  * 그 말을 한다.
29
29
  */
30
30
  footerNoticeKey?: string
31
+ /**
32
+ * Follow-up chips offered on a proposal that arrived without its own `choices`.
33
+ *
34
+ * These used to sit in the chat component as a literal array: "다른 콘셉트 재제안", "비율 슬림
35
+ * 조정", "색상 테마 변경", "디테일 보강". Those are a 3D modeller's words, written in Korean with
36
+ * no i18n, inside a component four apps share. A plant screen proposal offered to slim its
37
+ * aspect ratio.
38
+ *
39
+ * Same reason as `footerNoticeKey`: the host that knows what a follow-up means writes it.
40
+ * Leave it unset and a proposal without choices simply gets none, which is correct.
41
+ */
42
+ proposalChoices?: any[]
43
+ /**
44
+ * The **i18n key** for the line shown once the host reports a proposal staged. Unset means the
45
+ * badge is never drawn.
46
+ *
47
+ * The component cannot know this on its own. It used to read "선택지가 딸려 있다" as "the model is
48
+ * in the 3D viewer" and say so — in a component plant and twin also use, neither of which has a
49
+ * 3D viewer. Staging is something the host does, so the host says whether it happened
50
+ * (`acknowledgeProposalStaged`) and in what words.
51
+ */
52
+ stagedNoticeKey?: string
31
53
  }
32
54
 
33
55
  export const chatDefaults: ChatDefaults = { catalogEntries: [], slashTemplates: [] }
@@ -37,4 +59,6 @@ export function registerChatDefaults(defaults: Partial<ChatDefaults>): void {
37
59
  if (defaults.catalogEntries) chatDefaults.catalogEntries = defaults.catalogEntries
38
60
  if (defaults.slashTemplates) chatDefaults.slashTemplates = defaults.slashTemplates
39
61
  if (defaults.footerNoticeKey) chatDefaults.footerNoticeKey = defaults.footerNoticeKey
62
+ if (defaults.proposalChoices) chatDefaults.proposalChoices = defaults.proposalChoices
63
+ if (defaults.stagedNoticeKey) chatDefaults.stagedNoticeKey = defaults.stagedNoticeKey
40
64
  }
@@ -212,6 +212,21 @@ export declare class OxAssistantChat extends LitElement {
212
212
  private openToolSteps;
213
213
  /** 이미 실행을 요청한 제안 키 — 같은 카드를 두 번 눌러 되돌릴 수 없는 명령이 두 번 나가지 않게. */
214
214
  private sentProposals;
215
+ /** Proposal keys the host has reported staged. Only the host knows; see `acknowledgeProposalStaged`. */
216
+ private stagedProposals;
217
+ /**
218
+ * The host reports that it put this candidate somewhere the person can look at it.
219
+ *
220
+ * Staging happens outside this component — a dock catches `assistant-proposal-preview`, hands
221
+ * the candidate to a viewer, and only that host knows whether it worked, or whether it has a
222
+ * viewer at all. So the card says nothing until the host calls this, and it then says it in the
223
+ * words the host registered (`chatDefaults.stagedNoticeKey`).
224
+ *
225
+ * Safe to call for a candidate the card never showed; the key simply never matches.
226
+ */
227
+ acknowledgeProposalStaged(proposal: any): void;
228
+ /** Once per page: the miss is a wiring mistake, and repeating it per candidate buries it. */
229
+ private static warnedMissingStagedNotice;
215
230
  /**
216
231
  * 이 대화가 길어져 앞부분이 프롬프트에서 접혔다는 **사실**(서버 보고). 주제 이탈을 판정한 것이
217
232
  * 아니다 — 판정하면 오탐이 생기고, 오탐이 생기면 사용자는 안내 자체를 무시한다.
@@ -223,6 +223,8 @@ let OxAssistantChat = class OxAssistantChat extends LitElement {
223
223
  this.openToolSteps = new Set();
224
224
  /** 이미 실행을 요청한 제안 키 — 같은 카드를 두 번 눌러 되돌릴 수 없는 명령이 두 번 나가지 않게. */
225
225
  this.sentProposals = new Set();
226
+ /** Proposal keys the host has reported staged. Only the host knows; see `acknowledgeProposalStaged`. */
227
+ this.stagedProposals = new Set();
226
228
  /** 안내를 닫았는가 — 한 번 닫으면 이 세션에서는 다시 띄우지 않는다(잔소리 금지). */
227
229
  this.foldNoticeDismissed = false;
228
230
  this.busy = false;
@@ -395,6 +397,35 @@ let OxAssistantChat = class OxAssistantChat extends LitElement {
395
397
  this.onAttachFiles(e.dataTransfer.files);
396
398
  }
397
399
  }
400
+ /**
401
+ * The host reports that it put this candidate somewhere the person can look at it.
402
+ *
403
+ * Staging happens outside this component — a dock catches `assistant-proposal-preview`, hands
404
+ * the candidate to a viewer, and only that host knows whether it worked, or whether it has a
405
+ * viewer at all. So the card says nothing until the host calls this, and it then says it in the
406
+ * words the host registered (`chatDefaults.stagedNoticeKey`).
407
+ *
408
+ * Safe to call for a candidate the card never showed; the key simply never matches.
409
+ */
410
+ acknowledgeProposalStaged(proposal) {
411
+ /*
412
+ Silence has two meanings and they are not the same defect. A host that never calls this is
413
+ telling us it does not know, and drawing nothing is right. A host that calls it without
414
+ registering `stagedNoticeKey` meant to say something and left out the words -- staying quiet
415
+ about that reads to whoever wired it as "the badge just doesn't appear here".
416
+
417
+ The screen still shows nothing either way. There is no sentence to show, and inventing one is
418
+ what this whole correction removed.
419
+ */
420
+ if (!chatDefaults.stagedNoticeKey && !OxAssistantChat_1.warnedMissingStagedNotice) {
421
+ OxAssistantChat_1.warnedMissingStagedNotice = true;
422
+ console.warn('[assistant-chat] a host reported a proposal staged but registered no `stagedNoticeKey`, ' +
423
+ 'so the card can say nothing. Pass one to `registerChatDefaults` from the host.');
424
+ }
425
+ this.stagedProposals = new Set(this.stagedProposals).add(this.proposalKey(proposal));
426
+ }
427
+ /** Once per page: the miss is a wiring mistake, and repeating it per candidate buries it. */
428
+ static { this.warnedMissingStagedNotice = false; }
398
429
  /** 현재 로그인 사용자 email — 그룹챗에서 내 메시지 vs 타인 메시지 구분.
399
430
  * email 을 쓰는 이유: /auth/profile 이 항상 내려주는 로그인 식별자이고(비어 있지 않다),
400
431
  * 내부 관리키(User.id)를 클라이언트 판정에 의존하지 않기 위함. */
@@ -2836,13 +2867,12 @@ let OxAssistantChat = class OxAssistantChat extends LitElement {
2836
2867
  ${items.map(p => {
2837
2868
  const key = this.proposalKey(p);
2838
2869
  const sent = this.sentProposals.has(key);
2839
- const rawChoices = Array.isArray(p.choices) ? p.choices : (p.source ? [
2840
- { id: 'save', action: 'save', label: i18next.t('label.save-as-draft', { defaultValue: '초안 저장' }), icon: 'save', primary: true },
2841
- { id: 'repropose', action: 'prompt', label: '다른 콘셉트 재제안', icon: 'refresh', prompt: '현재 제안과 완전히 다른 스타일과 콘셉트로 새로 제안해줘' },
2842
- { id: 'slim', action: 'prompt', label: '비율 슬림 조정', icon: 'aspect_ratio', prompt: '전체적인 가로세로 비율을 좀 더 슬림하고 날렵하게 조정해줘' },
2843
- { id: 'palette', action: 'prompt', label: '색상 테마 변경', icon: 'palette', prompt: '다른 팔레트 색상 테마로 부품 색상을 변경해줘' },
2844
- { id: 'detail', action: 'prompt', label: '디테일 보강', icon: 'extension', prompt: '주요 디테일과 센서 부품을 좀 더 풍부하게 보강해줘' }
2845
- ] : []);
2870
+ /*
2871
+ Follow-up chips are the host's words, never this component's. The fallback that used
2872
+ to live here named a 3D modeller's operations in hardcoded Korean, and it reached the
2873
+ plant and twin chats too.
2874
+ */
2875
+ const rawChoices = Array.isArray(p.choices) ? p.choices : (chatDefaults.proposalChoices ?? []);
2846
2876
  return html `
2847
2877
  <div class="proposal ${sent ? 'sent' : ''}">
2848
2878
  <md-icon>${p.icon || (p.source ? 'view_in_ar' : 'bolt')}</md-icon>
@@ -2850,20 +2880,32 @@ let OxAssistantChat = class OxAssistantChat extends LitElement {
2850
2880
  <span class="plabel">${p.label || p.command}</span>
2851
2881
  ${p.reason ? html `<span class="preason">${p.reason}</span>` : nothing}
2852
2882
  </span>
2853
- ${!rawChoices.length ? html `
2854
- <button
2855
- class="run"
2856
- ?disabled=${sent}
2857
- @click=${() => this.executeProposal(p, key)}>
2858
- ${sent ? i18next.t('ai-assistant.text.proposal-sent', { defaultValue: '실행 요청됨' })
2883
+ ${ /*
2884
+ The run button and the follow-up chips answer different questions — "is there
2885
+ something to apply" and "what else could I ask for" — and they used to share one
2886
+ ternary on `rawChoices.length`. A proposal that carried chips lost its button, so
2887
+ the one action the card exists for became unreachable while the model kept
2888
+ telling people to press it.
2889
+ */''}
2890
+ <button
2891
+ class="run"
2892
+ ?disabled=${sent}
2893
+ @click=${() => this.executeProposal(p, key)}>
2894
+ ${sent ? i18next.t('ai-assistant.text.proposal-sent', { defaultValue: '실행 요청됨' })
2859
2895
  : i18next.t('ai-assistant.button.run-proposal', { defaultValue: '실행' })}
2860
- </button>
2861
- ` : html `
2862
- <span class="staged-badge" title="3D 뷰어에 모델이 표시되었습니다">
2896
+ </button>
2897
+ ${ /*
2898
+ Drawn only where the host has said it staged this candidate, in the host's own
2899
+ words. Not knowing is not a claim: with no word from the host, nothing is drawn.
2900
+ This badge used to read "there are chips" as "the model is in the 3D viewer" and
2901
+ say so, in a component that plant and twin also use.
2902
+ */''}
2903
+ ${this.stagedProposals.has(key) && chatDefaults.stagedNoticeKey ? html `
2904
+ <span class="staged-badge">
2863
2905
  <md-icon>check_circle</md-icon>
2864
- <span>뷰어 반영됨</span>
2906
+ <span>${i18next.t(chatDefaults.stagedNoticeKey)}</span>
2865
2907
  </span>
2866
- `}
2908
+ ` : nothing}
2867
2909
  </div>
2868
2910
  ${rawChoices.length ? html `
2869
2911
  <div class="proposal-choices" role="group" aria-label="대화 선택지">
@@ -3554,6 +3596,10 @@ __decorate([
3554
3596
  state(),
3555
3597
  __metadata("design:type", Object)
3556
3598
  ], OxAssistantChat.prototype, "sentProposals", void 0);
3599
+ __decorate([
3600
+ state(),
3601
+ __metadata("design:type", Object)
3602
+ ], OxAssistantChat.prototype, "stagedProposals", void 0);
3557
3603
  __decorate([
3558
3604
  state(),
3559
3605
  __metadata("design:type", Object)