@m6d/cortex-angular 1.7.0 → 1.8.0

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.
@@ -51,6 +51,9 @@ function createCortexApiClient(getTransport) {
51
51
  return text ? JSON.parse(text) : undefined;
52
52
  }
53
53
  return {
54
+ getCapabilities() {
55
+ return send("/capabilities");
56
+ },
54
57
  listThreads() {
55
58
  return send("/threads");
56
59
  },
@@ -1199,6 +1202,9 @@ var CORTEX_CLIENT_CONFIG = new InjectionToken("CORTEX_CLIENT_CONFIG");
1199
1202
  class CortexApiClient {
1200
1203
  config = inject(CORTEX_CLIENT_CONFIG);
1201
1204
  client = createCortexApiClient(() => this.config().transport);
1205
+ getCapabilities() {
1206
+ return this.client.getCapabilities();
1207
+ }
1202
1208
  listThreads() {
1203
1209
  return this.client.listThreads();
1204
1210
  }
@@ -1347,6 +1353,8 @@ class CortexChatService {
1347
1353
  return unansweredToolCalls(chat.messages()).length > 0;
1348
1354
  }, ...ngDevMode ? [{ debugName: "hasPendingToolCalls" }] : []);
1349
1355
  messageMetadata = signal(new Map, ...ngDevMode ? [{ debugName: "messageMetadata" }] : []);
1356
+ attachmentsEnabled = computed(() => this.config().attachments !== false && this.serverAcceptsAttachments(), ...ngDevMode ? [{ debugName: "attachmentsEnabled" }] : []);
1357
+ serverAcceptsAttachments = signal(true, ...ngDevMode ? [{ debugName: "serverAcceptsAttachments" }] : []);
1350
1358
  events = new Subject;
1351
1359
  selectedThreadSnapshot = signal(undefined, ...ngDevMode ? [{ debugName: "selectedThreadSnapshot" }] : []);
1352
1360
  config = inject(CORTEX_CLIENT_CONFIG);
@@ -1369,6 +1377,7 @@ class CortexChatService {
1369
1377
  this.threadService.start();
1370
1378
  this.subscribeToWsEvents();
1371
1379
  this.subscribeToWsConnections();
1380
+ this.api.getCapabilities().then((capabilities) => this.serverAcceptsAttachments.set(capabilities.attachments)).catch(() => {});
1372
1381
  }
1373
1382
  get events$() {
1374
1383
  return this.events.asObservable();
@@ -1807,6 +1816,7 @@ class ChatComposerComponent {
1807
1816
  translateService = inject(TranslateService);
1808
1817
  text = model("", ...ngDevMode ? [{ debugName: "text" }] : []);
1809
1818
  dragging = signal(false, ...ngDevMode ? [{ debugName: "dragging" }] : []);
1819
+ attachmentsEnabled = this.chatService.attachmentsEnabled;
1810
1820
  acceptedTypes = ATTACHMENT_MIME_TYPES.join(",");
1811
1821
  canSend = computed(() => !this.queue.busy() && (Boolean(this.text().trim()) || this.queue.hasReady()), ...ngDevMode ? [{ debugName: "canSend" }] : []);
1812
1822
  messageInput = viewChild("messageInput", ...ngDevMode ? [{ debugName: "messageInput" }] : []);
@@ -1821,7 +1831,8 @@ class ChatComposerComponent {
1821
1831
  }
1822
1832
  onDragOver(event) {
1823
1833
  event.preventDefault();
1824
- this.dragging.set(true);
1834
+ if (this.attachmentsEnabled())
1835
+ this.dragging.set(true);
1825
1836
  }
1826
1837
  onDragLeave(event) {
1827
1838
  const area = event.currentTarget;
@@ -1832,11 +1843,12 @@ class ChatComposerComponent {
1832
1843
  onDrop(event) {
1833
1844
  event.preventDefault();
1834
1845
  this.dragging.set(false);
1835
- this.queue.accept([...event.dataTransfer?.files ?? []]);
1846
+ if (this.attachmentsEnabled())
1847
+ this.queue.accept([...event.dataTransfer?.files ?? []]);
1836
1848
  }
1837
1849
  onPaste(event) {
1838
1850
  const files = event.clipboardData?.files;
1839
- if (!files?.length)
1851
+ if (!this.attachmentsEnabled() || !files?.length)
1840
1852
  return;
1841
1853
  event.preventDefault();
1842
1854
  this.queue.accept([...files]);
@@ -1902,31 +1914,33 @@ class ChatComposerComponent {
1902
1914
  </button>
1903
1915
  } @else {
1904
1916
  <div class="cortex-widget__input-actions">
1905
- <!-- Attach button -->
1906
- <button
1907
- type="button"
1908
- (click)="fileInput.click()"
1909
- class="cortex-widget__attach-btn"
1910
- [attr.aria-label]="'translate_attach_files' | translate"
1911
- >
1912
- <svg width="16" height="16" viewBox="0 0 16 16" fill="none">
1913
- <path
1914
- d="M10.5 5.5 6.2 9.8a1.4 1.4 0 0 0 2 2l4.6-4.6a2.8 2.8 0 0 0-4-4L4.2 7.8a4.2 4.2 0 0 0 6 6l4-4"
1915
- stroke="currentColor"
1916
- stroke-width="1.3"
1917
- stroke-linecap="round"
1918
- stroke-linejoin="round"
1919
- />
1920
- </svg>
1921
- </button>
1922
- <input
1923
- #fileInput
1924
- type="file"
1925
- multiple
1926
- [accept]="acceptedTypes"
1927
- (change)="onFilesPicked($event)"
1928
- class="cortex-widget__file-input"
1929
- />
1917
+ @if (attachmentsEnabled()) {
1918
+ <!-- Attach button -->
1919
+ <button
1920
+ type="button"
1921
+ (click)="fileInput.click()"
1922
+ class="cortex-widget__attach-btn"
1923
+ [attr.aria-label]="'translate_attach_files' | translate"
1924
+ >
1925
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none">
1926
+ <path
1927
+ d="M10.5 5.5 6.2 9.8a1.4 1.4 0 0 0 2 2l4.6-4.6a2.8 2.8 0 0 0-4-4L4.2 7.8a4.2 4.2 0 0 0 6 6l4-4"
1928
+ stroke="currentColor"
1929
+ stroke-width="1.3"
1930
+ stroke-linecap="round"
1931
+ stroke-linejoin="round"
1932
+ />
1933
+ </svg>
1934
+ </button>
1935
+ <input
1936
+ #fileInput
1937
+ type="file"
1938
+ multiple
1939
+ [accept]="acceptedTypes"
1940
+ (change)="onFilesPicked($event)"
1941
+ class="cortex-widget__file-input"
1942
+ />
1943
+ }
1930
1944
 
1931
1945
  <!-- Send button -->
1932
1946
  <button
@@ -2003,31 +2017,33 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.3", ngImpor
2003
2017
  </button>
2004
2018
  } @else {
2005
2019
  <div class="cortex-widget__input-actions">
2006
- <!-- Attach button -->
2007
- <button
2008
- type="button"
2009
- (click)="fileInput.click()"
2010
- class="cortex-widget__attach-btn"
2011
- [attr.aria-label]="'translate_attach_files' | translate"
2012
- >
2013
- <svg width="16" height="16" viewBox="0 0 16 16" fill="none">
2014
- <path
2015
- d="M10.5 5.5 6.2 9.8a1.4 1.4 0 0 0 2 2l4.6-4.6a2.8 2.8 0 0 0-4-4L4.2 7.8a4.2 4.2 0 0 0 6 6l4-4"
2016
- stroke="currentColor"
2017
- stroke-width="1.3"
2018
- stroke-linecap="round"
2019
- stroke-linejoin="round"
2020
- />
2021
- </svg>
2022
- </button>
2023
- <input
2024
- #fileInput
2025
- type="file"
2026
- multiple
2027
- [accept]="acceptedTypes"
2028
- (change)="onFilesPicked($event)"
2029
- class="cortex-widget__file-input"
2030
- />
2020
+ @if (attachmentsEnabled()) {
2021
+ <!-- Attach button -->
2022
+ <button
2023
+ type="button"
2024
+ (click)="fileInput.click()"
2025
+ class="cortex-widget__attach-btn"
2026
+ [attr.aria-label]="'translate_attach_files' | translate"
2027
+ >
2028
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none">
2029
+ <path
2030
+ d="M10.5 5.5 6.2 9.8a1.4 1.4 0 0 0 2 2l4.6-4.6a2.8 2.8 0 0 0-4-4L4.2 7.8a4.2 4.2 0 0 0 6 6l4-4"
2031
+ stroke="currentColor"
2032
+ stroke-width="1.3"
2033
+ stroke-linecap="round"
2034
+ stroke-linejoin="round"
2035
+ />
2036
+ </svg>
2037
+ </button>
2038
+ <input
2039
+ #fileInput
2040
+ type="file"
2041
+ multiple
2042
+ [accept]="acceptedTypes"
2043
+ (change)="onFilesPicked($event)"
2044
+ class="cortex-widget__file-input"
2045
+ />
2046
+ }
2031
2047
 
2032
2048
  <!-- Send button -->
2033
2049
  <button
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@m6d/cortex-angular",
3
- "version": "1.7.0",
3
+ "version": "1.8.0",
4
4
  "description": "Reusable AI agent chat UI library for Angular",
5
5
  "license": "MIT",
6
6
  "sideEffects": false,
@@ -124,6 +124,12 @@ export type CortexConfig = {
124
124
  /** Prompt chips on the blank-chat landing; clicking one sends it as the first message. */
125
125
  suggestions?: Localized<string[]>;
126
126
  showDebugButton?: boolean;
127
+ /**
128
+ * Embedder override for the file-upload controls (attach button, drop,
129
+ * paste). The widget already hides them when the server's capability
130
+ * handshake says the agent refuses uploads; `false` hides them regardless.
131
+ */
132
+ attachments?: boolean;
127
133
  /**
128
134
  * Render embedded tool pages served from this page's own origin with their
129
135
  * full identity — which, same-origin, means no effective sandbox at all: such
@@ -214,6 +220,13 @@ declare class CortexChatService {
214
220
  * the stream, and are merged back in by message id at render time.
215
221
  */
216
222
  readonly messageMetadata: _angular_core.WritableSignal<ReadonlyMap<string, MessageMetadata>>;
223
+ /**
224
+ * Server capability AND the embedder's `config.attachments` override.
225
+ * Optimistically true until the handshake answers; most agents accept files
226
+ * and a briefly hidden button would flicker for them.
227
+ */
228
+ readonly attachmentsEnabled: _angular_core.Signal<boolean>;
229
+ private readonly serverAcceptsAttachments;
217
230
  private readonly events;
218
231
  private readonly selectedThreadSnapshot;
219
232
  private readonly config;