@extrachill/chat 0.3.0 → 0.5.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,36 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.5.0] - 2026-03-24
4
+
5
+ ### Added
6
+ - v0.4.0 — metadata, onToolCalls, processingSessionId, request dedup
7
+ - media support — attachments in messages, image/video rendering, file input
8
+ - swap to react-markdown for proper rich content rendering
9
+ - built-in markdown rendering for chat messages
10
+ - remove adapter pattern, speak chat REST API natively
11
+ - implement adapter contract, message model, and component library
12
+
13
+ ### Changed
14
+ - Initial commit
15
+
16
+ ### Fixed
17
+ - scroll within chat container instead of hijacking page scroll
18
+ - extract readable error message from @wordpress/api-fetch error objects
19
+ - use npm run build in prepublishOnly (pnpm not available on server)
20
+
21
+ ## 0.4.0
22
+
23
+ ### Added
24
+ - `onToolCalls` callback on `useChat` — fires after each turn when tool calls are present, enabling consumers to react to tool executions (apply diffs, invalidate caches, update external state)
25
+ - `metadata` option on `useChat` — arbitrary key-value pairs forwarded to the backend with each message for context scoping (e.g. `{ post_id, context: 'editor' }` or `{ selected_pipeline_id }`)
26
+ - `sessionContext` option on `useChat` — filters session listing to only show sessions created in a specific context
27
+ - `processingSessionId` in `UseChatReturn` — tracks which session initiated the current request, preventing stale loading indicators when switching sessions mid-request
28
+ - `context` parameter on `listSessions` API function — optional context filter for session listing
29
+ - `metadata` parameter on `sendMessage` API function — forwarded to backend alongside the message
30
+ - `X-Request-ID` header on send requests — automatic request deduplication via `crypto.randomUUID()` with fallback
31
+ - `headers` field on `FetchOptions` — allows passing custom HTTP headers through the fetch function
32
+ - Session creation guard — prevents concurrent session creation with `isCreatingRef`
33
+
3
34
  ## 0.2.0
4
35
 
5
36
  - **BREAKING:** Remove `ChatAdapter` interface and adapter pattern
package/css/chat.css CHANGED
@@ -249,12 +249,17 @@
249
249
  ============================================ */
250
250
 
251
251
  .ec-chat-input {
252
+ display: flex;
253
+ flex-direction: column;
254
+ border-top: 1px solid var(--ec-chat-border);
255
+ background: var(--ec-chat-input-bg);
256
+ }
257
+
258
+ .ec-chat-input__row {
252
259
  display: flex;
253
260
  align-items: flex-end;
254
261
  gap: 8px;
255
262
  padding: var(--ec-chat-padding);
256
- border-top: 1px solid var(--ec-chat-border);
257
- background: var(--ec-chat-input-bg);
258
263
  }
259
264
 
260
265
  .ec-chat-input__textarea {
@@ -633,3 +638,185 @@
633
638
  background: var(--ec-chat-error-bg);
634
639
  color: var(--ec-chat-error-text);
635
640
  }
