@kodaris/krubble-app-components 1.0.71 → 1.0.73

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.
@@ -5647,6 +5647,12 @@
5647
5647
  * Input placeholder text.
5648
5648
  */
5649
5649
  this.placeholder = 'Type a message...';
5650
+ /**
5651
+ * Skills the user can invoke by typing `/` in the composer. Selecting one
5652
+ * inserts `/name ` into the message. The `/` menu shows the first 15 (filtered
5653
+ * as the user types); "Browse all skills" opens a dialog with the full list.
5654
+ */
5655
+ this.skills = [];
5650
5656
  /**
5651
5657
  * Whether the chat panel is open. Only relevant in floating mode.
5652
5658
  */
@@ -5676,6 +5682,20 @@
5676
5682
  this._pendingInternalFiles = [];
5677
5683
  /** True while a file is being dragged over the panel (shows the drop overlay). */
5678
5684
  this._isDraggingFile = false;
5685
+ /** Whether the `/` skill menu is open above the composer. */
5686
+ this._skillMenuOpened = false;
5687
+ /** Current `/` query (text after the slash), used to filter the skill menu. */
5688
+ this._skillQuery = '';
5689
+ /** Highlighted item in the skill menu for keyboard navigation (-1 = none). */
5690
+ this._skillHighlighted = 0;
5691
+ /** Index of the message whose copy button just flashed "Copied" (-1 = none). */
5692
+ this._copiedIndex = -1;
5693
+ /** Whether the "Browse all skills" dialog is open. */
5694
+ this._skillBrowseOpened = false;
5695
+ /** Active tab in the browse dialog. */
5696
+ this._skillBrowseSource = 'org';
5697
+ /** Search text in the browse dialog. */
5698
+ this._skillBrowseQuery = '';
5679
5699
  this._handleMouseMove = (e) => {
5680
5700
  if (this._isDragging) {
5681
5701
  let newLeft = this._dragStartLeft + (e.clientX - this._dragStartX);
@@ -5789,6 +5809,40 @@
5789
5809
  }
5790
5810
  }, 200);
5791
5811
  }
5812
+ // Position the fixed skill menu above the composer, following the krubble
5813
+ // select-field pattern (fixed element positioned from the trigger's rect).
5814
+ if (this._skillMenuOpened) {
5815
+ this._positionSkillMenu();
5816
+ }
5817
+ // Keep the keyboard-highlighted skill scrolled into view.
5818
+ if (changed.has('_skillHighlighted') && this._skillMenuOpened) {
5819
+ this._skillMenuEl
5820
+ ?.querySelector('.skill-menu__item--highlighted')
5821
+ ?.scrollIntoView({ block: 'nearest' });
5822
+ }
5823
+ }
5824
+ /** Anchor the fixed skill menu to the composer, opening upward above it. */
5825
+ _positionSkillMenu() {
5826
+ requestAnimationFrame(() => {
5827
+ if (!this._skillMenuEl || !this._inputWrapperEl) {
5828
+ return;
5829
+ }
5830
+ const inputRect = this._inputWrapperEl.getBoundingClientRect();
5831
+ this._skillMenuEl.style.left = inputRect.left + 'px';
5832
+ this._skillMenuEl.style.width = inputRect.width + 'px';
5833
+ // Bound the menu by the chat panel's own top edge so it stays within the
5834
+ // widget. In floating mode that keeps it inside the card; in inline mode the
5835
+ // panel top sits just below the app header/breadcrumb, so it stays clear of
5836
+ // those too. An 8px gap keeps it off both edges.
5837
+ const topLimit = this._panelEl.getBoundingClientRect().top + 8;
5838
+ const spaceAbove = inputRect.top - 8 - topLimit;
5839
+ this._skillMenuEl.style.maxHeight = spaceAbove + 'px';
5840
+ // Measure after the cap is applied so the upward anchor uses the real
5841
+ // height, then clamp the top edge to the panel bound.
5842
+ const menuRect = this._skillMenuEl.getBoundingClientRect();
5843
+ const top = Math.max(topLimit, inputRect.top - menuRect.height - 8);
5844
+ this._skillMenuEl.style.top = top + 'px';
5845
+ });
5792
5846
  }
