@joai/core 0.2.0 → 0.4.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 +1125 -742
- package/dist/index.mjs +51 -2
- package/dist/react/index.d.mts +103 -0
- package/dist/react/index.mjs +269 -0
- package/dist/types-C4eYYzWF.d.mts +31 -0
- package/package.json +14 -2
package/dist/index.mjs
CHANGED
|
@@ -18,7 +18,32 @@ const getAppBaseUrl = (env) => {
|
|
|
18
18
|
if (env === "testnet") return "https://testnet.joai.ai";
|
|
19
19
|
return "https://joai.ai";
|
|
20
20
|
};
|
|
21
|
+
const getDigestBaseUrl = (env) => {
|
|
22
|
+
if (env === "local") return "http://localhost:3005";
|
|
23
|
+
if (env === "devnet") return "https://devnet-digests.joai.ai";
|
|
24
|
+
if (env === "testnet") return "https://testnet-digests.joai.ai";
|
|
25
|
+
return "https://digests.joai.ai";
|
|
26
|
+
};
|
|
27
|
+
const getMarketplaceBaseUrl = (env) => {
|
|
28
|
+
if (env === "local") return "http://localhost:3006";
|
|
29
|
+
if (env === "devnet") return "https://devnet-marketplace.joai.ai";
|
|
30
|
+
if (env === "testnet") return "https://testnet-marketplace.joai.ai";
|
|
31
|
+
return "https://marketplace.joai.ai";
|
|
32
|
+
};
|
|
33
|
+
const getAgentsBaseUrl = (env) => {
|
|
34
|
+
if (env === "local") return "http://localhost:3007";
|
|
35
|
+
if (env === "devnet") return "https://devnet-agents.joai.ai";
|
|
36
|
+
if (env === "testnet") return "https://testnet-agents.joai.ai";
|
|
37
|
+
return "https://agents.joai.ai";
|
|
38
|
+
};
|
|
21
39
|
const getMcpServerAgentUrl = (env, agent) => `${toSubdomainBaseUrl(env, "cortex")}/mcp/agents/${agent}`;
|
|
40
|
+
const getSitesRootDomain = (env) => {
|
|
41
|
+
if (env === "local") return "sites.joai--api.test";
|
|
42
|
+
if (env === "devnet") return "devnet-sites.joai.ai";
|
|
43
|
+
if (env === "testnet") return "testnet-sites.joai.ai";
|
|
44
|
+
return "sites.joai.ai";
|
|
45
|
+
};
|
|
46
|
+
const getSiteUrl = (env, teamSlug, brandSlug) => `https://${getSitesRootDomain(env)}/${teamSlug}/${brandSlug}`;
|
|
22
47
|
//#endregion
|
|
23
48
|
//#region src/config/constants.ts
|
|
24
49
|
const JoaiUrls = {
|
|
@@ -142,7 +167,7 @@ const sanitizeAlphanumeric = (val) => val.replace(/[^a-z0-9]/gi, "");
|
|
|
142
167
|
const sanitizeNumeric = (val) => {
|
|
143
168
|
return (val.split(".").length > 2 && val.endsWith(".") ? val.slice(0, -1) : val).replace(/[^0-9\.]+/g, "");
|
|
144
169
|
};
|
|
145
|
-
const toNameInitials = (name) => name.split(" ").map((word) => word[0]).join("");
|
|
170
|
+
const toNameInitials = (name) => name ? name.split(" ").map((word) => word[0]).join("").toUpperCase().slice(0, 2) : "?";
|
|
146
171
|
const toPreviewText = (text, maxChars = 100) => {
|
|
147
172
|
if (!text) return "";
|
|
148
173
|
let sanitized = text.replace(/<\/?(h[1-6])[^>]*>/gi, " - ").replace(/<\/?(p|div|ul|ol|li|br|hr)[^>]*>/gi, " ").replace(/<[^>]+>/g, "").replace(/\s+/g, " ").trim();
|
|
@@ -167,6 +192,11 @@ const abbreviateNumber = (val) => {
|
|
|
167
192
|
const suffix = SI_SYMBOL[tier];
|
|
168
193
|
return (val / Math.pow(10, tier * 3)).toFixed(1) + suffix;
|
|
169
194
|
};
|
|
195
|
+
const toCurrencyDisplay = (valueInCents, currency = "USD") => new Intl.NumberFormat("en-US", {
|
|
196
|
+
style: "currency",
|
|
197
|
+
currency,
|
|
198
|
+
currencyDisplay: "symbol"
|
|
199
|
+
}).format(valueInCents / 100);
|
|
170
200
|
const toHexEncodedNonce = (nonce) => {
|
|
171
201
|
const hex = nonce.toString(16);
|
|
172
202
|
return hex.length % 2 ? "0" + hex : hex;
|
|
@@ -203,11 +233,22 @@ const withRetry = async (fn, validate, { maxRetries = 5, retryDelay = 1e3 } = {}
|
|
|
203
233
|
//#endregion
|
|
204
234
|
//#region src/helpers/asset.ts
|
|
205
235
|
const AssetBaseUrl = "https://raw.githubusercontent.com/JoAiHQ/assets/main";
|
|
236
|
+
const WarpsBaseUrl = "https://raw.githubusercontent.com/JoAiHQ/warps/main";
|
|
206
237
|
const getAssetAppLogoUrl = (name) => `${AssetBaseUrl}/apps/logos/${name}`;
|
|
207
238
|
const getAssetChainLogoUrl = (name) => `${AssetBaseUrl}/chains/logos/${name}`;
|
|
208
239
|
const getAssetTokenLogoUrl = (name) => `${AssetBaseUrl}/tokens/logos/${name}`;
|
|
209
240
|
const getAssetWalletLogoUrl = (name) => `${AssetBaseUrl}/wallets/logos/${name}`;
|
|
210
241
|
const getAssetPartnerLogoUrl = (name) => `${AssetBaseUrl}/partners/logos/${name}`;
|
|
242
|
+
const getWarpScreenshotUrl = (brandSlug, warpAlias, variant = "light") => {
|
|
243
|
+
return `${WarpsBaseUrl}/warps/${brandSlug}/images/${warpAlias}${variant === "light" ? "" : `-${variant}`}.png`;
|
|
244
|
+
};
|
|
245
|
+
//#endregion
|
|
246
|
+
//#region src/helpers/warp.ts
|
|
247
|
+
function resolveWarpText(text, locale = "en") {
|
|
248
|
+
if (!text) return "";
|
|
249
|
+
if (typeof text === "string") return text;
|
|
250
|
+
return text[locale] ?? text.en ?? Object.values(text)[0] ?? "";
|
|
251
|
+
}
|
|
211
252
|
//#endregion
|
|
212
253
|
//#region src/budget/types.ts
|
|
213
254
|
const PeriodTypeLabels = {
|
|
@@ -277,6 +318,14 @@ const OgImageColors = {
|
|
|
277
318
|
}
|
|
278
319
|
};
|
|
279
320
|
//#endregion
|
|
321
|
+
//#region src/ogimage/helpers.ts
|
|
322
|
+
const OG_BASE = "https://gen.joai.ai/image/og";
|
|
323
|
+
function ogImageUrl(type, params) {
|
|
324
|
+
switch (type) {
|
|
325
|
+
case "meeting": return `${OG_BASE}/meeting/${params.code}`;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
//#endregion
|
|
280
329
|
//#region src/skill/types.ts
|
|
281
330
|
const SkillType = {
|
|
282
331
|
Knowledge: "knowledge",
|
|
@@ -300,4 +349,4 @@ const SkillScope = {
|
|
|
300
349
|
Team: "team"
|
|
301
350
|
};
|
|
302
351
|
//#endregion
|
|
303
|
-
export { ContractCategoryDisplayNames, DefaultCodeMetadata, HttpService, JoaiUrls, OG_IMAGE_CONFIG, OG_IMAGE_HEIGHT, OG_IMAGE_WIDTH, OgImageColors, PeriodTypeLabels, PeriodTypeOptions, SkillMediaBindingRole, SkillScope, SkillType, abbreviateNumber, getApiBaseUrl, getAppBaseUrl, getAssetAppLogoUrl, getAssetChainLogoUrl, getAssetPartnerLogoUrl, getAssetTokenLogoUrl, getAssetWalletLogoUrl, getCortexBaseUrl, getMcpServerAgentUrl, isFileFormatAudio, isFileFormatImage, isFileFormatVideo, sanitizeAlphanumeric, sanitizeNumeric, toHexEncodedNonce, toNameInitials, toNftId, toPreviewText, withRetry };
|
|
352
|
+
export { ContractCategoryDisplayNames, DefaultCodeMetadata, HttpService, JoaiUrls, OG_IMAGE_CONFIG, OG_IMAGE_HEIGHT, OG_IMAGE_WIDTH, OgImageColors, PeriodTypeLabels, PeriodTypeOptions, SkillMediaBindingRole, SkillScope, SkillType, abbreviateNumber, getAgentsBaseUrl, getApiBaseUrl, getAppBaseUrl, getAssetAppLogoUrl, getAssetChainLogoUrl, getAssetPartnerLogoUrl, getAssetTokenLogoUrl, getAssetWalletLogoUrl, getCortexBaseUrl, getDigestBaseUrl, getMarketplaceBaseUrl, getMcpServerAgentUrl, getSiteUrl, getSitesRootDomain, getWarpScreenshotUrl, isFileFormatAudio, isFileFormatImage, isFileFormatVideo, ogImageUrl, resolveWarpText, sanitizeAlphanumeric, sanitizeNumeric, toCurrencyDisplay, toHexEncodedNonce, toNameInitials, toNftId, toPreviewText, withRetry };
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { n as IHttpService } from "../types-C4eYYzWF.mjs";
|
|
2
|
+
import * as react from "react";
|
|
3
|
+
import { ReactNode } from "react";
|
|
4
|
+
|
|
5
|
+
//#region src/react/types.d.ts
|
|
6
|
+
type EmbedChatItem = {
|
|
7
|
+
content: string;
|
|
8
|
+
byUser: boolean;
|
|
9
|
+
streaming?: boolean;
|
|
10
|
+
error?: boolean;
|
|
11
|
+
};
|
|
12
|
+
//#endregion
|
|
13
|
+
//#region src/react/guest.d.ts
|
|
14
|
+
declare function getOrCreateGuestId(storageKey?: string): string;
|
|
15
|
+
declare function loadRoom(storageKey: string): string | null;
|
|
16
|
+
declare function saveRoom(storageKey: string, roomId: string): void;
|
|
17
|
+
declare function clearRoom(storageKey: string): void;
|
|
18
|
+
//#endregion
|
|
19
|
+
//#region src/react/stream-connection.d.ts
|
|
20
|
+
type StreamDataHandler = (data: unknown) => void;
|
|
21
|
+
declare function openEventSourceStream(url: string, onData: StreamDataHandler, options?: {
|
|
22
|
+
withCredentials?: boolean;
|
|
23
|
+
onError?: (e: unknown) => void;
|
|
24
|
+
onConnectionError?: (e: Event | null) => void;
|
|
25
|
+
}): () => void;
|
|
26
|
+
//#endregion
|
|
27
|
+
//#region src/react/components.d.ts
|
|
28
|
+
type ListClassNames = {
|
|
29
|
+
container?: string;
|
|
30
|
+
userBubble?: string;
|
|
31
|
+
agentBubble?: string;
|
|
32
|
+
errorBubble?: string;
|
|
33
|
+
thinkingWrapper?: string;
|
|
34
|
+
dot?: string;
|
|
35
|
+
};
|
|
36
|
+
type EmbedChatListProps = {
|
|
37
|
+
items: EmbedChatItem[];
|
|
38
|
+
agentName: string;
|
|
39
|
+
awaitingResponse: boolean;
|
|
40
|
+
classNames?: ListClassNames;
|
|
41
|
+
renderEmptyState?: () => ReactNode;
|
|
42
|
+
renderAgentContent?: (item: EmbedChatItem) => ReactNode;
|
|
43
|
+
};
|
|
44
|
+
declare function EmbedChatList({
|
|
45
|
+
items,
|
|
46
|
+
agentName,
|
|
47
|
+
awaitingResponse,
|
|
48
|
+
classNames,
|
|
49
|
+
renderEmptyState,
|
|
50
|
+
renderAgentContent
|
|
51
|
+
}: EmbedChatListProps): string | number | bigint | boolean | Iterable<ReactNode> | Promise<string | number | bigint | boolean | react.ReactPortal | react.ReactElement<unknown, string | react.JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | react.JSX.Element | null | undefined;
|
|
52
|
+
type EmbedChatInputProps = {
|
|
53
|
+
onSend: (message: string) => void;
|
|
54
|
+
disabled: boolean;
|
|
55
|
+
placeholder?: string;
|
|
56
|
+
maxLength?: number;
|
|
57
|
+
classNames?: {
|
|
58
|
+
container?: string;
|
|
59
|
+
textarea?: string;
|
|
60
|
+
};
|
|
61
|
+
renderSendButton: (props: {
|
|
62
|
+
onClick: () => void;
|
|
63
|
+
disabled: boolean;
|
|
64
|
+
}) => ReactNode;
|
|
65
|
+
};
|
|
66
|
+
declare function EmbedChatInput({
|
|
67
|
+
onSend,
|
|
68
|
+
disabled,
|
|
69
|
+
placeholder,
|
|
70
|
+
maxLength,
|
|
71
|
+
classNames,
|
|
72
|
+
renderSendButton
|
|
73
|
+
}: EmbedChatInputProps): react.JSX.Element;
|
|
74
|
+
//#endregion
|
|
75
|
+
//#region src/react/use-embed-chat.d.ts
|
|
76
|
+
type Options = {
|
|
77
|
+
agentUuid: string | null;
|
|
78
|
+
http: IHttpService;
|
|
79
|
+
cortexBaseUrl: string;
|
|
80
|
+
guestId: string;
|
|
81
|
+
roomStorageKey: string;
|
|
82
|
+
enabled?: boolean;
|
|
83
|
+
extraPayload?: Record<string, unknown>;
|
|
84
|
+
};
|
|
85
|
+
declare function useEmbedChat({
|
|
86
|
+
agentUuid,
|
|
87
|
+
http,
|
|
88
|
+
cortexBaseUrl,
|
|
89
|
+
guestId,
|
|
90
|
+
roomStorageKey,
|
|
91
|
+
enabled,
|
|
92
|
+
extraPayload
|
|
93
|
+
}: Options): {
|
|
94
|
+
items: EmbedChatItem[];
|
|
95
|
+
setItems: react.Dispatch<react.SetStateAction<EmbedChatItem[]>>;
|
|
96
|
+
sending: boolean;
|
|
97
|
+
awaitingResponse: boolean;
|
|
98
|
+
roomId: string | null;
|
|
99
|
+
setRoomId: react.Dispatch<react.SetStateAction<string | null>>;
|
|
100
|
+
send: (message: string) => Promise<void>;
|
|
101
|
+
};
|
|
102
|
+
//#endregion
|
|
103
|
+
export { EmbedChatInput, EmbedChatItem, EmbedChatList, StreamDataHandler, clearRoom, getOrCreateGuestId, loadRoom, openEventSourceStream, saveRoom, useEmbedChat };
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
//#region src/react/guest.ts
|
|
4
|
+
function safeGet(key) {
|
|
5
|
+
try {
|
|
6
|
+
return localStorage.getItem(key);
|
|
7
|
+
} catch {
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
function safeSet(key, value) {
|
|
12
|
+
try {
|
|
13
|
+
localStorage.setItem(key, value);
|
|
14
|
+
} catch {}
|
|
15
|
+
}
|
|
16
|
+
function safeRemove(key) {
|
|
17
|
+
try {
|
|
18
|
+
localStorage.removeItem(key);
|
|
19
|
+
} catch {}
|
|
20
|
+
}
|
|
21
|
+
function getOrCreateGuestId(storageKey = "joai_embed_guest") {
|
|
22
|
+
if (typeof window === "undefined") return "";
|
|
23
|
+
const existing = safeGet(storageKey);
|
|
24
|
+
if (existing) return existing;
|
|
25
|
+
const id = crypto.randomUUID();
|
|
26
|
+
safeSet(storageKey, id);
|
|
27
|
+
return id;
|
|
28
|
+
}
|
|
29
|
+
function loadRoom(storageKey) {
|
|
30
|
+
return safeGet(storageKey);
|
|
31
|
+
}
|
|
32
|
+
function saveRoom(storageKey, roomId) {
|
|
33
|
+
safeSet(storageKey, roomId);
|
|
34
|
+
}
|
|
35
|
+
function clearRoom(storageKey) {
|
|
36
|
+
safeRemove(storageKey);
|
|
37
|
+
}
|
|
38
|
+
//#endregion
|
|
39
|
+
//#region src/react/stream-connection.ts
|
|
40
|
+
function openEventSourceStream(url, onData, options) {
|
|
41
|
+
const es = new EventSource(url, { withCredentials: options?.withCredentials ?? false });
|
|
42
|
+
es.onmessage = (event) => {
|
|
43
|
+
try {
|
|
44
|
+
onData(JSON.parse(event.data));
|
|
45
|
+
} catch (e) {
|
|
46
|
+
options?.onError?.(e);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
es.onerror = (event) => {
|
|
50
|
+
options?.onConnectionError?.(event);
|
|
51
|
+
};
|
|
52
|
+
return () => es.close();
|
|
53
|
+
}
|
|
54
|
+
//#endregion
|
|
55
|
+
//#region src/react/components.tsx
|
|
56
|
+
function EmbedChatList({ items, agentName, awaitingResponse, classNames = {}, renderEmptyState, renderAgentContent }) {
|
|
57
|
+
const containerRef = useRef(null);
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
const el = containerRef.current;
|
|
60
|
+
if (!el) return;
|
|
61
|
+
if (el.scrollHeight - el.scrollTop - el.clientHeight < 120) el.scrollTop = el.scrollHeight;
|
|
62
|
+
}, [items, awaitingResponse]);
|
|
63
|
+
if (items.length === 0 && !awaitingResponse) return renderEmptyState ? renderEmptyState() : /* @__PURE__ */ jsx("div", {
|
|
64
|
+
className: classNames.container,
|
|
65
|
+
children: /* @__PURE__ */ jsxs("p", { children: [
|
|
66
|
+
"Send ",
|
|
67
|
+
agentName,
|
|
68
|
+
" a message to start chatting"
|
|
69
|
+
] })
|
|
70
|
+
});
|
|
71
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
72
|
+
ref: containerRef,
|
|
73
|
+
className: classNames.container,
|
|
74
|
+
role: "log",
|
|
75
|
+
"aria-live": "polite",
|
|
76
|
+
children: [items.map((item, i) => /* @__PURE__ */ jsx(EmbedChatBubble, {
|
|
77
|
+
item,
|
|
78
|
+
classNames,
|
|
79
|
+
renderAgentContent
|
|
80
|
+
}, i)), awaitingResponse && /* @__PURE__ */ jsx(EmbedThinkingIndicator, {
|
|
81
|
+
wrapperClassName: classNames.thinkingWrapper,
|
|
82
|
+
dotClassName: classNames.dot
|
|
83
|
+
})]
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
function EmbedChatBubble({ item, classNames, renderAgentContent }) {
|
|
87
|
+
if (item.byUser) return /* @__PURE__ */ jsx("div", {
|
|
88
|
+
className: classNames.userBubble,
|
|
89
|
+
children: item.content
|
|
90
|
+
});
|
|
91
|
+
return /* @__PURE__ */ jsx("div", {
|
|
92
|
+
className: item.error ? classNames.errorBubble : classNames.agentBubble,
|
|
93
|
+
children: renderAgentContent ? renderAgentContent(item) : /* @__PURE__ */ jsxs(Fragment, { children: [item.content, item.streaming && /* @__PURE__ */ jsx("span", { className: "inline-block w-1 h-3 ml-1 bg-current animate-pulse rounded-full" })] })
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
function EmbedThinkingIndicator({ wrapperClassName, dotClassName }) {
|
|
97
|
+
return /* @__PURE__ */ jsx("div", {
|
|
98
|
+
className: wrapperClassName,
|
|
99
|
+
children: /* @__PURE__ */ jsxs("span", {
|
|
100
|
+
className: "inline-flex gap-1",
|
|
101
|
+
children: [
|
|
102
|
+
/* @__PURE__ */ jsx("span", {
|
|
103
|
+
className: dotClassName,
|
|
104
|
+
style: { animationDelay: "0ms" }
|
|
105
|
+
}),
|
|
106
|
+
/* @__PURE__ */ jsx("span", {
|
|
107
|
+
className: dotClassName,
|
|
108
|
+
style: { animationDelay: "150ms" }
|
|
109
|
+
}),
|
|
110
|
+
/* @__PURE__ */ jsx("span", {
|
|
111
|
+
className: dotClassName,
|
|
112
|
+
style: { animationDelay: "300ms" }
|
|
113
|
+
})
|
|
114
|
+
]
|
|
115
|
+
})
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
function EmbedChatInput({ onSend, disabled, placeholder = "Type a message...", maxLength = 4e3, classNames = {}, renderSendButton }) {
|
|
119
|
+
const [text, setText] = useState("");
|
|
120
|
+
const textareaRef = useRef(null);
|
|
121
|
+
function handleSend() {
|
|
122
|
+
const trimmed = text.trim();
|
|
123
|
+
if (!trimmed || disabled) return;
|
|
124
|
+
onSend(trimmed);
|
|
125
|
+
setText("");
|
|
126
|
+
if (textareaRef.current) textareaRef.current.style.height = "auto";
|
|
127
|
+
}
|
|
128
|
+
function handleKeyDown(e) {
|
|
129
|
+
if (e.key === "Enter" && !e.shiftKey) {
|
|
130
|
+
e.preventDefault();
|
|
131
|
+
handleSend();
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
function handleInput() {
|
|
135
|
+
if (textareaRef.current) {
|
|
136
|
+
textareaRef.current.style.height = "auto";
|
|
137
|
+
textareaRef.current.style.height = Math.min(textareaRef.current.scrollHeight, 120) + "px";
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
141
|
+
className: classNames.container,
|
|
142
|
+
children: [/* @__PURE__ */ jsx("textarea", {
|
|
143
|
+
ref: textareaRef,
|
|
144
|
+
value: text,
|
|
145
|
+
onChange: (e) => setText(e.target.value),
|
|
146
|
+
onKeyDown: handleKeyDown,
|
|
147
|
+
onInput: handleInput,
|
|
148
|
+
placeholder,
|
|
149
|
+
"aria-label": "Message input",
|
|
150
|
+
maxLength,
|
|
151
|
+
rows: 1,
|
|
152
|
+
disabled,
|
|
153
|
+
className: classNames.textarea
|
|
154
|
+
}), renderSendButton({
|
|
155
|
+
onClick: handleSend,
|
|
156
|
+
disabled: disabled || !text.trim()
|
|
157
|
+
})]
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
//#endregion
|
|
161
|
+
//#region src/react/use-embed-chat.ts
|
|
162
|
+
function useEmbedChat({ agentUuid, http, cortexBaseUrl, guestId, roomStorageKey, enabled = true, extraPayload }) {
|
|
163
|
+
const [items, setItems] = useState([]);
|
|
164
|
+
const [sending, setSending] = useState(false);
|
|
165
|
+
const [awaitingResponse, setAwaitingResponse] = useState(false);
|
|
166
|
+
const [roomId, setRoomId] = useState(null);
|
|
167
|
+
const roomIdRef = useRef(null);
|
|
168
|
+
roomIdRef.current = roomId;
|
|
169
|
+
const extraPayloadRef = useRef(extraPayload);
|
|
170
|
+
extraPayloadRef.current = extraPayload;
|
|
171
|
+
useEffect(() => {
|
|
172
|
+
if (!roomId) return;
|
|
173
|
+
return openEventSourceStream(`${cortexBaseUrl}/agents/stream/${roomId}`, (raw) => {
|
|
174
|
+
const data = raw;
|
|
175
|
+
if (data.type === "response:chunk") {
|
|
176
|
+
setAwaitingResponse(false);
|
|
177
|
+
setItems((prev) => {
|
|
178
|
+
const last = prev[prev.length - 1];
|
|
179
|
+
if (last && last.streaming && !last.byUser) {
|
|
180
|
+
const updated = [...prev];
|
|
181
|
+
updated[updated.length - 1] = {
|
|
182
|
+
...last,
|
|
183
|
+
content: last.content + (data.content ?? "")
|
|
184
|
+
};
|
|
185
|
+
return updated;
|
|
186
|
+
}
|
|
187
|
+
return [...prev, {
|
|
188
|
+
content: data.content ?? "",
|
|
189
|
+
byUser: false,
|
|
190
|
+
streaming: true
|
|
191
|
+
}];
|
|
192
|
+
});
|
|
193
|
+
} else if (data.type === "response") {
|
|
194
|
+
setAwaitingResponse(false);
|
|
195
|
+
setItems((prev) => {
|
|
196
|
+
const updated = [...prev];
|
|
197
|
+
const lastIdx = updated.length - 1;
|
|
198
|
+
if (lastIdx >= 0 && updated[lastIdx].streaming) updated[lastIdx] = {
|
|
199
|
+
...updated[lastIdx],
|
|
200
|
+
content: data.content ?? updated[lastIdx].content,
|
|
201
|
+
streaming: false
|
|
202
|
+
};
|
|
203
|
+
else updated.push({
|
|
204
|
+
content: data.content ?? "",
|
|
205
|
+
byUser: false
|
|
206
|
+
});
|
|
207
|
+
return updated;
|
|
208
|
+
});
|
|
209
|
+
} else if (data.type === "complete") setItems((prev) => prev.map((item) => item.streaming ? {
|
|
210
|
+
...item,
|
|
211
|
+
streaming: false
|
|
212
|
+
} : item));
|
|
213
|
+
});
|
|
214
|
+
}, [roomId, cortexBaseUrl]);
|
|
215
|
+
return {
|
|
216
|
+
items,
|
|
217
|
+
setItems,
|
|
218
|
+
sending,
|
|
219
|
+
awaitingResponse,
|
|
220
|
+
roomId,
|
|
221
|
+
setRoomId,
|
|
222
|
+
send: useCallback(async (message) => {
|
|
223
|
+
if (!enabled || sending || !agentUuid) return;
|
|
224
|
+
setItems((prev) => [...prev, {
|
|
225
|
+
content: message,
|
|
226
|
+
byUser: true
|
|
227
|
+
}]);
|
|
228
|
+
setSending(true);
|
|
229
|
+
try {
|
|
230
|
+
const res = await http.post(`integrations/embed/${agentUuid}/execute`, {
|
|
231
|
+
message,
|
|
232
|
+
guestId,
|
|
233
|
+
...extraPayloadRef.current
|
|
234
|
+
});
|
|
235
|
+
if (!res.ok) {
|
|
236
|
+
setItems((prev) => [...prev, {
|
|
237
|
+
content: "Something went wrong. Please try again.",
|
|
238
|
+
byUser: false,
|
|
239
|
+
error: true
|
|
240
|
+
}]);
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
setAwaitingResponse(true);
|
|
244
|
+
const newRoomId = res.data?.roomId;
|
|
245
|
+
if (newRoomId && !roomIdRef.current) {
|
|
246
|
+
setRoomId(newRoomId);
|
|
247
|
+
saveRoom(roomStorageKey, newRoomId);
|
|
248
|
+
}
|
|
249
|
+
} catch {
|
|
250
|
+
setItems((prev) => [...prev, {
|
|
251
|
+
content: "Something went wrong. Please try again.",
|
|
252
|
+
byUser: false,
|
|
253
|
+
error: true
|
|
254
|
+
}]);
|
|
255
|
+
} finally {
|
|
256
|
+
setSending(false);
|
|
257
|
+
}
|
|
258
|
+
}, [
|
|
259
|
+
enabled,
|
|
260
|
+
sending,
|
|
261
|
+
agentUuid,
|
|
262
|
+
http,
|
|
263
|
+
guestId,
|
|
264
|
+
roomStorageKey
|
|
265
|
+
])
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
//#endregion
|
|
269
|
+
export { EmbedChatInput, EmbedChatList, clearRoom, getOrCreateGuestId, loadRoom, openEventSourceStream, saveRoom, useEmbedChat };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
//#region src/http/types.d.ts
|
|
2
|
+
interface IHttpService {
|
|
3
|
+
readonly baseUrl: string;
|
|
4
|
+
readonly authToken: string | null;
|
|
5
|
+
onNetworkError: (error: Error) => void;
|
|
6
|
+
onUnauthorized: (response: IAppResponse<any>) => void;
|
|
7
|
+
onSuccessfulResponse: (response: IAppResponse<any>) => void;
|
|
8
|
+
onMaintenanceMode: (response: IAppResponse<any>) => void;
|
|
9
|
+
get: <T>(url: string, options?: IRequestOptions) => Promise<IAppResponse<T>>;
|
|
10
|
+
post: <T>(url: string, data?: any, options?: IRequestOptions) => Promise<IAppResponse<T>>;
|
|
11
|
+
put: <T>(url: string, data?: any, options?: IRequestOptions) => Promise<IAppResponse<T>>;
|
|
12
|
+
delete: <T>(url: string, options?: IRequestOptions) => Promise<IAppResponse<T>>;
|
|
13
|
+
}
|
|
14
|
+
interface IAppResponse<T> {
|
|
15
|
+
data: T;
|
|
16
|
+
meta?: any;
|
|
17
|
+
body: any;
|
|
18
|
+
errors: any;
|
|
19
|
+
ok: boolean;
|
|
20
|
+
paymentRequired: boolean;
|
|
21
|
+
noContent: boolean;
|
|
22
|
+
original: Response;
|
|
23
|
+
}
|
|
24
|
+
interface IRequestOptions {
|
|
25
|
+
isJson?: boolean;
|
|
26
|
+
headers?: {
|
|
27
|
+
[name: string]: string;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
//#endregion
|
|
31
|
+
export { IHttpService as n, IRequestOptions as r, IAppResponse as t };
|
package/package.json
CHANGED
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@joai/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "JoAi shared types, HTTP service, and config",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "./dist/index.mjs",
|
|
8
|
+
"types": "./dist/index.d.mts",
|
|
8
9
|
"exports": {
|
|
9
10
|
".": {
|
|
10
11
|
"import": "./dist/index.mjs",
|
|
11
12
|
"types": "./dist/index.d.mts"
|
|
13
|
+
},
|
|
14
|
+
"./react": {
|
|
15
|
+
"import": "./dist/react/index.mjs",
|
|
16
|
+
"types": "./dist/react/index.d.mts"
|
|
12
17
|
}
|
|
13
18
|
},
|
|
14
19
|
"files": [
|
|
@@ -21,10 +26,17 @@
|
|
|
21
26
|
"test:watch": "jest --watch"
|
|
22
27
|
},
|
|
23
28
|
"peerDependencies": {
|
|
24
|
-
"@joai/warps": ">=4.0.0"
|
|
29
|
+
"@joai/warps": ">=4.0.0",
|
|
30
|
+
"react": ">=18.0.0"
|
|
31
|
+
},
|
|
32
|
+
"peerDependenciesMeta": {
|
|
33
|
+
"react": {
|
|
34
|
+
"optional": true
|
|
35
|
+
}
|
|
25
36
|
},
|
|
26
37
|
"devDependencies": {
|
|
27
38
|
"@types/jest": "^30.0.0",
|
|
39
|
+
"@types/react": "^19.2.14",
|
|
28
40
|
"jest": "^30.3.0",
|
|
29
41
|
"ts-jest": "^29.4.6",
|
|
30
42
|
"tsdown": "^0.21.0",
|