641
+
642
+ /* ============================================
643
+ Message Attachments
644
+ ============================================ */
645
+
646
+ .ec-chat-message__attachments {
647
+ margin-top: 8px;
648
+ display: flex;
649
+ flex-direction: column;
650
+ gap: 8px;
651
+ }
652
+
653
+ .ec-chat-message__images {
654
+ display: flex;
655
+ flex-wrap: wrap;
656
+ gap: 6px;
657
+ }
658
+
659
+ .ec-chat-message__image-link {
660
+ display: block;
661
+ border-radius: 8px;
662
+ overflow: hidden;
663
+ line-height: 0;
664
+ }
665
+
666
+ .ec-chat-message__image {
667
+ max-width: 280px;
668
+ max-height: 280px;
669
+ border-radius: 8px;
670
+ object-fit: cover;
671
+ cursor: pointer;
672
+ transition: opacity 0.15s;
673
+ }
674
+
675
+ .ec-chat-message__image:hover {
676
+ opacity: 0.9;
677
+ }
678
+
679
+ .ec-chat-message__video {
680
+ max-width: 100%;
681
+ border-radius: 8px;
682
+ }
683
+
684
+ .ec-chat-message__file {
685
+ display: inline-flex;
686
+ align-items: center;
687
+ gap: 6px;
688
+ padding: 8px 12px;
689
+ border: 1px solid var(--ec-chat-border);
690
+ border-radius: 8px;
691
+ text-decoration: none;
692
+ color: var(--ec-chat-text);
693
+ font-size: 13px;
694
+ transition: background 0.15s;
695
+ }
696
+
697
+ .ec-chat-message__file:hover {
698
+ background: var(--ec-chat-assistant-bg);
699
+ }
700
+
701
+ .ec-chat-message__file-icon {
702
+ flex-shrink: 0;
703
+ color: var(--ec-chat-text-muted);
704
+ }
705
+
706
+ .ec-chat-message__file-name {
707
+ overflow: hidden;
708
+ text-overflow: ellipsis;
709
+ white-space: nowrap;
710
+ max-width: 200px;
711
+ }
712
+
713
+ .ec-chat-message__file-size {
714
+ color: var(--ec-chat-text-muted);
715
+ white-space: nowrap;
716
+ }
717
+
718
+ /* ============================================
719
+ Input Attachments & Drag-Drop
720
+ ============================================ */
721
+
722
+ .ec-chat-input--dragging {
723
+ outline: 2px dashed var(--ec-chat-input-focus-border);
724
+ outline-offset: -2px;
725
+ border-radius: var(--ec-chat-border-radius);
726
+ }
727
+
728
+ .ec-chat-input__attachments {
729
+ display: flex;
730
+ flex-wrap: wrap;
731
+ gap: 6px;
732
+ padding: 8px var(--ec-chat-padding) 0;
733
+ }
734
+
735
+ .ec-chat-input__row {
736
+ display: flex;
737
+ align-items: flex-end;
738
+ gap: 4px;
739
+ padding: 0 var(--ec-chat-padding) var(--ec-chat-padding);
740
+ }
741
+
742
+ .ec-chat-input__file-input {
743
+ position: absolute;
744
+ width: 1px;
745
+ height: 1px;
746
+ overflow: hidden;
747
+ clip: rect(0, 0, 0, 0);
748
+ border: 0;
749
+ }
750
+
751
+ .ec-chat-input__attach {
752
+ display: flex;
753
+ align-items: center;
754
+ justify-content: center;
755
+ width: 36px;
756
+ height: 36px;
757
+ flex-shrink: 0;
758
+ border: none;
759
+ background: none;
760
+ color: var(--ec-chat-text-muted);
761
+ cursor: pointer;
762
+ border-radius: 8px;
763
+ transition: color 0.15s, background 0.15s;
764
+ }
765
+
766
+ .ec-chat-input__attach:hover {
767
+ color: var(--ec-chat-text);
768
+ background: var(--ec-chat-assistant-bg);
769
+ }
770
+
771
+ .ec-chat-input__attach:disabled {
772
+ opacity: var(--ec-chat-send-disabled-opacity);
773
+ cursor: not-allowed;
774
+ }
775
+
776
+ .ec-chat-input__attach-icon {
777
+ display: block;
778
+ }
779
+
780
+ .ec-chat-input__preview {
781
+ position: relative;
782
+ display: inline-flex;
783
+ align-items: center;
784
+ gap: 4px;
785
+ padding: 4px 8px;
786
+ background: var(--ec-chat-assistant-bg);
787
+ border-radius: 6px;
788
+ font-size: 12px;
789
+ color: var(--ec-chat-text);
790
+ }
791
+
792
+ .ec-chat-input__preview-image {
793
+ width: 48px;
794
+ height: 48px;
795
+ border-radius: 4px;
796
+ object-fit: cover;
797
+ }
798
+
799
+ .ec-chat-input__preview-name {
800
+ max-width: 120px;
801
+ overflow: hidden;
802
+ text-overflow: ellipsis;
803
+ white-space: nowrap;
804
+ }
805
+
806
+ .ec-chat-input__preview-remove {
807
+ display: flex;
808
+ align-items: center;
809
+ justify-content: center;
810
+ width: 18px;
811
+ height: 18px;
812
+ border: none;
813
+ background: var(--ec-chat-text-muted);
814
+ color: #fff;
815
+ border-radius: 50%;
816
+ font-size: 12px;
817
+ line-height: 1;
818
+ cursor: pointer;
819
+ position: absolute;
820
+ top: -4px;
821
+ right: -4px;
822
+ }
package/dist/Chat.d.ts CHANGED
@@ -45,6 +45,10 @@ export interface ChatProps {
45
45
  showSessions?: boolean;
46
46
  /** Label shown during multi-turn processing. */
47
47
  processingLabel?: (turnCount: number) => string;
48
+ /** Whether to show the attachment button in the input. Defaults to true. */
49
+ allowAttachments?: boolean;
50
+ /** Accepted file types for attachments. Defaults to 'image/*,video/*'. */
51
+ acceptFileTypes?: string;
48
52
  }
49
53
  /**
50
54
  * Ready-to-use chat component.
@@ -69,5 +73,5 @@ export interface ChatProps {
69
73
  * }
70
74
  * ```
71
75
  */