5793
5847
  // --- Public methods ---
5794
5848
  /**
@@ -5982,13 +6036,129 @@
5982
6036
  const textarea = e.target;
5983
6037
  textarea.style.height = 'auto';
5984
6038
  textarea.style.height = Math.min(textarea.scrollHeight, 120) + 'px';
6039
+ // Open the skill menu while the message is a `/` command being typed (a
6040
+ // leading slash with no space yet). The text after the slash filters it.
6041
+ const slashMatch = this._inputValue.match(/^\/(\S*)$/);
6042
+ if (slashMatch && this.skills.length) {
6043
+ this._skillMenuOpened = true;
6044
+ this._skillQuery = slashMatch[1];
6045
+ this._skillHighlighted = 0;
6046
+ }
6047
+ else {
6048
+ this._skillMenuOpened = false;
6049
+ }
5985
6050
  }
5986
6051
  _handleKeyDown(e) {
6052
+ // When the skill menu is open, arrow keys / Enter / Escape drive it. The two
6053
+ // top actions (Attach Files, Browse All Skills) are index 0 and 1, then the
6054
+ // filtered skills follow — all navigable as one list.
6055
+ if (this._skillMenuOpened) {
6056
+ const count = 2 + this._filteredSkills().length;
6057
+ if (e.key === 'ArrowDown') {
6058
+ e.preventDefault();
6059
+ this._skillHighlighted = (this._skillHighlighted + 1) % count;
6060
+ return;
6061
+ }
6062
+ if (e.key === 'ArrowUp') {
6063
+ e.preventDefault();
6064
+ this._skillHighlighted = (this._skillHighlighted - 1 + count) % count;
6065
+ return;
6066
+ }
6067
+ if (e.key === 'Enter' && !e.shiftKey) {
6068
+ e.preventDefault();
6069
+ this._activateSkillMenuItem(this._skillHighlighted);
6070
+ return;
6071
+ }
6072
+ if (e.key === 'Escape') {
6073
+ e.preventDefault();
6074
+ this._skillMenuOpened = false;
6075
+ return;
6076
+ }
6077
+ }
5987
6078
  if (e.key === 'Enter' && !e.shiftKey) {
5988
6079
  e.preventDefault();
5989
6080
  this._handleSubmit();
5990
6081
  }
5991
6082
  }
6083
+ /** Run the skill-menu item at the given flat index (0/1 = actions, then skills). */
6084
+ _activateSkillMenuItem(index) {
6085
+ if (index === 0) {
6086
+ this._openFilePicker();
6087
+ return;
6088
+ }
6089
+ if (index === 1) {
6090
+ this._handleSkillBrowseOpen();
6091
+ return;
6092
+ }
6093
+ const skill = this._filteredSkills()[index - 2];
6094
+ if (skill) {
6095
+ this._handleSkillSelect(skill);
6096
+ }
6097
+ }
6098
+ // --- Skills ---
6099
+ /** Skills matching the current `/` query, capped at the first 15. */
6100
+ _filteredSkills() {
6101
+ const query = this._skillQuery.toLowerCase();
6102
+ return this.skills
6103
+ .filter((skill) => skill.name.toLowerCase().includes(query) ||
6104
+ skill.label.toLowerCase().includes(query))
6105
+ .slice(0, 15);
6106
+ }
6107
+ /** The `+` button toggles the skill/attachment menu open (showing all skills). */
6108
+ _handlePlusClick() {
6109
+ if (this._skillMenuOpened) {
6110
+ this._skillMenuOpened = false;
6111
+ return;
6112
+ }
6113
+ this._skillQuery = '';
6114
+ this._skillHighlighted = 0;
6115
+ this._skillMenuOpened = true;
6116
+ }
6117
+ /** Insert `/name ` into the composer and focus it, ready for the message. */
6118
+ _handleSkillSelect(skill) {
6119
+ this._inputValue = `/${skill.name} `;
6120
+ this._skillMenuOpened = false;
6121
+ this._skillBrowseOpened = false;
6122
+ this._inputEl?.focus();
6123
+ }
6124
+ /** Copy a message's text to the clipboard and briefly flash "Copied". */
6125
+ _handleCopyMessage(content, index) {
6126
+ navigator.clipboard.writeText(content).then(() => {
6127
+ this._copiedIndex = index;
6128
+ setTimeout(() => {
6129
+ if (this._copiedIndex === index) {
6130
+ this._copiedIndex = -1;
6131
+ }
6132
+ }, 2000);
6133
+ }).catch(() => { });
6134
+ }
6135
+ _handleSkillBrowseOpen() {
6136
+ this._skillMenuOpened = false;
6137
+ this._skillBrowseQuery = '';
6138
+ this._skillBrowseOpened = true;
6139
+ }
6140
+ _handleSkillBrowseClose() {
6141
+ this._skillBrowseOpened = false;
6142
+ }
6143
+ _handleSkillBrowseSearch(e) {
6144
+ this._skillBrowseQuery = e.target.value;
6145
+ }
6146
+ _handleSkillBrowseTabChange(e) {
6147
+ this._skillBrowseSource = e.detail.activeTabId;
6148
+ }
6149
+ /** Skills for the browse dialog's active tab, filtered by its search box. */
6150
+ _browseSkills() {
6151
+ const query = this._skillBrowseQuery.toLowerCase();
6152
+ return this.skills.filter((skill) => {
6153
+ const source = skill.source || 'org';
6154
+ if (source !== this._skillBrowseSource) {
6155
+ return false;
6156
+ }
6157
+ return skill.name.toLowerCase().includes(query) ||
6158
+ skill.label.toLowerCase().includes(query) ||
6159
+ (skill.description || '').toLowerCase().includes(query);
6160
+ });
6161
+ }
5992
6162
  // --- Attachments ---
