@kodaris/krubble-app-components 1.0.123 → 1.0.124

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.
@@ -2926,6 +2926,7 @@
2926
2926
  this._orgSkillRequestId = 0;
2927
2927
  this._globalSkillRequestId = 0;
2928
2928
  this._invocableSkillRequestId = 0;
2929
+ this._suggestedActionRequestId = 0;
2929
2930
  /**
2930
2931
  * Codes of global skills with an in-flight enable/disable request. The `+`
2931
2932
  * button reads this to show a pending state and ignore repeat clicks. Mutated
@@ -3129,6 +3130,12 @@
3129
3130
  * so installed globals in the menu never leak into the "Your Org" tab.
3130
3131
  */
3131
3132
  this._invocableSkills = [];
3133
+ /**
3134
+ * Context-aware action chips for the empty state, from `/conversation/suggestedActions`.
3135
+ * Loaded fresh every time the panel opens on an empty conversation (see `_loadSuggestedActions`);
3136
+ * cleared when the first message is sent so they don't linger over a live conversation.
3137
+ */
3138
+ this._suggestedActions = [];
3132
3139
  /**
3133
3140
  * AI framework UI config flags fetched from `aiFrameworkConfig` on connect.
3134
3141
  * `debugTelemetryEnabled` (same server-side gate as TOKEN_USAGE emission) turns
@@ -3483,6 +3490,13 @@
3483
3490
  }
3484
3491
  }, 200);
3485
3492
  }
3493
+ // Load context-aware action chips every time the panel opens on an empty conversation. Fires for
3494
+ // the floating variant on toggle-open and for the inline variant on its first render (which sets
3495
+ // `opened` true at connect). Only when empty — a resumed conversation shows its transcript, not chips.
3496
+ // The request-id guard inside handles a stale reply if the user closes/reopens quickly.
3497
+ if (changed.has('opened') && this.opened && this.messages.length === 0) {
3498
+ this._loadSuggestedActions();
3499
+ }
3486
3500
  // Position the fixed skill menu above the composer, following the krubble
3487
3501
  // select-field pattern (fixed element positioned from the trigger's rect).
3488
3502
  if (this._skillMenuOpened) {
@@ -3915,6 +3929,41 @@
3915
3929
  console.error('kr-chatbot: invocable skill search failed', error);
3916
3930
  });
3917
3931
  }
3932
+ /**
3933
+ * Load the empty-state action chips from `/conversation/suggestedActions`, passing the user's
3934
+ * current page/selection context (captured live, same as a send). The server picks a few skills
3935
+ * the user can run that fit where they are and writes a label for each. Called every time the
3936
+ * panel opens on an empty conversation (per product decision — context can change between opens).
3937
+ *
3938
+ * AI-only, no fallback: any failure, or v2 chat being off (server returns `[]`), just leaves the
3939
+ * empty state with no chips. A request-id guard drops a response superseded by a newer open, so a
3940
+ * slow reply from a previous open can't land after the user has started typing.
3941
+ */
3942
+ _loadSuggestedActions() {
3943
+ this._suggestedActionRequestId++;
3944
+ const requestId = this._suggestedActionRequestId;
3945
+ const context = this._captureContext();
3946
+ krubbleHttp.Http.fetch({
3947
+ url: '/api/system/conversation/suggestedActions',
3948
+ method: 'POST',
3949
+ body: {
3950
+ pageContext: context.pageContext,
3951
+ selectionContext: context.selectionContext,
3952
+ },
3953
+ })
3954
+ .then((r) => r.json())
3955
+ .then((res) => {
3956
+ // Ignore a stale response from a prior open, and never overwrite chips once a conversation
3957
+ // has started (the user opened, the reply was slow, they typed and sent in the meantime).
3958
+ if (requestId !== this._suggestedActionRequestId || this.messages.length > 0) {
3959
+ return;
3960
+ }
3961
+ this._suggestedActions = (res?.data || []);
3962
+ })
3963
+ .catch((error) => {
3964
+ console.error('kr-chatbot: suggested actions load failed', error);
3965
+ });
3966
+ }
3918
3967
  /**
3919
3968
  * Load the browse dialog's "Kodaris" tab into `_globalSkills` — the whole
3920
3969
  * published Kodaris catalog for discovery, installed or not, runnable by this
@@ -4158,6 +4207,17 @@
4158
4207
  this._handleSkillSelect(skill);
4159
4208
  }
4160
4209
  }
4210
+ /**
4211
+ * Click an empty-state suggestion chip: prefill `/code ` into the composer, same as picking the
4212
+ * skill from the `/` menu, and focus so the user can add details before sending. No permission
4213
+ * guards here (unlike `_handleSkillSelect`) — a suggested action's code always comes from the
4214
+ * server's role-filtered runnable set, so it's already known to be invokable by this user.
4215
+ */
4216
+ _handleSuggestedActionClick(action) {
4217
+ this._inputValue = `/${action.code} `;
4218
+ this._saveState();
4219
+ this._inputEl?.focus();
4220
+ }
4161
4221
  /** Copy a message's text to the clipboard and briefly flash "Copied". */