72
- export declare function Chat({ basePath, fetchFn, agentId, contentFormat, renderContent, showTools, toolNames, placeholder, emptyState, initialMessages, initialSessionId, maxContinueTurns, onError, onMessage, className, showSessions, processingLabel, }: ChatProps): import("react/jsx-runtime").JSX.Element;
76
+ export declare function Chat({ basePath, fetchFn, agentId, contentFormat, renderContent, showTools, toolNames, placeholder, emptyState, initialMessages, initialSessionId, maxContinueTurns, onError, onMessage, className, showSessions, processingLabel, allowAttachments, acceptFileTypes, }: ChatProps): import("react/jsx-runtime").JSX.Element;
73
77
  //# sourceMappingURL=Chat.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Chat.d.ts","sourceRoot":"","sources":["../src/Chat.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,WAAW,IAAI,eAAe,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACtF,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AACxC,OAAO,EAAW,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAQlE,MAAM,WAAW,SAAS;IACzB;;;OAGG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,OAAO,EAAE,OAAO,CAAC;IACjB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,oEAAoE;IACpE,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,4CAA4C;IAC5C,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,CAAC,MAAM,CAAC,KAAK,SAAS,CAAC;IAC9E,sEAAsE;IACtE,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,6DAA6D;IAC7D,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,sCAAsC;IACtC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gDAAgD;IAChD,UAAU,CAAC,EAAE,SAAS,CAAC;IACvB,+CAA+C;IAC/C,eAAe,CAAC,EAAE,eAAe,EAAE,CAAC;IACpC,0BAA0B;IAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,kCAAkC;IAClC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,mCAAmC;IACnC,OAAO,CAAC,EAAE,cAAc,CAAC,SAAS,CAAC,CAAC;IACpC,0CAA0C;IAC1C,SAAS,CAAC,EAAE,cAAc,CAAC,WAAW,CAAC,CAAC;IACxC,qDAAqD;IACrD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8DAA8D;IAC9D,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,gDAAgD;IAChD,eAAe,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,CAAC;CAChD;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,IAAI,CAAC,EACpB,QAAQ,EACR,OAAO,EACP,OAAO,EACP,aAA0B,EAC1B,aAAa,EACb,SAAgB,EAChB,SAAS,EACT,WAAW,EACX,UAAU,EACV,eAAe,EACf,gBAAgB,EAChB,gBAAgB,EAChB,OAAO,EACP,SAAS,EACT,SAAS,EACT,YAAmB,EACnB,eAAe,GACf,EAAE,SAAS,2CA2DX"}
1
+ {"version":3,"file":"Chat.d.ts","sourceRoot":"","sources":["../src/Chat.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,WAAW,IAAI,eAAe,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACtF,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AACxC,OAAO,EAAW,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAQlE,MAAM,WAAW,SAAS;IACzB;;;OAGG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,OAAO,EAAE,OAAO,CAAC;IACjB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,oEAAoE;IACpE,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,4CAA4C;IAC5C,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,CAAC,MAAM,CAAC,KAAK,SAAS,CAAC;IAC9E,sEAAsE;IACtE,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,6DAA6D;IAC7D,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,sCAAsC;IACtC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gDAAgD;IAChD,UAAU,CAAC,EAAE,SAAS,CAAC;IACvB,+CAA+C;IAC/C,eAAe,CAAC,EAAE,eAAe,EAAE,CAAC;IACpC,0BAA0B;IAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,kCAAkC;IAClC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,mCAAmC;IACnC,OAAO,CAAC,EAAE,cAAc,CAAC,SAAS,CAAC,CAAC;IACpC,0CAA0C;IAC1C,SAAS,CAAC,EAAE,cAAc,CAAC,WAAW,CAAC,CAAC;IACxC,qDAAqD;IACrD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8DAA8D;IAC9D,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,gDAAgD;IAChD,eAAe,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,CAAC;IAChD,4EAA4E;IAC5E,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,0EAA0E;IAC1E,eAAe,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,IAAI,CAAC,EACpB,QAAQ,EACR,OAAO,EACP,OAAO,EACP,aAA0B,EAC1B,aAAa,EACb,SAAgB,EAChB,SAAS,EACT,WAAW,EACX,UAAU,EACV,eAAe,EACf,gBAAgB,EAChB,gBAAgB,EAChB,OAAO,EACP,SAAS,EACT,SAAS,EACT,YAAmB,EACnB,eAAe,EACf,gBAAuB,EACvB,eAAe,GACf,EAAE,SAAS,2CA6DX"}
package/dist/Chat.js CHANGED
@@ -29,7 +29,7 @@ import { SessionSwitcher } from "./components/SessionSwitcher.js";
29
29
  * }