5993
6163
  _openFilePicker() {
5994
6164
  this._fileInputEl?.click();
@@ -6346,6 +6516,119 @@
6346
6516
  }
6347
6517
  }
6348
6518
  // --- Render ---
6519
+ /** The `/` skill menu shown above the composer while typing a slash command. */
6520
+ _renderSkillMenu() {
6521
+ const skills = this._filteredSkills();
6522
+ return b `
6523
+ <div class="skill-menu">
6524
+ <div class="skill-menu__actions">
6525
+ <button
6526
+ class=${e({
6527
+ 'skill-menu__action': true,
6528
+ 'skill-menu__item--highlighted': this._skillHighlighted === 0,
6529
+ })}
6530
+ @click=${this._openFilePicker}
6531
+ >
6532
+ Attach Files
6533
+ </button>
6534
+ <button
6535
+ class=${e({
6536
+ 'skill-menu__action': true,
6537
+ 'skill-menu__item--highlighted': this._skillHighlighted === 1,
6538
+ })}
6539
+ @click=${this._handleSkillBrowseOpen}
6540
+ >
6541
+ Browse All Skills
6542
+ </button>
6543
+ </div>
6544
+ ${skills.length ? b `
6545
+ <ul class="skill-menu__list">
6546
+ ${skills.map((skill, index) => b `
6547
+ <li>
6548
+ <button
6549
+ class=${e({
6550
+ 'skill-menu__item': true,
6551
+ 'skill-menu__item--highlighted': index + 2 === this._skillHighlighted,
6552
+ })}
6553
+ @click=${() => this._handleSkillSelect(skill)}
6554
+ >
6555
+ <span class="skill-menu__name">${skill.label}</span>
6556
+ ${skill.description ? b `
6557
+ <span class="skill-menu__desc">${skill.description}</span>
6558
+ ` : A}
6559
+ </button>
6560
+ </li>
6561
+ `)}
6562
+ </ul>
6563
+ ` : b `
6564
+ <div class="skill-menu__empty">No matching skills</div>
6565
+ `}
6566
+ </div>
6567
+ `;
6568
+ }
6569
+ /** The "Browse all skills" dialog — a searchable grid of Org / Kodaris cards. */
6570
+ _renderSkillBrowse() {
6571
+ return b `
6572
+ <kr-dialog
6573
+ opened
6574
+ label="Browse Skills"
6575
+ width="720px"
6576
+ @close=${this._handleSkillBrowseClose}
6577
+ >
6578
+ <div class="skill-browse">
6579
+ <kr-text-field
6580
+ placeholder="Search skills..."
6581
+ .value=${this._skillBrowseQuery}
6582
+ @input=${this._handleSkillBrowseSearch}
6583
+ ></kr-text-field>
6584
+ <kr-tab-group
6585
+ active-tab-id=${this._skillBrowseSource}
6586
+ @tab-change=${this._handleSkillBrowseTabChange}
6587
+ >
6588
+ <kr-tab id="org" label="Your Org">
6589
+ ${this._renderSkillGrid()}
6590
+ </kr-tab>
6591
+ <kr-tab id="kodaris" label="Kodaris">
6592
+ ${this._renderSkillGrid()}
6593
+ </kr-tab>
6594
+ </kr-tab-group>
6595
+ </div>
6596
+ </kr-dialog>
6597
+ `;
6598
+ }
6599
+ /** The card grid for the browse dialog's active tab. */
6600
+ _renderSkillGrid() {
6601
+ const skills = this._browseSkills();
6602
+ if (!skills.length) {
6603
+ return b `<div class="skill-browse__empty">No matching skills</div>`;
6604
+ }
6605
+ return b `
6606
+ <div class="skill-browse__grid">
6607
+ ${skills.map((skill) => b `
6608
+ <button
6609
+ class="skill-card"
6610
+ @click=${() => this._handleSkillSelect(skill)}
6611
+ >
6612
+ <span class="skill-card__name">${skill.label}</span>
6613
+ ${skill.description ? b `
6614
+ <span class="skill-card__desc">${skill.description}</span>
6615
+ ` : A}
6616
+ </button>
6617
+ `)}
6618
+ </div>
6619
+ `;
6620
+ }
6621
+ /**
6622
+ * Render a user message, highlighting a leading `/skill-name` token when it
6623
+ * matches a known skill (like the way Claude highlights slash commands).
6624
+ */
6625
+ _renderUserContent(content) {
6626
+ const match = content.match(/^\/(\S+)(\s[\s\S]*)?$/);
6627
+ if (match && this.skills.some((skill) => skill.name === match[1])) {
6628
+ return b `<span class="message-skill">/${match[1]}</span>${match[2] || ''}`;
6629
+ }
6630
+ return content;
6631
+ }
6349
6632
  render() {
6350
6633
  return b `
6351
6634
  ${(this.hideToggle || this.variant === 'inline') ? A : b `
@@ -6502,7 +6785,7 @@
6502
6785
  <span class="empty-text">What can I help you with?</span>
6503
6786
  </div>
6504
6787
  ` : A}
6505
- ${this.messages.map((msg) => b `
6788
+ ${this.messages.map((msg, index) => b `
6506
6789
  <div class=${e({
6507
6790
  'message': true,
6508
6791
  'message--user': msg.type === 'user',
@@ -6534,7 +6817,7 @@
6534
6817
  ${msg.content ? b `
6535
6818
  <div class="message-content">
6536
6819
  ${msg.type === 'user'
6537
- ? msg.content
6820
+ ? this._renderUserContent(msg.content)
6538
6821
  : o(marked.parse(msg.content, { breaks: true }))}
6539
6822
  </div>
6540
6823
  ` : A}
@@ -6545,6 +6828,46 @@
6545
6828
  <span></span>
6546
6829
  </div>
6547
6830
  ` : A}
6831
+ ${msg.content ? b `
6832
+ <button
6833
+ class="message-copy"
6834
+ title="Copy"
6835
+ @click=${() => this._handleCopyMessage(msg.content, index)}
6836
+ >
6837
+ ${this._copiedIndex === index ? b `
6838
+ <svg
6839
+ viewBox="0 0 24 24"
6840
+ fill="none"
6841
+ stroke="currentColor"
6842
+ stroke-width="1.5"
6843
+ stroke-linecap="round"
6844
+ stroke-linejoin="round"
6845
+ >
6846
+ <path d="M20 6 9 17l-5-5" />
6847
+ </svg>
6848
+ Copied
6849
+ ` : b `
6850
+ <svg
6851
+ viewBox="0 0 24 24"
6852
+ fill="none"
6853
+ stroke="currentColor"
6854
+ stroke-width="1.5"
6855
+ stroke-linecap="round"
6856
+ stroke-linejoin="round"
6857
+ >
6858
+ <rect
6859
+ x="9"
6860
+ y="9"
6861
+ width="13"
6862
+ height="13"
6863
+ rx="2"
6864
+ />
6865
+ <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
6866
+ </svg>
6867
+ Copy
6868
+ `}
6869
+ </button>
6870
+ ` : A}
6548
6871
  </div>
6549
6872
  `)}
6550
6873
  </div>
@@ -6609,11 +6932,11 @@
6609
6932
  hidden
6610
6933
  @change=${this._handleFileInputChange}
6611
6934
  />
6935
+ ${this._skillMenuOpened ? this._renderSkillMenu() : A}
6612
6936
  <div class="input-wrapper">
6613
6937
  <button
6614
6938
  class="input-plus"
6615
- title="Attach image"
6616
- @click=${this._openFilePicker}
6939
+ @click=${this._handlePlusClick}
6617
6940
  >+</button>
6618
6941
  <textarea
6619
6942
  class="input"
@@ -6670,6 +6993,7 @@
6670
6993
  `}
6671
6994
  </div>
6672
6995
  </div>
6996
+ ${this._skillBrowseOpened ? this._renderSkillBrowse() : A}
6673
6997
  </div>
6674
6998
  `;
6675
6999
  }
@@ -6909,6 +7233,7 @@
6909
7233
  }
6910
7234
 
6911
7235
  .message {
7236
+ position: relative;
6912
7237
  display: flex;
6913
7238
  flex-direction: column;
6914
7239
  max-width: 100%;
@@ -6953,6 +7278,58 @@
6953
7278
  font-size: 14px;
6954
7279
  }
6955
7280
 
7281
+ /* Highlighted leading /skill token in a sent user message. Monospace + a
7282
+ stronger color makes it read as a distinct command without a pill. */
7283
+ .message-skill {
7284
+ font-family: 'SF Mono', SFMono-Regular, ui-monospace, Menlo, Consolas, monospace;
7285
+ font-weight: 600;
7286
+ color: #b45309;
7287
+ }
7288
+
7289
+ /* Per-message copy button. Kept in flow with a fixed height that is always
7290
+ reserved (via a negative bottom margin that cancels it) so message spacing
7291
+ is identical whether or not it's shown; only its opacity toggles on hover. */
7292
+ .message-copy {
7293
+ align-self: flex-start;
7294
+ margin-top: 4px;
7295
+ margin-bottom: -27px;
7296
+ display: inline-flex;
7297
+ align-items: center;
7298
+ gap: 4px;
7299
+ height: 23px;
7300
+ padding: 4px 8px;
7301
+ background: none;
7302
+ border: none;
7303
+ border-radius: 6px;
7304
+ font: inherit;
7305
+ font-size: 12px;
7306
+ color: #64668b;
7307
+ cursor: pointer;
7308
+ opacity: 0;
7309
+ pointer-events: none;
7310
+ transition: opacity 0.15s, background-color 0.15s, color 0.15s;
7311
+ }
7312
+
7313
+ .message--user .message-copy {
7314
+ align-self: flex-end;
7315
+ }
7316
+
7317
+ .message:hover .message-copy,
7318
+ .message-copy:focus-visible {
7319
+ opacity: 1;
7320
+ pointer-events: auto;
7321
+ }
7322
+
7323
+ .message-copy:hover {
7324
+ background: rgba(0, 0, 0, 0.06);
7325
+ color: #1a1a2e;
7326
+ }
7327
+
7328
+ .message-copy svg {
7329
+ width: 14px;
7330
+ height: 14px;
7331
+ }
7332
+
6956
7333
  .message--assistant .message-content {
6957
7334
  background: transparent;
6958
7335
  color: #000000;
@@ -7288,6 +7665,176 @@
7288
7665
  display: block;
7289
7666
  }
7290
7667
 
7668
+ /* Skill menu (opened by typing '/'). Fixed + JS-positioned above the
7669
+ composer, following the krubble select-field dropdown pattern. */
7670
+ .skill-menu {
7671
+ position: fixed;
7672
+ z-index: 10000;
7673
+ background: white;
7674
+ border: 1px solid #9ba7b6;
7675
+ border-radius: 8px;
7676
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
7677
+ overflow: hidden;
7678
+ display: flex;
7679
+ flex-direction: column;
7680
+ /* max-height is set dynamically to the space above the composer. */
7681
+ }
7682
+
7683
+ /* Floating mode is a compact card — use a lighter shadow and softer border. */
7684
+ :host(:not([variant='inline'])) .skill-menu {
7685
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
7686
+ border-color: #b0b0b0;
7687
+ }
7688
+
7689
+ .skill-menu__list {
7690
+ list-style: none;
7691
+ margin: 0;
7692
+ padding: 8px 0;
7693
+ overflow-y: auto;
7694
+ }
7695
+
7696
+ .skill-menu__item {
7697
+ display: flex;
7698
+ flex-direction: column;
7699
+ gap: 2px;
7700
+ width: 100%;
7701
+ padding: 8px 16px;
7702
+ background: none;
7703
+ border: none;
7704
+ text-align: left;
7705
+ cursor: pointer;
7706
+ font: inherit;
7707
+ color: #000000;
7708
+ }
7709
+
7710
+
7711
+ /* Floating mode is a compact card — tighten name/description spacing. */
7712
+ :host(:not([variant='inline'])) .skill-menu__item {
7713
+ gap: 1px;
7714
+ }
7715
+
7716
+ .skill-menu__name {
7717
+ font-weight: 500;
7718
+ }
7719
+
7720
+ /* Floating mode is a compact card — shrink the skill name a touch. */
7721
+ :host(:not([variant='inline'])) .skill-menu__name {
7722
+ font-size: 13px;
7723
+ }
7724
+
7725
+ .skill-menu__desc {
7726
+ font-size: 12px;
7727
+ color: #000000;
7728
+ overflow: hidden;
7729
+ text-overflow: ellipsis;
7730
+ white-space: nowrap;
7731
+ }
7732
+
7733
+ .skill-menu__empty {
7734
+ padding: 16px;
7735
+ text-align: center;
7736
+ color: #000000;
7737
+ }
7738
+
7739
+ .skill-menu__actions {
7740
+ display: flex;
7741
+ flex-direction: column;
7742
+ padding: 8px 0;
7743
+ border-bottom: 1px solid #eee;
7744
+ flex-shrink: 0;
7745
+ }
7746
+
7747
+ .skill-menu__action {
7748
+ width: 100%;
7749
+ padding: 6px 16px;
7750
+ background: transparent;
7751
+ border: none;
7752
+ text-align: left;
7753
+ cursor: pointer;
7754
+ font: inherit;
7755
+ font-size: 13px;
7756
+ color: var(--kr-chatbot-primary);
7757
+ font-weight: 500;
7758
+ }
7759
+
7760
+ /* Highlight (keyboard) and hover (mouse) are independent — either shades the
7761
+ row. Declared after the base rules so it wins the cascade for both the
7762
+ action buttons and the skill items. */
7763
+ .skill-menu__action.skill-menu__item--highlighted,
7764
+ .skill-menu__item.skill-menu__item--highlighted,
7765
+ .skill-menu__action:hover,
7766
+ .skill-menu__item:hover {
7767
+ background: #f3f4f6;
7768
+ }
7769
+
7770
+ /* Browse-all dialog body. Owns the height so the dialog is the same size on
7771
+ every tab; the search is fixed and the card grid flexes/scrolls to fill. */
7772
+ .skill-browse {
7773
+ display: flex;
7774
+ flex-direction: column;
7775
+ height: 70vh;
7776
+ }
7777
+
7778
+ .skill-browse kr-text-field {
7779
+ display: block;
7780
+ margin: 20px 20px 16px;
7781
+ flex-shrink: 0;
7782
+ }
7783
+
7784
+ .skill-browse kr-tab-group {
7785
+ flex: 1;
7786
+ min-height: 0;
7787
+ }
7788
+
7789
+ /* The grid is the scroller and spans the full width, so its scrollbar sits
7790
+ flush against the dialog edge; padding-right insets the cards from it. */
7791
+ .skill-browse__grid {
7792
+ height: 100%;
7793
+ overflow-y: auto;
7794
+ display: grid;
7795
+ grid-template-columns: repeat(3, 1fr);
7796
+ grid-auto-rows: min-content;
7797
+ align-content: start;
7798
+ gap: 12px;
7799
+ padding: 16px 20px 20px;
7800
+ }
7801
+
7802
+ .skill-card {
7803
+ display: flex;
7804
+ flex-direction: column;
7805
+ gap: 6px;
7806
+ padding: 14px;
7807
+ background: white;
7808
+ border: 1px solid #e5e7eb;
7809
+ border-radius: 10px;
7810
+ text-align: left;
7811
+ cursor: pointer;
7812
+ font: inherit;
7813
+ color: #000000;
7814
+ transition: border-color 0.15s ease, box-shadow 0.15s ease;
7815
+ }
7816
+
7817
+ .skill-card:hover {
7818
+ border-color: var(--kr-chatbot-primary);
7819
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
7820
+ }
7821
+
7822
+ .skill-card__name {
7823
+ font-weight: 600;
7824
+ }
7825
+
7826
+ .skill-card__desc {
7827
+ font-size: 12px;
7828
+ color: #000000;
7829
+ }
7830
+
7831
+ .skill-browse__empty {
7832
+ height: 100%;
7833
+ padding: 32px;
7834
+ text-align: center;
7835
+ color: #000000;
7836
+ }
7837
+
7291
7838
  /* Composer attachment chips (pre-send) */
7292
7839
  .composer-attachments {
7293
7840
  display: flex;
@@ -7387,6 +7934,9 @@
7387
7934
  __decorate([
7388
7935
  n({ type: String })
7389
7936
  ], exports.Chatbot.prototype, "placeholder", void 0);
7937
+ __decorate([
7938
+ n({ attribute: false })
7939
+ ], exports.Chatbot.prototype, "skills", void 0);
7390
7940
  __decorate([
7391
7941
  n({
7392
7942
  type: Boolean,
@@ -7421,6 +7971,27 @@
7421
7971
  __decorate([
7422
7972
  r()
7423
7973
  ], exports.Chatbot.prototype, "_isDraggingFile", void 0);
7974
+ __decorate([
7975
+ r()
7976
+ ], exports.Chatbot.prototype, "_skillMenuOpened", void 0);
7977
+ __decorate([
7978
+ r()
7979
+ ], exports.Chatbot.prototype, "_skillQuery", void 0);
7980
+ __decorate([
7981
+ r()
7982
+ ], exports.Chatbot.prototype, "_skillHighlighted", void 0);
7983
+ __decorate([
7984
+ r()
7985
+ ], exports.Chatbot.prototype, "_copiedIndex", void 0);
7986
+ __decorate([
7987
+ r()
7988
+ ], exports.Chatbot.prototype, "_skillBrowseOpened", void 0);
7989
+ __decorate([
7990
+ r()
7991
+ ], exports.Chatbot.prototype, "_skillBrowseSource", void 0);
7992
+ __decorate([
7993
+ r()
7994
+ ], exports.Chatbot.prototype, "_skillBrowseQuery", void 0);
7424
7995
  __decorate([
7425
7996
  e$3('.panel')
7426
7997
  ], exports.Chatbot.prototype, "_panelEl", void 0);
@@ -7433,6 +8004,12 @@
7433
8004
  __decorate([
7434
8005
  e$3('.file-input')
7435
8006
  ], exports.Chatbot.prototype, "_fileInputEl", void 0);
8007
+ __decorate([
8008
+ e$3('.input-wrapper')
8009
+ ], exports.Chatbot.prototype, "_inputWrapperEl", void 0);
8010
+ __decorate([
8011
+ e$3('.skill-menu')
8012
+ ], exports.Chatbot.prototype, "_skillMenuEl", void 0);
7436
8013
  exports.Chatbot = __decorate([
7437
8014
  t$2('kr-chatbot')
7438
8015
  ], exports.Chatbot);