4162
4222
  _handleCopyMessage(content, index) {
4163
4223
  navigator.clipboard.writeText(content).then(() => {
@@ -4440,6 +4500,9 @@
4440
4500
  }
4441
4501
  this.messages = [...this.messages, userMessage];
4442
4502
  this._inputValue = '';
4503
+ // The conversation has started — drop the empty-state chips so they don't linger above the
4504
+ // transcript. A newer suggestedActions reply is also ignored now (its guard checks messages.length).
4505
+ this._suggestedActions = [];
4443
4506
  this._saveState();
4444
4507
  this._error = '';
4445
4508
  this._userHasScrolledUp = false;
@@ -6183,6 +6246,17 @@
6183
6246
  ${this.messages.length === 0 ? b `
6184
6247
  <div class="empty">
6185
6248
  <span class="empty-text">What can I help you with?</span>
6249
+ ${this._suggestedActions.length ? b `
6250
+ <div class="empty-suggestions">
6251
+ ${this._suggestedActions.map((action) => b `
6252
+ <button
6253
+ type="button"
6254
+ class="empty-suggestions__chip"
6255
+ @click=${() => this._handleSuggestedActionClick(action)}
6256
+ >${action.label}</button>
6257
+ `)}
6258
+ </div>
6259
+ ` : A}
6186
6260
  </div>
6187
6261
  ` : A}
6188
6262
  ${messageGroups.map((group) => {
@@ -7038,7 +7112,7 @@
7038
7112
  align-items: center;
7039
7113
  justify-content: center;
7040
7114
  color: var(--kr-chatbot-text-secondary);
7041
- gap: 8px;
7115
+ gap: 16px;
7042
7116
  }
7043
7117
 
7044
7118
  .empty-text {
@@ -7046,6 +7120,35 @@
7046
7120
  color: rgb(0 0 0 / 80%);
7047
7121
  }
7048
7122
 
7123
+ /* Context-aware action chips under the empty-state prompt. A row of pills the user can click to
7124
+ prefill a skill — capped small server-side so this reads as a few suggestions, not a menu. */
7125
+ .empty-suggestions {
7126
+ display: flex;
7127
+ flex-wrap: wrap;
7128
+ justify-content: center;
7129
+ gap: 10px;
7130
+ max-width: 420px;
7131
+ margin-top: 4px;
7132
+ }
7133
+
7134
+ .empty-suggestions__chip {
7135
+ padding: 8px 12px;
7136
+ background: rgb(0 0 0 / 6%);
7137
+ border: none;
7138
+ border-radius: 16px;
7139
+ color: #000000;
7140
+ font: inherit;
7141
+ font-size: 13px;
7142
+ line-height: 1.2;
7143
+ cursor: pointer;
7144
+ white-space: nowrap;
7145
+ }
7146
+
7147
+ .empty-suggestions__chip:hover {
7148
+ background: rgb(0 0 0 / 10%);
7149
+ color: #000000;
7150
+ }
7151
+
7049
7152
  .message {
7050
7153
  position: relative;
7051
7154
  display: flex;
@@ -8709,6 +8812,9 @@
8709
8812
  __decorate$4([
8710
8813
  r()
8711
8814
  ], KRChatbot.prototype, "_invocableSkills", void 0);
8815
+ __decorate$4([
8816
+ r()
8817
+ ], KRChatbot.prototype, "_suggestedActions", void 0);
8712
8818
  __decorate$4([
8713
8819
  r()
8714
8820
  ], KRChatbot.prototype, "_aiFrameworkConfig", void 0);