30
30
  * ```
31
31
  */
32
- export function Chat({ basePath, fetchFn, agentId, contentFormat = 'markdown', renderContent, showTools = true, toolNames, placeholder, emptyState, initialMessages, initialSessionId, maxContinueTurns, onError, onMessage, className, showSessions = true, processingLabel, }) {
32
+ export function Chat({ basePath, fetchFn, agentId, contentFormat = 'markdown', renderContent, showTools = true, toolNames, placeholder, emptyState, initialMessages, initialSessionId, maxContinueTurns, onError, onMessage, className, showSessions = true, processingLabel, allowAttachments = true, acceptFileTypes, }) {
33
33
  const chat = useChat({
34
34
  basePath,
35
35
  fetchFn,
@@ -46,5 +46,5 @@ export function Chat({ basePath, fetchFn, agentId, contentFormat = 'markdown', r
46
46
  ? (processingLabel
47
47
  ? processingLabel(chat.turnCount)
48
48
  : `Processing turn ${chat.turnCount}...`)
49
- : undefined }), _jsx(ChatInput, { onSend: chat.sendMessage, disabled: chat.isLoading, placeholder: placeholder })] }) }) }));
49
+ : undefined }), _jsx(ChatInput, { onSend: chat.sendMessage, disabled: chat.isLoading, placeholder: placeholder, allowAttachments: allowAttachments, accept: acceptFileTypes })] }) }) }));
50
50
  }
package/dist/api.d.ts CHANGED
@@ -21,7 +21,12 @@ import type { ChatSession } from './types/session.ts';
21
21
  export interface FetchOptions {
22
22
  path: string;
23
23
  method?: string;
24
+ /** JSON body (mutually exclusive with formData). */
24
25
  data?: Record<string, unknown>;
26
+ /** FormData body for file uploads (mutually exclusive with data). */
27
+ formData?: FormData;
28
+ /** Additional HTTP headers. */
29
+ headers?: Record<string, string>;
25
30
  }
26
31
  export type FetchFn = (options: FetchOptions) => Promise<unknown>;
27
32
  export interface ChatApiConfig {
@@ -45,18 +50,39 @@ export interface ContinueResult {
45
50
  turnNumber: number;
46
51
  maxTurnsReached: boolean;
47
52
  }
53
+ /**
54
+ * Attachment metadata to send with a message.
55
+ */
56
+ export interface SendAttachment {
57
+ url?: string;
58
+ media_id?: number;
59
+ mime_type?: string;
60
+ filename?: string;
61
+ }
48
62
  /**
49
63
  * Send a user message (create or continue a session).
64
+ *
65
+ * When attachments are provided, they are included in the JSON body
66
+ * as structured metadata (not as file uploads — files should already
67
+ * be in the WordPress media library or accessible by URL).
68
+ *
69
+ * @param metadata - Arbitrary key-value pairs forwarded to the backend
70
+ * alongside the message (e.g. `{ selected_pipeline_id: 42 }` or
71
+ * `{ post_id: 100, context: 'editor' }`). The backend can use these
72
+ * to scope the AI's behavior. Not persisted as message content.
50
73
  */
51
- export declare function sendMessage(config: ChatApiConfig, content: string, sessionId?: string): Promise<SendResult>;
74
+ export declare function sendMessage(config: ChatApiConfig, content: string, sessionId?: string, attachments?: SendAttachment[], metadata?: Record<string, unknown>): Promise<SendResult>;
52
75
  /**
53
76
  * Continue a multi-turn response.
54
77
  */
55
78
  export declare function continueResponse(config: ChatApiConfig, sessionId: string): Promise<ContinueResult>;
56
79
  /**
57
80
  * List sessions for the current user.
81
+ *
82
+ * @param context - Optional context filter (e.g. 'chat', 'editor', 'pipeline').
83
+ * Only sessions created in the matching context are returned.
58
84
  */
59
- export declare function listSessions(config: ChatApiConfig, limit?: number): Promise<ChatSession[]>;
85
+ export declare function listSessions(config: ChatApiConfig, limit?: number, context?: string): Promise<ChatSession[]>;
60
86
  /**
61
87
  * Load a single session's conversation.
62
88
  */
package/dist/api.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAUtD;;;;;;;;GAQG;AACH,MAAM,WAAW,YAAY;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/B;AAED,MAAM,MAAM,OAAO,GAAG,CAAC,OAAO,EAAE,YAAY,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;AAElE,MAAM,WAAW,aAAa;IAC7B,sEAAsE;IACtE,QAAQ,EAAE,MAAM,CAAC;IACjB,8CAA8C;IAC9C,OAAO,EAAE,OAAO,CAAC;IACjB,qCAAqC;IACrC,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,UAAU;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,cAAc;IAC9B,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,OAAO,CAAC;CACzB;AAED;;GAEG;AACH,wBAAsB,WAAW,CAChC,MAAM,EAAE,aAAa,EACrB,OAAO,EAAE,MAAM,EACf,SAAS,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC,UAAU,CAAC,CAsBrB;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CACrC,MAAM,EAAE,aAAa,EACrB,SAAS,EAAE,MAAM,GACf,OAAO,CAAC,cAAc,CAAC,CAiBzB;AAED;;GAEG;AACH,wBAAsB,YAAY,CACjC,MAAM,EAAE,aAAa,EACrB,KAAK,SAAK,GACR,OAAO,CAAC,WAAW,EAAE,CAAC,CAaxB;AAED;;GAEG;AACH,wBAAsB,WAAW,CAChC,MAAM,EAAE,aAAa,EACrB,SAAS,EAAE,MAAM,GACf,OAAO,CAAC,WAAW,EAAE,CAAC,CAUxB;AAED;;GAEG;AACH,wBAAsB,aAAa,CAClC,MAAM,EAAE,aAAa,EACrB,SAAS,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC,CASf"}
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAUtD;;;;;;;;GAQG;AACH,MAAM,WAAW,YAAY;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oDAAoD;IACpD,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,qEAAqE;IACrE,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,+BAA+B;IAC/B,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC;AAED,MAAM,MAAM,OAAO,GAAG,CAAC,OAAO,EAAE,YAAY,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;AAElE,MAAM,WAAW,aAAa;IAC7B,sEAAsE;IACtE,QAAQ,EAAE,MAAM,CAAC;IACjB,8CAA8C;IAC9C,OAAO,EAAE,OAAO,CAAC;IACjB,qCAAqC;IACrC,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,UAAU;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,cAAc;IAC9B,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,OAAO,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC9B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,WAAW,CAChC,MAAM,EAAE,aAAa,EACrB,OAAO,EAAE,MAAM,EACf,SAAS,CAAC,EAAE,MAAM,EAClB,WAAW,CAAC,EAAE,cAAc,EAAE,EAC9B,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAChC,OAAO,CAAC,UAAU,CAAC,CA8BrB;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CACrC,MAAM,EAAE,aAAa,EACrB,SAAS,EAAE,MAAM,GACf,OAAO,CAAC,cAAc,CAAC,CAiBzB;AAED;;;;;GAKG;AACH,wBAAsB,YAAY,CACjC,MAAM,EAAE,aAAa,EACrB,KAAK,SAAK,EACV,OAAO,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,WAAW,EAAE,CAAC,CAcxB;AAED;;GAEG;AACH,wBAAsB,WAAW,CAChC,MAAM,EAAE,aAAa,EACrB,SAAS,EAAE,MAAM,GACf,OAAO,CAAC,WAAW,EAAE,CAAC,CAUxB;AAED;;GAEG;AACH,wBAAsB,aAAa,CAClC,MAAM,EAAE,aAAa,EACrB,SAAS,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC,CASf"}
package/dist/api.js CHANGED
@@ -10,17 +10,35 @@
10
10
  import { normalizeConversation, normalizeMessage, normalizeSession } from "./normalizer.js";
11
11
  /**
12
12
  * Send a user message (create or continue a session).
13
+ *
14
+ * When attachments are provided, they are included in the JSON body
15
+ * as structured metadata (not as file uploads — files should already
16
+ * be in the WordPress media library or accessible by URL).
17
+ *
18
+ * @param metadata - Arbitrary key-value pairs forwarded to the backend
19
+ * alongside the message (e.g. `{ selected_pipeline_id: 42 }` or
20
+ * `{ post_id: 100, context: 'editor' }`). The backend can use these
21
+ * to scope the AI's behavior. Not persisted as message content.
13
22
  */
14
- export async function sendMessage(config, content, sessionId) {
23
+ export async function sendMessage(config, content, sessionId, attachments, metadata) {
15
24
  const body = { message: content };
16
25
  if (sessionId)
17
26
  body.session_id = sessionId;
18
27
  if (config.agentId)
19
28
  body.agent_id = config.agentId;
29
+ if (attachments?.length)
30
+ body.attachments = attachments;
31
+ if (metadata)
32
+ Object.assign(body, metadata);
33
+ // Generate a unique request ID for idempotent request handling.
34
+ const requestId = typeof crypto !== 'undefined' && crypto.randomUUID
35
+ ? crypto.randomUUID()
36
+ : `req_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
20
37
  const raw = await config.fetchFn({
21
38
  path: config.basePath,
22
39
  method: 'POST',
23
40
  data: body,
41
+ headers: { 'X-Request-ID': requestId },
24
42
  });
