@menuia/core 1.0.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/dist/index.d.mts +92 -0
- package/dist/index.d.ts +92 -0
- package/dist/index.js +332 -0
- package/dist/index.mjs +305 -0
- package/package.json +19 -0
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
interface MenuIAConfig {
|
|
2
|
+
agentId: string;
|
|
3
|
+
apiUrl?: string;
|
|
4
|
+
sessionId?: string;
|
|
5
|
+
customFields?: Record<string, string>;
|
|
6
|
+
metadata?: Record<string, unknown>;
|
|
7
|
+
channel?: 'WIDGET' | 'API';
|
|
8
|
+
onMessage?: (message: ChatMessage) => void;
|
|
9
|
+
onStatusChange?: (status: ConnectionStatus) => void;
|
|
10
|
+
onTicketCreated?: (ticketId: string) => void;
|
|
11
|
+
onError?: (error: Error) => void;
|
|
12
|
+
}
|
|
13
|
+
type ConnectionStatus = 'disconnected' | 'connecting' | 'connected' | 'error';
|
|
14
|
+
interface ChatMessage {
|
|
15
|
+
id: string;
|
|
16
|
+
role: 'user' | 'assistant';
|
|
17
|
+
content: string;
|
|
18
|
+
createdAt: string;
|
|
19
|
+
mediaUrl?: string;
|
|
20
|
+
mediaType?: string;
|
|
21
|
+
toolCalls?: string;
|
|
22
|
+
toolResults?: string;
|
|
23
|
+
authorName?: string;
|
|
24
|
+
}
|
|
25
|
+
interface SendMessageOptions {
|
|
26
|
+
content: string;
|
|
27
|
+
attachments?: MessageAttachment[];
|
|
28
|
+
}
|
|
29
|
+
interface MessageAttachment {
|
|
30
|
+
type: 'image' | 'audio' | 'document';
|
|
31
|
+
data: string;
|
|
32
|
+
mimeType: string;
|
|
33
|
+
name?: string;
|
|
34
|
+
}
|
|
35
|
+
interface ChatResponse {
|
|
36
|
+
success: boolean;
|
|
37
|
+
data: {
|
|
38
|
+
conversationId: string;
|
|
39
|
+
message: string;
|
|
40
|
+
toolsUsed: string[];
|
|
41
|
+
tokensUsed: number;
|
|
42
|
+
ticketId?: string;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
interface StreamEvent {
|
|
46
|
+
type: 'token' | 'tool_start' | 'tool_end' | 'done' | 'error' | 'response';
|
|
47
|
+
data: unknown;
|
|
48
|
+
}
|
|
49
|
+
interface WidgetSession {
|
|
50
|
+
sessionId: string;
|
|
51
|
+
conversationId?: string;
|
|
52
|
+
ticketId?: string;
|
|
53
|
+
messages: ChatMessage[];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
declare class MenuIAClient {
|
|
57
|
+
private config;
|
|
58
|
+
private session;
|
|
59
|
+
private status;
|
|
60
|
+
private pollInterval;
|
|
61
|
+
private lastMessageCount;
|
|
62
|
+
constructor(config: MenuIAConfig);
|
|
63
|
+
/** Send a message to the AI agent */
|
|
64
|
+
sendMessage(options: SendMessageOptions): Promise<ChatResponse>;
|
|
65
|
+
/** Send a message and receive streaming response */
|
|
66
|
+
sendMessageStream(options: SendMessageOptions, onToken: (token: string, fullText: string) => void): Promise<ChatResponse | null>;
|
|
67
|
+
/** Load conversation history from server */
|
|
68
|
+
loadHistory(): Promise<ChatMessage[]>;
|
|
69
|
+
/** Start polling for new messages (human agent replies) */
|
|
70
|
+
startPolling(intervalMs?: number): void;
|
|
71
|
+
/** Stop polling */
|
|
72
|
+
stopPolling(): void;
|
|
73
|
+
/** Get current messages */
|
|
74
|
+
getMessages(): ChatMessage[];
|
|
75
|
+
/** Get connection status */
|
|
76
|
+
getStatus(): ConnectionStatus;
|
|
77
|
+
/** Get session info */
|
|
78
|
+
getSession(): WidgetSession;
|
|
79
|
+
/** Update custom fields (sent with next message) */
|
|
80
|
+
setCustomFields(fields: Record<string, string>): void;
|
|
81
|
+
/** Clear session and start fresh */
|
|
82
|
+
clearSession(): void;
|
|
83
|
+
/** Cleanup - call when component unmounts */
|
|
84
|
+
destroy(): void;
|
|
85
|
+
private setStatus;
|
|
86
|
+
private getSessionKey;
|
|
87
|
+
private loadSession;
|
|
88
|
+
private createNewSession;
|
|
89
|
+
private saveSession;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export { type ChatMessage, type ChatResponse, type ConnectionStatus, MenuIAClient, type MenuIAConfig, type MessageAttachment, type SendMessageOptions, type StreamEvent, type WidgetSession };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
interface MenuIAConfig {
|
|
2
|
+
agentId: string;
|
|
3
|
+
apiUrl?: string;
|
|
4
|
+
sessionId?: string;
|
|
5
|
+
customFields?: Record<string, string>;
|
|
6
|
+
metadata?: Record<string, unknown>;
|
|
7
|
+
channel?: 'WIDGET' | 'API';
|
|
8
|
+
onMessage?: (message: ChatMessage) => void;
|
|
9
|
+
onStatusChange?: (status: ConnectionStatus) => void;
|
|
10
|
+
onTicketCreated?: (ticketId: string) => void;
|
|
11
|
+
onError?: (error: Error) => void;
|
|
12
|
+
}
|
|
13
|
+
type ConnectionStatus = 'disconnected' | 'connecting' | 'connected' | 'error';
|
|
14
|
+
interface ChatMessage {
|
|
15
|
+
id: string;
|
|
16
|
+
role: 'user' | 'assistant';
|
|
17
|
+
content: string;
|
|
18
|
+
createdAt: string;
|
|
19
|
+
mediaUrl?: string;
|
|
20
|
+
mediaType?: string;
|
|
21
|
+
toolCalls?: string;
|
|
22
|
+
toolResults?: string;
|
|
23
|
+
authorName?: string;
|
|
24
|
+
}
|
|
25
|
+
interface SendMessageOptions {
|
|
26
|
+
content: string;
|
|
27
|
+
attachments?: MessageAttachment[];
|
|
28
|
+
}
|
|
29
|
+
interface MessageAttachment {
|
|
30
|
+
type: 'image' | 'audio' | 'document';
|
|
31
|
+
data: string;
|
|
32
|
+
mimeType: string;
|
|
33
|
+
name?: string;
|
|
34
|
+
}
|
|
35
|
+
interface ChatResponse {
|
|
36
|
+
success: boolean;
|
|
37
|
+
data: {
|
|
38
|
+
conversationId: string;
|
|
39
|
+
message: string;
|
|
40
|
+
toolsUsed: string[];
|
|
41
|
+
tokensUsed: number;
|
|
42
|
+
ticketId?: string;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
interface StreamEvent {
|
|
46
|
+
type: 'token' | 'tool_start' | 'tool_end' | 'done' | 'error' | 'response';
|
|
47
|
+
data: unknown;
|
|
48
|
+
}
|
|
49
|
+
interface WidgetSession {
|
|
50
|
+
sessionId: string;
|
|
51
|
+
conversationId?: string;
|
|
52
|
+
ticketId?: string;
|
|
53
|
+
messages: ChatMessage[];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
declare class MenuIAClient {
|
|
57
|
+
private config;
|
|
58
|
+
private session;
|
|
59
|
+
private status;
|
|
60
|
+
private pollInterval;
|
|
61
|
+
private lastMessageCount;
|
|
62
|
+
constructor(config: MenuIAConfig);
|
|
63
|
+
/** Send a message to the AI agent */
|
|
64
|
+
sendMessage(options: SendMessageOptions): Promise<ChatResponse>;
|
|
65
|
+
/** Send a message and receive streaming response */
|
|
66
|
+
sendMessageStream(options: SendMessageOptions, onToken: (token: string, fullText: string) => void): Promise<ChatResponse | null>;
|
|
67
|
+
/** Load conversation history from server */
|
|
68
|
+
loadHistory(): Promise<ChatMessage[]>;
|
|
69
|
+
/** Start polling for new messages (human agent replies) */
|
|
70
|
+
startPolling(intervalMs?: number): void;
|
|
71
|
+
/** Stop polling */
|
|
72
|
+
stopPolling(): void;
|
|
73
|
+
/** Get current messages */
|
|
74
|
+
getMessages(): ChatMessage[];
|
|
75
|
+
/** Get connection status */
|
|
76
|
+
getStatus(): ConnectionStatus;
|
|
77
|
+
/** Get session info */
|
|
78
|
+
getSession(): WidgetSession;
|
|
79
|
+
/** Update custom fields (sent with next message) */
|
|
80
|
+
setCustomFields(fields: Record<string, string>): void;
|
|
81
|
+
/** Clear session and start fresh */
|
|
82
|
+
clearSession(): void;
|
|
83
|
+
/** Cleanup - call when component unmounts */
|
|
84
|
+
destroy(): void;
|
|
85
|
+
private setStatus;
|
|
86
|
+
private getSessionKey;
|
|
87
|
+
private loadSession;
|
|
88
|
+
private createNewSession;
|
|
89
|
+
private saveSession;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export { type ChatMessage, type ChatResponse, type ConnectionStatus, MenuIAClient, type MenuIAConfig, type MessageAttachment, type SendMessageOptions, type StreamEvent, type WidgetSession };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
MenuIAClient: () => MenuIAClient
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(index_exports);
|
|
26
|
+
|
|
27
|
+
// src/client.ts
|
|
28
|
+
var DEFAULT_API_URL = "https://api-ia.menuia.com/api/v1";
|
|
29
|
+
var SESSION_KEY_PREFIX = "menuia_session_";
|
|
30
|
+
var MenuIAClient = class {
|
|
31
|
+
constructor(config) {
|
|
32
|
+
this.status = "disconnected";
|
|
33
|
+
this.pollInterval = null;
|
|
34
|
+
this.lastMessageCount = 0;
|
|
35
|
+
this.config = {
|
|
36
|
+
...config,
|
|
37
|
+
apiUrl: config.apiUrl || DEFAULT_API_URL,
|
|
38
|
+
channel: config.channel || "WIDGET"
|
|
39
|
+
};
|
|
40
|
+
this.session = this.loadSession();
|
|
41
|
+
}
|
|
42
|
+
// =====================
|
|
43
|
+
// PUBLIC API
|
|
44
|
+
// =====================
|
|
45
|
+
/** Send a message to the AI agent */
|
|
46
|
+
async sendMessage(options) {
|
|
47
|
+
this.setStatus("connecting");
|
|
48
|
+
try {
|
|
49
|
+
const body = {
|
|
50
|
+
agentId: this.config.agentId,
|
|
51
|
+
message: options.content,
|
|
52
|
+
channel: this.config.channel,
|
|
53
|
+
sessionId: this.session.sessionId,
|
|
54
|
+
metadata: {
|
|
55
|
+
...this.config.metadata,
|
|
56
|
+
widgetId: this.config.agentId,
|
|
57
|
+
customData: this.config.customFields
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
if (this.session.conversationId) {
|
|
61
|
+
body.conversationId = this.session.conversationId;
|
|
62
|
+
}
|
|
63
|
+
if (options.attachments?.length) {
|
|
64
|
+
body.attachments = options.attachments;
|
|
65
|
+
}
|
|
66
|
+
const response = await fetch(`${this.config.apiUrl}/widget/message`, {
|
|
67
|
+
method: "POST",
|
|
68
|
+
headers: { "Content-Type": "application/json" },
|
|
69
|
+
body: JSON.stringify(body)
|
|
70
|
+
});
|
|
71
|
+
if (!response.ok) {
|
|
72
|
+
const err = await response.json().catch(() => ({}));
|
|
73
|
+
throw new Error(err.message || `HTTP ${response.status}`);
|
|
74
|
+
}
|
|
75
|
+
const data = await response.json();
|
|
76
|
+
if (data.data.conversationId) {
|
|
77
|
+
this.session.conversationId = data.data.conversationId;
|
|
78
|
+
}
|
|
79
|
+
if (data.data.ticketId) {
|
|
80
|
+
this.session.ticketId = data.data.ticketId;
|
|
81
|
+
this.config.onTicketCreated?.(data.data.ticketId);
|
|
82
|
+
}
|
|
83
|
+
const userMsg = {
|
|
84
|
+
id: `user_${Date.now()}`,
|
|
85
|
+
role: "user",
|
|
86
|
+
content: options.content,
|
|
87
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
88
|
+
};
|
|
89
|
+
const assistantMsg = {
|
|
90
|
+
id: `assistant_${Date.now()}`,
|
|
91
|
+
role: "assistant",
|
|
92
|
+
content: data.data.message,
|
|
93
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
94
|
+
};
|
|
95
|
+
this.session.messages.push(userMsg, assistantMsg);
|
|
96
|
+
this.saveSession();
|
|
97
|
+
this.setStatus("connected");
|
|
98
|
+
this.config.onMessage?.(assistantMsg);
|
|
99
|
+
return data;
|
|
100
|
+
} catch (error) {
|
|
101
|
+
this.setStatus("error");
|
|
102
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
103
|
+
this.config.onError?.(err);
|
|
104
|
+
throw err;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/** Send a message and receive streaming response */
|
|
108
|
+
async sendMessageStream(options, onToken) {
|
|
109
|
+
this.setStatus("connecting");
|
|
110
|
+
try {
|
|
111
|
+
const body = {
|
|
112
|
+
agentId: this.config.agentId,
|
|
113
|
+
message: options.content,
|
|
114
|
+
channel: this.config.channel,
|
|
115
|
+
sessionId: this.session.sessionId,
|
|
116
|
+
metadata: {
|
|
117
|
+
...this.config.metadata,
|
|
118
|
+
widgetId: this.config.agentId,
|
|
119
|
+
customData: this.config.customFields
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
if (this.session.conversationId) {
|
|
123
|
+
body.conversationId = this.session.conversationId;
|
|
124
|
+
}
|
|
125
|
+
if (options.attachments?.length) {
|
|
126
|
+
body.attachments = options.attachments;
|
|
127
|
+
}
|
|
128
|
+
const response = await fetch(`${this.config.apiUrl}/widget/stream`, {
|
|
129
|
+
method: "POST",
|
|
130
|
+
headers: { "Content-Type": "application/json" },
|
|
131
|
+
body: JSON.stringify(body)
|
|
132
|
+
});
|
|
133
|
+
if (!response.ok) {
|
|
134
|
+
const err = await response.json().catch(() => ({}));
|
|
135
|
+
throw new Error(err.message || `HTTP ${response.status}`);
|
|
136
|
+
}
|
|
137
|
+
const userMsg = {
|
|
138
|
+
id: `user_${Date.now()}`,
|
|
139
|
+
role: "user",
|
|
140
|
+
content: options.content,
|
|
141
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
142
|
+
};
|
|
143
|
+
this.session.messages.push(userMsg);
|
|
144
|
+
this.setStatus("connected");
|
|
145
|
+
const reader = response.body?.getReader();
|
|
146
|
+
if (!reader) throw new Error("Stream not supported");
|
|
147
|
+
const decoder = new TextDecoder();
|
|
148
|
+
let fullText = "";
|
|
149
|
+
let responseData = null;
|
|
150
|
+
while (true) {
|
|
151
|
+
const { done, value } = await reader.read();
|
|
152
|
+
if (done) break;
|
|
153
|
+
const chunk = decoder.decode(value, { stream: true });
|
|
154
|
+
const lines = chunk.split("\n");
|
|
155
|
+
for (const line of lines) {
|
|
156
|
+
if (!line.startsWith("data: ")) continue;
|
|
157
|
+
const jsonStr = line.slice(6).trim();
|
|
158
|
+
if (!jsonStr || jsonStr === "[DONE]") continue;
|
|
159
|
+
try {
|
|
160
|
+
const event = JSON.parse(jsonStr);
|
|
161
|
+
if (event.type === "token") {
|
|
162
|
+
const token = event.data;
|
|
163
|
+
fullText += token;
|
|
164
|
+
onToken(token, fullText);
|
|
165
|
+
} else if (event.type === "response") {
|
|
166
|
+
responseData = { success: true, data: event.data };
|
|
167
|
+
if (responseData.data.conversationId) {
|
|
168
|
+
this.session.conversationId = responseData.data.conversationId;
|
|
169
|
+
}
|
|
170
|
+
if (responseData.data.ticketId) {
|
|
171
|
+
this.session.ticketId = responseData.data.ticketId;
|
|
172
|
+
this.config.onTicketCreated?.(responseData.data.ticketId);
|
|
173
|
+
}
|
|
174
|
+
} else if (event.type === "error") {
|
|
175
|
+
const errData = event.data;
|
|
176
|
+
throw new Error(errData?.message || "Stream error");
|
|
177
|
+
}
|
|
178
|
+
} catch (e) {
|
|
179
|
+
if (e instanceof SyntaxError) continue;
|
|
180
|
+
throw e;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
const finalContent = responseData?.data?.message || fullText;
|
|
185
|
+
const assistantMsg = {
|
|
186
|
+
id: `assistant_${Date.now()}`,
|
|
187
|
+
role: "assistant",
|
|
188
|
+
content: finalContent,
|
|
189
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
190
|
+
};
|
|
191
|
+
this.session.messages.push(assistantMsg);
|
|
192
|
+
this.saveSession();
|
|
193
|
+
this.config.onMessage?.(assistantMsg);
|
|
194
|
+
return responseData;
|
|
195
|
+
} catch (error) {
|
|
196
|
+
this.setStatus("error");
|
|
197
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
198
|
+
this.config.onError?.(err);
|
|
199
|
+
throw err;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
/** Load conversation history from server */
|
|
203
|
+
async loadHistory() {
|
|
204
|
+
if (!this.session.conversationId) return [];
|
|
205
|
+
try {
|
|
206
|
+
const response = await fetch(
|
|
207
|
+
`${this.config.apiUrl}/widget/history/${this.session.sessionId}?agentId=${this.config.agentId}`
|
|
208
|
+
);
|
|
209
|
+
if (!response.ok) return this.session.messages;
|
|
210
|
+
const data = await response.json();
|
|
211
|
+
const messages = (data.messages || []).map((m) => ({
|
|
212
|
+
id: m.id,
|
|
213
|
+
role: m.role,
|
|
214
|
+
content: m.content,
|
|
215
|
+
createdAt: m.createdAt,
|
|
216
|
+
mediaUrl: m.mediaUrl,
|
|
217
|
+
mediaType: m.mediaType,
|
|
218
|
+
authorName: m.authorName
|
|
219
|
+
}));
|
|
220
|
+
this.session.messages = messages;
|
|
221
|
+
this.session.conversationId = data.conversationId || this.session.conversationId;
|
|
222
|
+
this.lastMessageCount = messages.length;
|
|
223
|
+
this.saveSession();
|
|
224
|
+
return messages;
|
|
225
|
+
} catch {
|
|
226
|
+
return this.session.messages;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
/** Start polling for new messages (human agent replies) */
|
|
230
|
+
startPolling(intervalMs = 1e4) {
|
|
231
|
+
this.stopPolling();
|
|
232
|
+
this.pollInterval = setInterval(async () => {
|
|
233
|
+
const messages = await this.loadHistory();
|
|
234
|
+
if (messages.length > this.lastMessageCount) {
|
|
235
|
+
const newMessages = messages.slice(this.lastMessageCount);
|
|
236
|
+
for (const msg of newMessages) {
|
|
237
|
+
if (msg.role === "assistant") {
|
|
238
|
+
this.config.onMessage?.(msg);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
this.lastMessageCount = messages.length;
|
|
242
|
+
}
|
|
243
|
+
}, intervalMs);
|
|
244
|
+
}
|
|
245
|
+
/** Stop polling */
|
|
246
|
+
stopPolling() {
|
|
247
|
+
if (this.pollInterval) {
|
|
248
|
+
clearInterval(this.pollInterval);
|
|
249
|
+
this.pollInterval = null;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
/** Get current messages */
|
|
253
|
+
getMessages() {
|
|
254
|
+
return [...this.session.messages];
|
|
255
|
+
}
|
|
256
|
+
/** Get connection status */
|
|
257
|
+
getStatus() {
|
|
258
|
+
return this.status;
|
|
259
|
+
}
|
|
260
|
+
/** Get session info */
|
|
261
|
+
getSession() {
|
|
262
|
+
return { ...this.session };
|
|
263
|
+
}
|
|
264
|
+
/** Update custom fields (sent with next message) */
|
|
265
|
+
setCustomFields(fields) {
|
|
266
|
+
this.config.customFields = { ...this.config.customFields, ...fields };
|
|
267
|
+
}
|
|
268
|
+
/** Clear session and start fresh */
|
|
269
|
+
clearSession() {
|
|
270
|
+
this.session = this.createNewSession();
|
|
271
|
+
this.saveSession();
|
|
272
|
+
this.lastMessageCount = 0;
|
|
273
|
+
}
|
|
274
|
+
/** Cleanup - call when component unmounts */
|
|
275
|
+
destroy() {
|
|
276
|
+
this.stopPolling();
|
|
277
|
+
}
|
|
278
|
+
// =====================
|
|
279
|
+
// PRIVATE
|
|
280
|
+
// =====================
|
|
281
|
+
setStatus(status) {
|
|
282
|
+
if (this.status !== status) {
|
|
283
|
+
this.status = status;
|
|
284
|
+
this.config.onStatusChange?.(status);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
getSessionKey() {
|
|
288
|
+
return `${SESSION_KEY_PREFIX}${this.config.agentId}`;
|
|
289
|
+
}
|
|
290
|
+
loadSession() {
|
|
291
|
+
if (this.config.sessionId) {
|
|
292
|
+
try {
|
|
293
|
+
const stored = localStorage.getItem(this.getSessionKey());
|
|
294
|
+
if (stored) {
|
|
295
|
+
const parsed = JSON.parse(stored);
|
|
296
|
+
if (parsed.sessionId === this.config.sessionId) {
|
|
297
|
+
return parsed;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
} catch {
|
|
301
|
+
}
|
|
302
|
+
return {
|
|
303
|
+
sessionId: this.config.sessionId,
|
|
304
|
+
messages: []
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
try {
|
|
308
|
+
const stored = localStorage.getItem(this.getSessionKey());
|
|
309
|
+
if (stored) {
|
|
310
|
+
return JSON.parse(stored);
|
|
311
|
+
}
|
|
312
|
+
} catch {
|
|
313
|
+
}
|
|
314
|
+
return this.createNewSession();
|
|
315
|
+
}
|
|
316
|
+
createNewSession() {
|
|
317
|
+
return {
|
|
318
|
+
sessionId: `widget_${crypto.randomUUID?.() || Math.random().toString(36).slice(2)}`,
|
|
319
|
+
messages: []
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
saveSession() {
|
|
323
|
+
try {
|
|
324
|
+
localStorage.setItem(this.getSessionKey(), JSON.stringify(this.session));
|
|
325
|
+
} catch {
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
330
|
+
0 && (module.exports = {
|
|
331
|
+
MenuIAClient
|
|
332
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
// src/client.ts
|
|
2
|
+
var DEFAULT_API_URL = "https://api-ia.menuia.com/api/v1";
|
|
3
|
+
var SESSION_KEY_PREFIX = "menuia_session_";
|
|
4
|
+
var MenuIAClient = class {
|
|
5
|
+
constructor(config) {
|
|
6
|
+
this.status = "disconnected";
|
|
7
|
+
this.pollInterval = null;
|
|
8
|
+
this.lastMessageCount = 0;
|
|
9
|
+
this.config = {
|
|
10
|
+
...config,
|
|
11
|
+
apiUrl: config.apiUrl || DEFAULT_API_URL,
|
|
12
|
+
channel: config.channel || "WIDGET"
|
|
13
|
+
};
|
|
14
|
+
this.session = this.loadSession();
|
|
15
|
+
}
|
|
16
|
+
// =====================
|
|
17
|
+
// PUBLIC API
|
|
18
|
+
// =====================
|
|
19
|
+
/** Send a message to the AI agent */
|
|
20
|
+
async sendMessage(options) {
|
|
21
|
+
this.setStatus("connecting");
|
|
22
|
+
try {
|
|
23
|
+
const body = {
|
|
24
|
+
agentId: this.config.agentId,
|
|
25
|
+
message: options.content,
|
|
26
|
+
channel: this.config.channel,
|
|
27
|
+
sessionId: this.session.sessionId,
|
|
28
|
+
metadata: {
|
|
29
|
+
...this.config.metadata,
|
|
30
|
+
widgetId: this.config.agentId,
|
|
31
|
+
customData: this.config.customFields
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
if (this.session.conversationId) {
|
|
35
|
+
body.conversationId = this.session.conversationId;
|
|
36
|
+
}
|
|
37
|
+
if (options.attachments?.length) {
|
|
38
|
+
body.attachments = options.attachments;
|
|
39
|
+
}
|
|
40
|
+
const response = await fetch(`${this.config.apiUrl}/widget/message`, {
|
|
41
|
+
method: "POST",
|
|
42
|
+
headers: { "Content-Type": "application/json" },
|
|
43
|
+
body: JSON.stringify(body)
|
|
44
|
+
});
|
|
45
|
+
if (!response.ok) {
|
|
46
|
+
const err = await response.json().catch(() => ({}));
|
|
47
|
+
throw new Error(err.message || `HTTP ${response.status}`);
|
|
48
|
+
}
|
|
49
|
+
const data = await response.json();
|
|
50
|
+
if (data.data.conversationId) {
|
|
51
|
+
this.session.conversationId = data.data.conversationId;
|
|
52
|
+
}
|
|
53
|
+
if (data.data.ticketId) {
|
|
54
|
+
this.session.ticketId = data.data.ticketId;
|
|
55
|
+
this.config.onTicketCreated?.(data.data.ticketId);
|
|
56
|
+
}
|
|
57
|
+
const userMsg = {
|
|
58
|
+
id: `user_${Date.now()}`,
|
|
59
|
+
role: "user",
|
|
60
|
+
content: options.content,
|
|
61
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
62
|
+
};
|
|
63
|
+
const assistantMsg = {
|
|
64
|
+
id: `assistant_${Date.now()}`,
|
|
65
|
+
role: "assistant",
|
|
66
|
+
content: data.data.message,
|
|
67
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
68
|
+
};
|
|
69
|
+
this.session.messages.push(userMsg, assistantMsg);
|
|
70
|
+
this.saveSession();
|
|
71
|
+
this.setStatus("connected");
|
|
72
|
+
this.config.onMessage?.(assistantMsg);
|
|
73
|
+
return data;
|
|
74
|
+
} catch (error) {
|
|
75
|
+
this.setStatus("error");
|
|
76
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
77
|
+
this.config.onError?.(err);
|
|
78
|
+
throw err;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/** Send a message and receive streaming response */
|
|
82
|
+
async sendMessageStream(options, onToken) {
|
|
83
|
+
this.setStatus("connecting");
|
|
84
|
+
try {
|
|
85
|
+
const body = {
|
|
86
|
+
agentId: this.config.agentId,
|
|
87
|
+
message: options.content,
|
|
88
|
+
channel: this.config.channel,
|
|
89
|
+
sessionId: this.session.sessionId,
|
|
90
|
+
metadata: {
|
|
91
|
+
...this.config.metadata,
|
|
92
|
+
widgetId: this.config.agentId,
|
|
93
|
+
customData: this.config.customFields
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
if (this.session.conversationId) {
|
|
97
|
+
body.conversationId = this.session.conversationId;
|
|
98
|
+
}
|
|
99
|
+
if (options.attachments?.length) {
|
|
100
|
+
body.attachments = options.attachments;
|
|
101
|
+
}
|
|
102
|
+
const response = await fetch(`${this.config.apiUrl}/widget/stream`, {
|
|
103
|
+
method: "POST",
|
|
104
|
+
headers: { "Content-Type": "application/json" },
|
|
105
|
+
body: JSON.stringify(body)
|
|
106
|
+
});
|
|
107
|
+
if (!response.ok) {
|
|
108
|
+
const err = await response.json().catch(() => ({}));
|
|
109
|
+
throw new Error(err.message || `HTTP ${response.status}`);
|
|
110
|
+
}
|
|
111
|
+
const userMsg = {
|
|
112
|
+
id: `user_${Date.now()}`,
|
|
113
|
+
role: "user",
|
|
114
|
+
content: options.content,
|
|
115
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
116
|
+
};
|
|
117
|
+
this.session.messages.push(userMsg);
|
|
118
|
+
this.setStatus("connected");
|
|
119
|
+
const reader = response.body?.getReader();
|
|
120
|
+
if (!reader) throw new Error("Stream not supported");
|
|
121
|
+
const decoder = new TextDecoder();
|
|
122
|
+
let fullText = "";
|
|
123
|
+
let responseData = null;
|
|
124
|
+
while (true) {
|
|
125
|
+
const { done, value } = await reader.read();
|
|
126
|
+
if (done) break;
|
|
127
|
+
const chunk = decoder.decode(value, { stream: true });
|
|
128
|
+
const lines = chunk.split("\n");
|
|
129
|
+
for (const line of lines) {
|
|
130
|
+
if (!line.startsWith("data: ")) continue;
|
|
131
|
+
const jsonStr = line.slice(6).trim();
|
|
132
|
+
if (!jsonStr || jsonStr === "[DONE]") continue;
|
|
133
|
+
try {
|
|
134
|
+
const event = JSON.parse(jsonStr);
|
|
135
|
+
if (event.type === "token") {
|
|
136
|
+
const token = event.data;
|
|
137
|
+
fullText += token;
|
|
138
|
+
onToken(token, fullText);
|
|
139
|
+
} else if (event.type === "response") {
|
|
140
|
+
responseData = { success: true, data: event.data };
|
|
141
|
+
if (responseData.data.conversationId) {
|
|
142
|
+
this.session.conversationId = responseData.data.conversationId;
|
|
143
|
+
}
|
|
144
|
+
if (responseData.data.ticketId) {
|
|
145
|
+
this.session.ticketId = responseData.data.ticketId;
|
|
146
|
+
this.config.onTicketCreated?.(responseData.data.ticketId);
|
|
147
|
+
}
|
|
148
|
+
} else if (event.type === "error") {
|
|
149
|
+
const errData = event.data;
|
|
150
|
+
throw new Error(errData?.message || "Stream error");
|
|
151
|
+
}
|
|
152
|
+
} catch (e) {
|
|
153
|
+
if (e instanceof SyntaxError) continue;
|
|
154
|
+
throw e;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
const finalContent = responseData?.data?.message || fullText;
|
|
159
|
+
const assistantMsg = {
|
|
160
|
+
id: `assistant_${Date.now()}`,
|
|
161
|
+
role: "assistant",
|
|
162
|
+
content: finalContent,
|
|
163
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
164
|
+
};
|
|
165
|
+
this.session.messages.push(assistantMsg);
|
|
166
|
+
this.saveSession();
|
|
167
|
+
this.config.onMessage?.(assistantMsg);
|
|
168
|
+
return responseData;
|
|
169
|
+
} catch (error) {
|
|
170
|
+
this.setStatus("error");
|
|
171
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
172
|
+
this.config.onError?.(err);
|
|
173
|
+
throw err;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
/** Load conversation history from server */
|
|
177
|
+
async loadHistory() {
|
|
178
|
+
if (!this.session.conversationId) return [];
|
|
179
|
+
try {
|
|
180
|
+
const response = await fetch(
|
|
181
|
+
`${this.config.apiUrl}/widget/history/${this.session.sessionId}?agentId=${this.config.agentId}`
|
|
182
|
+
);
|
|
183
|
+
if (!response.ok) return this.session.messages;
|
|
184
|
+
const data = await response.json();
|
|
185
|
+
const messages = (data.messages || []).map((m) => ({
|
|
186
|
+
id: m.id,
|
|
187
|
+
role: m.role,
|
|
188
|
+
content: m.content,
|
|
189
|
+
createdAt: m.createdAt,
|
|
190
|
+
mediaUrl: m.mediaUrl,
|
|
191
|
+
mediaType: m.mediaType,
|
|
192
|
+
authorName: m.authorName
|
|
193
|
+
}));
|
|
194
|
+
this.session.messages = messages;
|
|
195
|
+
this.session.conversationId = data.conversationId || this.session.conversationId;
|
|
196
|
+
this.lastMessageCount = messages.length;
|
|
197
|
+
this.saveSession();
|
|
198
|
+
return messages;
|
|
199
|
+
} catch {
|
|
200
|
+
return this.session.messages;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
/** Start polling for new messages (human agent replies) */
|
|
204
|
+
startPolling(intervalMs = 1e4) {
|
|
205
|
+
this.stopPolling();
|
|
206
|
+
this.pollInterval = setInterval(async () => {
|
|
207
|
+
const messages = await this.loadHistory();
|
|
208
|
+
if (messages.length > this.lastMessageCount) {
|
|
209
|
+
const newMessages = messages.slice(this.lastMessageCount);
|
|
210
|
+
for (const msg of newMessages) {
|
|
211
|
+
if (msg.role === "assistant") {
|
|
212
|
+
this.config.onMessage?.(msg);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
this.lastMessageCount = messages.length;
|
|
216
|
+
}
|
|
217
|
+
}, intervalMs);
|
|
218
|
+
}
|
|
219
|
+
/** Stop polling */
|
|
220
|
+
stopPolling() {
|
|
221
|
+
if (this.pollInterval) {
|
|
222
|
+
clearInterval(this.pollInterval);
|
|
223
|
+
this.pollInterval = null;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
/** Get current messages */
|
|
227
|
+
getMessages() {
|
|
228
|
+
return [...this.session.messages];
|
|
229
|
+
}
|
|
230
|
+
/** Get connection status */
|
|
231
|
+
getStatus() {
|
|
232
|
+
return this.status;
|
|
233
|
+
}
|
|
234
|
+
/** Get session info */
|
|
235
|
+
getSession() {
|
|
236
|
+
return { ...this.session };
|
|
237
|
+
}
|
|
238
|
+
/** Update custom fields (sent with next message) */
|
|
239
|
+
setCustomFields(fields) {
|
|
240
|
+
this.config.customFields = { ...this.config.customFields, ...fields };
|
|
241
|
+
}
|
|
242
|
+
/** Clear session and start fresh */
|
|
243
|
+
clearSession() {
|
|
244
|
+
this.session = this.createNewSession();
|
|
245
|
+
this.saveSession();
|
|
246
|
+
this.lastMessageCount = 0;
|
|
247
|
+
}
|
|
248
|
+
/** Cleanup - call when component unmounts */
|
|
249
|
+
destroy() {
|
|
250
|
+
this.stopPolling();
|
|
251
|
+
}
|
|
252
|
+
// =====================
|
|
253
|
+
// PRIVATE
|
|
254
|
+
// =====================
|
|
255
|
+
setStatus(status) {
|
|
256
|
+
if (this.status !== status) {
|
|
257
|
+
this.status = status;
|
|
258
|
+
this.config.onStatusChange?.(status);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
getSessionKey() {
|
|
262
|
+
return `${SESSION_KEY_PREFIX}${this.config.agentId}`;
|
|
263
|
+
}
|
|
264
|
+
loadSession() {
|
|
265
|
+
if (this.config.sessionId) {
|
|
266
|
+
try {
|
|
267
|
+
const stored = localStorage.getItem(this.getSessionKey());
|
|
268
|
+
if (stored) {
|
|
269
|
+
const parsed = JSON.parse(stored);
|
|
270
|
+
if (parsed.sessionId === this.config.sessionId) {
|
|
271
|
+
return parsed;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
} catch {
|
|
275
|
+
}
|
|
276
|
+
return {
|
|
277
|
+
sessionId: this.config.sessionId,
|
|
278
|
+
messages: []
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
try {
|
|
282
|
+
const stored = localStorage.getItem(this.getSessionKey());
|
|
283
|
+
if (stored) {
|
|
284
|
+
return JSON.parse(stored);
|
|
285
|
+
}
|
|
286
|
+
} catch {
|
|
287
|
+
}
|
|
288
|
+
return this.createNewSession();
|
|
289
|
+
}
|
|
290
|
+
createNewSession() {
|
|
291
|
+
return {
|
|
292
|
+
sessionId: `widget_${crypto.randomUUID?.() || Math.random().toString(36).slice(2)}`,
|
|
293
|
+
messages: []
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
saveSession() {
|
|
297
|
+
try {
|
|
298
|
+
localStorage.setItem(this.getSessionKey(), JSON.stringify(this.session));
|
|
299
|
+
} catch {
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
export {
|
|
304
|
+
MenuIAClient
|
|
305
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@menuia/core",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MenuIA Chat SDK - Core",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.mjs",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"files": ["dist"],
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsup src/index.ts --format cjs,esm --dts --clean",
|
|
11
|
+
"dev": "tsup src/index.ts --format cjs,esm --dts --watch"
|
|
12
|
+
},
|
|
13
|
+
"peerDependencies": {},
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"tsup": "^8.0.0",
|
|
16
|
+
"typescript": "^5.3.0"
|
|
17
|
+
},
|
|
18
|
+
"license": "MIT"
|
|
19
|
+
}
|