25
43
  if (!raw.success) {
26
44
  throw new Error(raw.message ?? 'Failed to send message');
@@ -54,11 +72,16 @@ export async function continueResponse(config, sessionId) {
54
72
  }
55
73
  /**
56
74
  * List sessions for the current user.
75
+ *
76
+ * @param context - Optional context filter (e.g. 'chat', 'editor', 'pipeline').
77
+ * Only sessions created in the matching context are returned.
57
78
  */
58
- export async function listSessions(config, limit = 20) {
79
+ export async function listSessions(config, limit = 20, context) {
59
80
  const params = new URLSearchParams({ limit: String(limit) });
60
81
  if (config.agentId)
61
82
  params.set('agent_id', String(config.agentId));
83
+ if (context)
84
+ params.set('context', context);
62
85
  const raw = await config.fetchFn({
63
86
  path: `${config.basePath}/sessions?${params.toString()}`,
64
87
  });
@@ -1,21 +1,28 @@
1
1
  export interface ChatInputProps {
2
- /** Called when the user submits a message. */
3
- onSend: (content: string) => void;
2
+ /** Called when the user submits a message (with optional file attachments). */
3
+ onSend: (content: string, files?: File[]) => void;
4
4
  /** Whether input is disabled (e.g. while waiting for response). */
5
5
  disabled?: boolean;
6
6
  /** Placeholder text. Defaults to 'Type a message...'. */
7
7
  placeholder?: string;
8
8
  /** Maximum number of rows the textarea auto-grows to. Defaults to 6. */
9
9
  maxRows?: number;
10
+ /** Accepted file types for the file picker. Defaults to 'image/*,video/*'. */
11
+ accept?: string;
12
+ /** Maximum number of files per message. Defaults to 5. */
13
+ maxFiles?: number;
14
+ /** Whether to show the attachment button. Defaults to true. */
15
+ allowAttachments?: boolean;
10
16
  /** Additional CSS class name. */
11
17
  className?: string;
12
18
  }
13
19
  /**
14
- * Chat input with auto-growing textarea and keyboard shortcuts.
20
+ * Chat input with auto-growing textarea, keyboard shortcuts, and file attachments.
15
21
  *
16
22
  * - Enter sends the message
17
23
  * - Shift+Enter adds a newline
18
24
  * - Textarea auto-grows up to `maxRows`
25
+ * - File attachment via button, drag-and-drop, or clipboard paste
19
26
  */
20
- export declare function ChatInput({ onSend, disabled, placeholder, maxRows, className, }: ChatInputProps): import("react/jsx-runtime").JSX.Element;
27
+ export declare function ChatInput({ onSend, disabled, placeholder, maxRows, accept, maxFiles, allowAttachments, className, }: ChatInputProps): import("react/jsx-runtime").JSX.Element;
21
28
  //# sourceMappingURL=ChatInput.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ChatInput.d.ts","sourceRoot":"","sources":["../../src/components/ChatInput.tsx"],"names":[],"mappings":"AAEA,MAAM,WAAW,cAAc;IAC9B,8CAA8C;IAC9C,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,mEAAmE;IACnE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,yDAAyD;IACzD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,wEAAwE;IACxE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,iCAAiC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,EACzB,MAAM,EACN,QAAgB,EAChB,WAAiC,EACjC,OAAW,EACX,SAAS,GACT,EAAE,cAAc,2CAkEhB"}
1
+ {"version":3,"file":"ChatInput.d.ts","sourceRoot":"","sources":["../../src/components/ChatInput.tsx"],"names":[],"mappings":"AAEA,MAAM,WAAW,cAAc;IAC9B,+EAA+E;IAC/E,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,IAAI,EAAE,KAAK,IAAI,CAAC;IAClD,mEAAmE;IACnE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,yDAAyD;IACzD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,wEAAwE;IACxE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,8EAA8E;IAC9E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,0DAA0D;IAC1D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,+DAA+D;IAC/D,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,iCAAiC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,EACzB,MAAM,EACN,QAAgB,EAChB,WAAiC,EACjC,OAAW,EACX,MAA0B,EAC1B,QAAY,EACZ,gBAAuB,EACvB,SAAS,GACT,EAAE,cAAc,2CA4JhB"}
@@ -1,15 +1,19 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useState, useRef, useCallback } from 'react';
3
3
  /**
4
- * Chat input with auto-growing textarea and keyboard shortcuts.
4
+ * Chat input with auto-growing textarea, keyboard shortcuts, and file attachments.
5
5
  *
6
6
  * - Enter sends the message
7
7
  * - Shift+Enter adds a newline
8
8
  * - Textarea auto-grows up to `maxRows`
9
+ * - File attachment via button, drag-and-drop, or clipboard paste
9
10
  */
10
- export function ChatInput({ onSend, disabled = false, placeholder = 'Type a message...', maxRows = 6, className, }) {
11
+ export function ChatInput({ onSend, disabled = false, placeholder = 'Type a message...', maxRows = 6, accept = 'image/*,video/*', maxFiles = 5, allowAttachments = true, className, }) {
11
12
  const [value, setValue] = useState('');
13
+ const [files, setFiles] = useState([]);
14
+ const [isDragging, setIsDragging] = useState(false);
12
15
  const textareaRef = useRef(null);
16
+ const fileInputRef = useRef(null);
13
17
  const cooldownRef = useRef(false);
14
18
  const resize = useCallback(() => {
15
19
  const el = textareaRef.current;
@@ -20,33 +24,87 @@ export function ChatInput({ onSend, disabled = false, placeholder = 'Type a mess
20
24
  const maxHeight = lineHeight * maxRows;
21
25
  el.style.height = `${Math.min(el.scrollHeight, maxHeight)}px`;
22
26
  }, [maxRows]);
27
+ const addFiles = useCallback((newFiles) => {
28
+ const fileArray = Array.from(newFiles);
29
+ setFiles((prev) => {
30
+ const combined = [...prev, ...fileArray];
31
+ return combined.slice(0, maxFiles);
32
+ });
33
+ }, [maxFiles]);
34
+ const removeFile = useCallback((index) => {
35
+ setFiles((prev) => prev.filter((_, i) => i !== index));
36
+ }, []);
23
37
  const handleSubmit = useCallback((e) => {
24
38
  e?.preventDefault();
25
39
  const trimmed = value.trim();
26
- if (!trimmed || disabled || cooldownRef.current)
40
+ if ((!trimmed && files.length === 0) || disabled || cooldownRef.current)
27
41
  return;
28
- // Debounce to prevent double-submit
29
42
  cooldownRef.current = true;
30
43
  setTimeout(() => { cooldownRef.current = false; }, 300);
31
- onSend(trimmed);
44
+ onSend(trimmed, files.length > 0 ? files : undefined);
32
45
  setValue('');
33
- // Reset textarea height after clearing
46
+ setFiles([]);
34
47
  requestAnimationFrame(() => {
35
48
  const el = textareaRef.current;
36
49
  if (el)
37
50
  el.style.height = 'auto';
38
51
  });
39
- }, [value, disabled, onSend]);
52
+ }, [value, files, disabled, onSend]);
40
53
  const handleKeyDown = useCallback((e) => {
41
54
  if (e.key === 'Enter' && !e.shiftKey) {
42
55
  e.preventDefault();
43
56
  handleSubmit();
44
57
  }
45
58
  }, [handleSubmit]);
59
+ const handleDragOver = useCallback((e) => {
60
+ e.preventDefault();
61
+ if (allowAttachments)
62
+ setIsDragging(true);
63
+ }, [allowAttachments]);
64
+ const handleDragLeave = useCallback((e) => {
65
+ e.preventDefault();
66
+ setIsDragging(false);
67
+ }, []);
68
+ const handleDrop = useCallback((e) => {
69
+ e.preventDefault();
70
+ setIsDragging(false);
71
+ if (!allowAttachments || !e.dataTransfer.files.length)
72
+ return;
73
+ addFiles(e.dataTransfer.files);
74
+ }, [allowAttachments, addFiles]);
75
+ const handlePaste = useCallback((e) => {
76
+ if (!allowAttachments)
77
+ return;
78
+ const pastedFiles = Array.from(e.clipboardData.items)
79
+ .filter((item) => item.kind === 'file')
80
+ .map((item) => item.getAsFile())
81
+ .filter((f) => f !== null);
82
+ if (pastedFiles.length > 0) {
83
+ addFiles(pastedFiles);
84
+ }
85
+ }, [allowAttachments, addFiles]);
46
86
  const baseClass = 'ec-chat-input';
47
- const classes = [baseClass, className].filter(Boolean).join(' ');
48
- return (_jsxs("form", { className: classes, onSubmit: handleSubmit, children: [_jsx("textarea", { ref: textareaRef, className: `${baseClass}__textarea`, value: value, onChange: (e) => { setValue(e.target.value); resize(); }, onKeyDown: handleKeyDown, placeholder: placeholder, disabled: disabled, rows: 1, "aria-label": placeholder }), _jsx("button", { className: `${baseClass}__send`, type: "submit", disabled: disabled || !value.trim(), "aria-label": "Send message", children: _jsx(SendIcon, {}) })] }));
87
+ const classes = [
88
+ baseClass,
89
+ isDragging ? `${baseClass}--dragging` : '',
90
+ className,
91
+ ].filter(Boolean).join(' ');
92
+ return (_jsxs("form", { className: classes, onSubmit: handleSubmit, onDragOver: handleDragOver, onDragLeave: handleDragLeave, onDrop: handleDrop, children: [files.length > 0 && (_jsx("div", { className: `${baseClass}__attachments`, children: files.map((file, i) => (_jsx(FilePreview, { file: file, onRemove: () => removeFile(i) }, `${file.name}-${i}`))) })), _jsxs("div", { className: `${baseClass}__row`, children: [allowAttachments && (_jsxs(_Fragment, { children: [_jsx("input", { ref: fileInputRef, type: "file", accept: accept, multiple: true, className: `${baseClass}__file-input`, onChange: (e) => {
93
+ if (e.target.files)
94
+ addFiles(e.target.files);
95
+ e.target.value = '';
96
+ }, tabIndex: -1, "aria-hidden": "true" }), _jsx("button", { type: "button", className: `${baseClass}__attach`, onClick: () => fileInputRef.current?.click(), disabled: disabled, "aria-label": "Attach file", title: "Attach file", children: _jsx(AttachIcon, {}) })] })), _jsx("textarea", { ref: textareaRef, className: `${baseClass}__textarea`, value: value, onChange: (e) => { setValue(e.target.value); resize(); }, onKeyDown: handleKeyDown, onPaste: handlePaste, placeholder: placeholder, disabled: disabled, rows: 1, "aria-label": placeholder }), _jsx("button", { className: `${baseClass}__send`, type: "submit", disabled: disabled || (!value.trim() && files.length === 0), "aria-label": "Send message", children: _jsx(SendIcon, {}) })] })] }));
97
+ }
98
+ /* ---- File Preview ---- */
99
+ function FilePreview({ file, onRemove }) {
100
+ const isImage = file.type.startsWith('image/');
101
+ const preview = isImage ? URL.createObjectURL(file) : null;
102
+ return (_jsxs("div", { className: "ec-chat-input__preview", children: [preview ? (_jsx("img", { src: preview, alt: file.name, className: "ec-chat-input__preview-image" })) : (_jsx("span", { className: "ec-chat-input__preview-name", children: file.name })), _jsx("button", { type: "button", className: "ec-chat-input__preview-remove", onClick: onRemove, "aria-label": `Remove ${file.name}`, children: "\u00D7" })] }));
49
103
  }
104
+ /* ---- Icons ---- */
50
105
  function SendIcon() {
51
106
  return (_jsxs("svg", { className: "ec-chat-input__send-icon", viewBox: "0 0 24 24", width: "20", height: "20", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx("line", { x1: "22", y1: "2", x2: "11", y2: "13" }), _jsx("polygon", { points: "22 2 15 22 11 13 2 9 22 2" })] }));
52
107
  }
108
+ function AttachIcon() {
109
+ return (_jsx("svg", { className: "ec-chat-input__attach-icon", viewBox: "0 0 24 24", width: "20", height: "20", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: _jsx("path", { d: "M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48" }) }));
110
+ }
@@ -7,7 +7,6 @@ export interface ChatMessageProps {
7
7
  contentFormat?: ContentFormat;
8
8
  /**
9
9
  * Custom content renderer. When provided, overrides contentFormat.
10
- * Use this to plug in your own markdown renderer (react-markdown, etc.).
11
10
  */
12
11
  renderContent?: (content: string, role: ChatMessageType['role']) => ReactNode;
13
12
  /** Additional CSS class name on the outer wrapper. */
@@ -17,7 +16,8 @@ export interface ChatMessageProps {
17
16
  * Renders a single chat message bubble.
18
17
  *
19
18
  * User messages align right, assistant messages align left.
20
- * Content rendering is pluggable via `renderContent` or `contentFormat`.
19
+ * Markdown content is rendered via react-markdown (lazy-loaded).
20
+ * Media attachments render inline below the text content.
21
21
  */
22
22
  export declare function ChatMessage({ message, contentFormat, renderContent, className, }: ChatMessageProps): import("react/jsx-runtime").JSX.Element;
23
23
  //# sourceMappingURL=ChatMessage.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ChatMessage.d.ts","sourceRoot":"","sources":["../../src/components/ChatMessage.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAW,MAAM,OAAO,CAAC;AAChD,OAAO,KAAK,EAAE,WAAW,IAAI,eAAe,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAGvF,MAAM,WAAW,gBAAgB;IAChC,6BAA6B;IAC7B,OAAO,EAAE,eAAe,CAAC;IACzB,6DAA6D;IAC7D,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B;;;OAGG;IACH,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,CAAC,MAAM,CAAC,KAAK,SAAS,CAAC;IAC9E,sDAAsD;IACtD,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,EAC3B,OAAO,EACP,aAA0B,EAC1B,aAAa,EACb,SAAS,GACT,EAAE,gBAAgB,2CAyBlB"}
1
+ {"version":3,"file":"ChatMessage.d.ts","sourceRoot":"","sources":["../../src/components/ChatMessage.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAkB,MAAM,OAAO,CAAC;AACvD,OAAO,KAAK,EAAE,WAAW,IAAI,eAAe,EAAE,aAAa,EAAmB,MAAM,mBAAmB,CAAC;AAKxG,MAAM,WAAW,gBAAgB;IAChC,6BAA6B;IAC7B,OAAO,EAAE,eAAe,CAAC;IACzB,6DAA6D;IAC7D,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B;;OAEG;IACH,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,CAAC,MAAM,CAAC,KAAK,SAAS,CAAC;IAC9E,sDAAsD;IACtD,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,EAC3B,OAAO,EACP,aAA0B,EAC1B,aAAa,EACb,SAAS,GACT,EAAE,gBAAgB,2CAgClB"}