@helpway/react 0.1.0 → 0.1.1
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.css +2865 -0
- package/dist/index.css.map +1 -0
- package/dist/index.d.ts +44 -34
- package/dist/index.js +244 -248
- package/dist/index.js.map +1 -1
- package/package.json +4 -2
package/dist/index.js
CHANGED
|
@@ -1,32 +1,3 @@
|
|
|
1
|
-
// src/helpway-widget.tsx
|
|
2
|
-
import { useEffect } from "react";
|
|
3
|
-
function HelpwayWidget({
|
|
4
|
-
apiKey,
|
|
5
|
-
src = "https://api.helpway.ai/widget.js"
|
|
6
|
-
}) {
|
|
7
|
-
useEffect(() => {
|
|
8
|
-
if (typeof document === "undefined") return;
|
|
9
|
-
const existing = document.querySelector(
|
|
10
|
-
`script[data-helpway-loader="${apiKey}"]`
|
|
11
|
-
);
|
|
12
|
-
if (existing) return;
|
|
13
|
-
const s = document.createElement("script");
|
|
14
|
-
s.src = src;
|
|
15
|
-
s.dataset.key = apiKey;
|
|
16
|
-
s.dataset.helpwayLoader = apiKey;
|
|
17
|
-
s.defer = true;
|
|
18
|
-
document.body.appendChild(s);
|
|
19
|
-
return () => {
|
|
20
|
-
s.remove();
|
|
21
|
-
try {
|
|
22
|
-
window.dispatchEvent(new CustomEvent("helpway:cleanup"));
|
|
23
|
-
} catch {
|
|
24
|
-
}
|
|
25
|
-
};
|
|
26
|
-
}, [apiKey, src]);
|
|
27
|
-
return null;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
1
|
// ../widget-core/src/client.ts
|
|
31
2
|
import { createTRPCClient, httpBatchLink } from "@trpc/client";
|
|
32
3
|
|
|
@@ -740,7 +711,7 @@ import {
|
|
|
740
711
|
createContext,
|
|
741
712
|
useCallback,
|
|
742
713
|
useContext,
|
|
743
|
-
useEffect
|
|
714
|
+
useEffect,
|
|
744
715
|
useRef,
|
|
745
716
|
useState
|
|
746
717
|
} from "react";
|
|
@@ -811,7 +782,7 @@ function SupportProvider({
|
|
|
811
782
|
setHasBootstrapError(false);
|
|
812
783
|
setBootstrapAttempt((n) => n + 1);
|
|
813
784
|
}, []);
|
|
814
|
-
|
|
785
|
+
useEffect(() => {
|
|
815
786
|
let cancelled = false;
|
|
816
787
|
async function bootstrap() {
|
|
817
788
|
try {
|
|
@@ -903,7 +874,7 @@ function SupportProvider({
|
|
|
903
874
|
ws.disconnect();
|
|
904
875
|
};
|
|
905
876
|
}, [client, store, ws, bootstrapAttempt]);
|
|
906
|
-
|
|
877
|
+
useEffect(() => {
|
|
907
878
|
if (locale != null) {
|
|
908
879
|
setLocale(locale);
|
|
909
880
|
}
|
|
@@ -936,176 +907,20 @@ function inferApiUrl() {
|
|
|
936
907
|
return typeof window !== "undefined" ? window.location.origin : "http://localhost:52500";
|
|
937
908
|
}
|
|
938
909
|
|
|
939
|
-
// src/
|
|
940
|
-
|
|
941
|
-
const { client } = useWidgetContext();
|
|
942
|
-
const isReady = useWidgetStore((s) => s.isReady);
|
|
943
|
-
const isConnecting = useWidgetStore((s) => s.isConnecting);
|
|
944
|
-
const visitor = useWidgetStore((s) => s.visitor);
|
|
945
|
-
const website = useWidgetStore((s) => s.website);
|
|
946
|
-
const agentConfig = useWidgetStore((s) => s.agentConfig);
|
|
947
|
-
async function identify(data) {
|
|
948
|
-
return client.widget.identify.mutate(data);
|
|
949
|
-
}
|
|
950
|
-
return { isReady, isConnecting, visitor, website, agentConfig, identify };
|
|
951
|
-
}
|
|
952
|
-
|
|
953
|
-
// src/hooks/use-conversations.ts
|
|
954
|
-
import { useCallback as useCallback2, useRef as useRef2, useState as useState2 } from "react";
|
|
955
|
-
function useConversations() {
|
|
956
|
-
const { client, store } = useWidgetContext();
|
|
957
|
-
const conversations = useWidgetStore((s) => s.conversations);
|
|
958
|
-
const setActiveConversation = useWidgetStore((s) => s.setActiveConversation);
|
|
959
|
-
const [isCreating, setIsCreating] = useState2(false);
|
|
960
|
-
const clientRef = useRef2(client);
|
|
961
|
-
const storeRef = useRef2(store);
|
|
962
|
-
clientRef.current = client;
|
|
963
|
-
storeRef.current = store;
|
|
964
|
-
const create = useCallback2(async (text) => {
|
|
965
|
-
setIsCreating(true);
|
|
966
|
-
const tempConversationId = `temp-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
|
|
967
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
968
|
-
const optimisticMessageId = `optimistic-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
|
|
969
|
-
const currentVisitorId = storeRef.current.getState().visitor?.id ?? null;
|
|
970
|
-
const optimisticMsg = {
|
|
971
|
-
id: optimisticMessageId,
|
|
972
|
-
conversationId: tempConversationId,
|
|
973
|
-
organizationId: "",
|
|
974
|
-
type: "message",
|
|
975
|
-
visibility: "public",
|
|
976
|
-
text,
|
|
977
|
-
parts: null,
|
|
978
|
-
userId: null,
|
|
979
|
-
visitorId: currentVisitorId,
|
|
980
|
-
aiAgentId: null,
|
|
981
|
-
createdAt: now,
|
|
982
|
-
editedAt: null,
|
|
983
|
-
deletedAt: null
|
|
984
|
-
};
|
|
985
|
-
const store2 = storeRef.current.getState();
|
|
986
|
-
store2.addConversation({
|
|
987
|
-
id: tempConversationId,
|
|
988
|
-
title: null,
|
|
989
|
-
status: "open",
|
|
990
|
-
lastMessageText: text,
|
|
991
|
-
lastMessageAt: now,
|
|
992
|
-
unreadCount: 0
|
|
993
|
-
});
|
|
994
|
-
store2.setMessages(tempConversationId, [optimisticMsg]);
|
|
995
|
-
store2.setActiveConversation(tempConversationId);
|
|
996
|
-
try {
|
|
997
|
-
const result = await clientRef.current.widget.createConversation.mutate({ text });
|
|
998
|
-
const conv = result.conversation;
|
|
999
|
-
storeRef.current.getState().remapConversationId(tempConversationId, conv.id);
|
|
1000
|
-
if (result.item) {
|
|
1001
|
-
storeRef.current.getState().replaceMessage(conv.id, optimisticMessageId, result.item);
|
|
1002
|
-
}
|
|
1003
|
-
if (result.welcomeItem) {
|
|
1004
|
-
const welcome = result.welcomeItem;
|
|
1005
|
-
const existing = storeRef.current.getState().messages.get(conv.id) ?? [];
|
|
1006
|
-
const withoutWelcome = existing.filter((m) => m.id !== welcome.id);
|
|
1007
|
-
storeRef.current.getState().setMessages(conv.id, [welcome, ...withoutWelcome]);
|
|
1008
|
-
}
|
|
1009
|
-
return conv;
|
|
1010
|
-
} catch (err) {
|
|
1011
|
-
storeRef.current.getState().removeMessage(tempConversationId, optimisticMessageId);
|
|
1012
|
-
const convs = storeRef.current.getState().conversations.filter((c) => c.id !== tempConversationId);
|
|
1013
|
-
storeRef.current.setState({ conversations: convs, activeConversationId: null });
|
|
1014
|
-
throw err;
|
|
1015
|
-
} finally {
|
|
1016
|
-
setIsCreating(false);
|
|
1017
|
-
}
|
|
1018
|
-
}, []);
|
|
1019
|
-
return { conversations, create, isCreating, setActiveConversation };
|
|
1020
|
-
}
|
|
1021
|
-
|
|
1022
|
-
// src/hooks/use-timeline.ts
|
|
1023
|
-
import { useCallback as useCallback3, useEffect as useEffect3, useRef as useRef3, useState as useState3 } from "react";
|
|
1024
|
-
var EMPTY_ITEMS = [];
|
|
1025
|
-
function useTimeline(conversationId) {
|
|
1026
|
-
const { client, store } = useWidgetContext();
|
|
1027
|
-
const messages = useWidgetStore(
|
|
1028
|
-
(s) => conversationId ? s.messages.get(conversationId) ?? EMPTY_ITEMS : EMPTY_ITEMS
|
|
1029
|
-
);
|
|
1030
|
-
const [isLoading, setIsLoading] = useState3(false);
|
|
1031
|
-
const [hasMore, setHasMore] = useState3(false);
|
|
1032
|
-
const cursorRef = useRef3(void 0);
|
|
1033
|
-
const clientRef = useRef3(client);
|
|
1034
|
-
const storeRef = useRef3(store);
|
|
1035
|
-
clientRef.current = client;
|
|
1036
|
-
storeRef.current = store;
|
|
1037
|
-
useEffect3(() => {
|
|
1038
|
-
if (!conversationId) return;
|
|
1039
|
-
if (conversationId.startsWith("temp-")) return;
|
|
1040
|
-
let cancelled = false;
|
|
1041
|
-
const existingMessages = storeRef.current.getState().messages.get(conversationId);
|
|
1042
|
-
const hasSeed = !!existingMessages && existingMessages.length > 0;
|
|
1043
|
-
if (hasSeed) {
|
|
1044
|
-
storeRef.current.getState().clearUnread(conversationId);
|
|
1045
|
-
clientRef.current.widget.markSeen.mutate({ conversationId }).catch(() => {
|
|
1046
|
-
});
|
|
1047
|
-
return () => {
|
|
1048
|
-
cancelled = true;
|
|
1049
|
-
};
|
|
1050
|
-
}
|
|
1051
|
-
setIsLoading(true);
|
|
1052
|
-
async function load() {
|
|
1053
|
-
try {
|
|
1054
|
-
const data = await clientRef.current.widget.getTimeline.query({
|
|
1055
|
-
conversationId,
|
|
1056
|
-
limit: 50
|
|
1057
|
-
});
|
|
1058
|
-
if (cancelled) return;
|
|
1059
|
-
if (data.items.length > 0) {
|
|
1060
|
-
storeRef.current.getState().setMessages(conversationId, data.items);
|
|
1061
|
-
}
|
|
1062
|
-
setHasMore(data.hasMore);
|
|
1063
|
-
cursorRef.current = data.nextCursor ?? void 0;
|
|
1064
|
-
storeRef.current.getState().clearUnread(conversationId);
|
|
1065
|
-
clientRef.current.widget.markSeen.mutate({ conversationId }).catch(() => {
|
|
1066
|
-
});
|
|
1067
|
-
} catch (err) {
|
|
1068
|
-
console.error("[useTimeline] Failed to load:", err);
|
|
1069
|
-
} finally {
|
|
1070
|
-
if (!cancelled) setIsLoading(false);
|
|
1071
|
-
}
|
|
1072
|
-
}
|
|
1073
|
-
load();
|
|
1074
|
-
return () => {
|
|
1075
|
-
cancelled = true;
|
|
1076
|
-
};
|
|
1077
|
-
}, [conversationId]);
|
|
1078
|
-
const loadMore = useCallback3(async () => {
|
|
1079
|
-
if (!conversationId || !hasMore || isLoading) return;
|
|
1080
|
-
setIsLoading(true);
|
|
1081
|
-
try {
|
|
1082
|
-
const data = await clientRef.current.widget.getTimeline.query({
|
|
1083
|
-
conversationId,
|
|
1084
|
-
cursor: cursorRef.current,
|
|
1085
|
-
limit: 50
|
|
1086
|
-
});
|
|
1087
|
-
const existing = storeRef.current.getState().messages.get(conversationId) ?? [];
|
|
1088
|
-
storeRef.current.getState().setMessages(conversationId, [...data.items, ...existing]);
|
|
1089
|
-
setHasMore(data.hasMore);
|
|
1090
|
-
cursorRef.current = data.nextCursor ?? void 0;
|
|
1091
|
-
} finally {
|
|
1092
|
-
setIsLoading(false);
|
|
1093
|
-
}
|
|
1094
|
-
}, [conversationId, hasMore, isLoading]);
|
|
1095
|
-
return { items: messages, isLoading, hasMore, loadMore };
|
|
1096
|
-
}
|
|
910
|
+
// src/themed/default-widget.tsx
|
|
911
|
+
import { useCallback as useCallback12, useEffect as useEffect10, useRef as useRef13, useState as useState12 } from "react";
|
|
1097
912
|
|
|
1098
913
|
// src/hooks/use-composer.ts
|
|
1099
|
-
import { useCallback as
|
|
914
|
+
import { useCallback as useCallback2, useState as useState2 } from "react";
|
|
1100
915
|
var MAX_ATTACHMENTS = 10;
|
|
1101
916
|
var MAX_FILE_SIZE = 10 * 1024 * 1024;
|
|
1102
917
|
function useComposer(conversationId) {
|
|
1103
918
|
const { client, ws, store } = useWidgetContext();
|
|
1104
|
-
const [text, setText] =
|
|
1105
|
-
const [isSubmitting, setIsSubmitting] =
|
|
1106
|
-
const [attachments, setAttachments] =
|
|
1107
|
-
const [sendError, setSendError] =
|
|
1108
|
-
const addFiles =
|
|
919
|
+
const [text, setText] = useState2("");
|
|
920
|
+
const [isSubmitting, setIsSubmitting] = useState2(false);
|
|
921
|
+
const [attachments, setAttachments] = useState2([]);
|
|
922
|
+
const [sendError, setSendError] = useState2(null);
|
|
923
|
+
const addFiles = useCallback2(
|
|
1109
924
|
async (files) => {
|
|
1110
925
|
if (!conversationId) return;
|
|
1111
926
|
const remaining = MAX_ATTACHMENTS - attachments.length;
|
|
@@ -1148,14 +963,14 @@ function useComposer(conversationId) {
|
|
|
1148
963
|
},
|
|
1149
964
|
[conversationId, attachments.length, client]
|
|
1150
965
|
);
|
|
1151
|
-
const removeAttachment =
|
|
966
|
+
const removeAttachment = useCallback2((id) => {
|
|
1152
967
|
setAttachments((prev) => {
|
|
1153
968
|
const item = prev.find((a) => a.id === id);
|
|
1154
969
|
if (item?.previewUrl) URL.revokeObjectURL(item.previewUrl);
|
|
1155
970
|
return prev.filter((a) => a.id !== id);
|
|
1156
971
|
});
|
|
1157
972
|
}, []);
|
|
1158
|
-
const submit =
|
|
973
|
+
const submit = useCallback2(async () => {
|
|
1159
974
|
const trimmed = text.trim();
|
|
1160
975
|
const readyParts = attachments.filter((a) => a.part !== null).map((a) => a.part);
|
|
1161
976
|
const hasText = trimmed.length > 0;
|
|
@@ -1232,7 +1047,7 @@ function useComposer(conversationId) {
|
|
|
1232
1047
|
setIsSubmitting(false);
|
|
1233
1048
|
}
|
|
1234
1049
|
}, [text, attachments, conversationId, isSubmitting, client, ws, store]);
|
|
1235
|
-
const clearSendError =
|
|
1050
|
+
const clearSendError = useCallback2(() => setSendError(null), []);
|
|
1236
1051
|
return {
|
|
1237
1052
|
text,
|
|
1238
1053
|
setText,
|
|
@@ -1259,8 +1074,89 @@ function fileToBase64(file) {
|
|
|
1259
1074
|
});
|
|
1260
1075
|
}
|
|
1261
1076
|
|
|
1077
|
+
// src/hooks/use-conversations.ts
|
|
1078
|
+
import { useCallback as useCallback3, useRef as useRef2, useState as useState3 } from "react";
|
|
1079
|
+
function useConversations() {
|
|
1080
|
+
const { client, store } = useWidgetContext();
|
|
1081
|
+
const conversations = useWidgetStore((s) => s.conversations);
|
|
1082
|
+
const setActiveConversation = useWidgetStore((s) => s.setActiveConversation);
|
|
1083
|
+
const [isCreating, setIsCreating] = useState3(false);
|
|
1084
|
+
const clientRef = useRef2(client);
|
|
1085
|
+
const storeRef = useRef2(store);
|
|
1086
|
+
clientRef.current = client;
|
|
1087
|
+
storeRef.current = store;
|
|
1088
|
+
const create = useCallback3(async (text) => {
|
|
1089
|
+
setIsCreating(true);
|
|
1090
|
+
const tempConversationId = `temp-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
|
|
1091
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1092
|
+
const optimisticMessageId = `optimistic-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
|
|
1093
|
+
const currentVisitorId = storeRef.current.getState().visitor?.id ?? null;
|
|
1094
|
+
const optimisticMsg = {
|
|
1095
|
+
id: optimisticMessageId,
|
|
1096
|
+
conversationId: tempConversationId,
|
|
1097
|
+
organizationId: "",
|
|
1098
|
+
type: "message",
|
|
1099
|
+
visibility: "public",
|
|
1100
|
+
text,
|
|
1101
|
+
parts: null,
|
|
1102
|
+
userId: null,
|
|
1103
|
+
visitorId: currentVisitorId,
|
|
1104
|
+
aiAgentId: null,
|
|
1105
|
+
createdAt: now,
|
|
1106
|
+
editedAt: null,
|
|
1107
|
+
deletedAt: null
|
|
1108
|
+
};
|
|
1109
|
+
const store2 = storeRef.current.getState();
|
|
1110
|
+
store2.addConversation({
|
|
1111
|
+
id: tempConversationId,
|
|
1112
|
+
title: null,
|
|
1113
|
+
status: "open",
|
|
1114
|
+
lastMessageText: text,
|
|
1115
|
+
lastMessageAt: now,
|
|
1116
|
+
unreadCount: 0
|
|
1117
|
+
});
|
|
1118
|
+
store2.setMessages(tempConversationId, [optimisticMsg]);
|
|
1119
|
+
store2.setActiveConversation(tempConversationId);
|
|
1120
|
+
try {
|
|
1121
|
+
const result = await clientRef.current.widget.createConversation.mutate({ text });
|
|
1122
|
+
const conv = result.conversation;
|
|
1123
|
+
storeRef.current.getState().remapConversationId(tempConversationId, conv.id);
|
|
1124
|
+
if (result.item) {
|
|
1125
|
+
storeRef.current.getState().replaceMessage(conv.id, optimisticMessageId, result.item);
|
|
1126
|
+
}
|
|
1127
|
+
if (result.welcomeItem) {
|
|
1128
|
+
const welcome = result.welcomeItem;
|
|
1129
|
+
const existing = storeRef.current.getState().messages.get(conv.id) ?? [];
|
|
1130
|
+
const withoutWelcome = existing.filter((m) => m.id !== welcome.id);
|
|
1131
|
+
storeRef.current.getState().setMessages(conv.id, [welcome, ...withoutWelcome]);
|
|
1132
|
+
}
|
|
1133
|
+
return conv;
|
|
1134
|
+
} catch (err) {
|
|
1135
|
+
storeRef.current.getState().removeMessage(tempConversationId, optimisticMessageId);
|
|
1136
|
+
const convs = storeRef.current.getState().conversations.filter((c) => c.id !== tempConversationId);
|
|
1137
|
+
storeRef.current.setState({ conversations: convs, activeConversationId: null });
|
|
1138
|
+
throw err;
|
|
1139
|
+
} finally {
|
|
1140
|
+
setIsCreating(false);
|
|
1141
|
+
}
|
|
1142
|
+
}, []);
|
|
1143
|
+
return { conversations, create, isCreating, setActiveConversation };
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
// src/hooks/use-i18n.ts
|
|
1147
|
+
import { useEffect as useEffect2, useState as useState4 } from "react";
|
|
1148
|
+
function useI18n() {
|
|
1149
|
+
const [rev, setRev] = useState4(() => getI18nRevision());
|
|
1150
|
+
useEffect2(() => {
|
|
1151
|
+
return onLocaleChange(() => {
|
|
1152
|
+
setRev(getI18nRevision());
|
|
1153
|
+
});
|
|
1154
|
+
}, []);
|
|
1155
|
+
return rev;
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1262
1158
|
// src/hooks/use-presence.ts
|
|
1263
|
-
import { useCallback as
|
|
1159
|
+
import { useCallback as useCallback4, useRef as useRef3 } from "react";
|
|
1264
1160
|
function usePresence(conversationId) {
|
|
1265
1161
|
const { ws } = useWidgetContext();
|
|
1266
1162
|
const isTyping = useWidgetStore(
|
|
@@ -1269,9 +1165,9 @@ function usePresence(conversationId) {
|
|
|
1269
1165
|
const isAiProcessing = useWidgetStore(
|
|
1270
1166
|
(s) => conversationId ? s.aiProcessing.get(conversationId) ?? false : false
|
|
1271
1167
|
);
|
|
1272
|
-
const typingTimer =
|
|
1273
|
-
const isSendingTyping =
|
|
1274
|
-
const setTyping =
|
|
1168
|
+
const typingTimer = useRef3(null);
|
|
1169
|
+
const isSendingTyping = useRef3(false);
|
|
1170
|
+
const setTyping = useCallback4(
|
|
1275
1171
|
(typing) => {
|
|
1276
1172
|
if (!conversationId) return;
|
|
1277
1173
|
if (typingTimer.current) clearTimeout(typingTimer.current);
|
|
@@ -1296,6 +1192,82 @@ function usePresence(conversationId) {
|
|
|
1296
1192
|
return { isTyping, isAiProcessing, setTyping };
|
|
1297
1193
|
}
|
|
1298
1194
|
|
|
1195
|
+
// src/hooks/use-timeline.ts
|
|
1196
|
+
import { useCallback as useCallback5, useEffect as useEffect3, useRef as useRef4, useState as useState5 } from "react";
|
|
1197
|
+
var EMPTY_ITEMS = [];
|
|
1198
|
+
function useTimeline(conversationId) {
|
|
1199
|
+
const { client, store } = useWidgetContext();
|
|
1200
|
+
const messages = useWidgetStore(
|
|
1201
|
+
(s) => conversationId ? s.messages.get(conversationId) ?? EMPTY_ITEMS : EMPTY_ITEMS
|
|
1202
|
+
);
|
|
1203
|
+
const [isLoading, setIsLoading] = useState5(false);
|
|
1204
|
+
const [hasMore, setHasMore] = useState5(false);
|
|
1205
|
+
const cursorRef = useRef4(void 0);
|
|
1206
|
+
const clientRef = useRef4(client);
|
|
1207
|
+
const storeRef = useRef4(store);
|
|
1208
|
+
clientRef.current = client;
|
|
1209
|
+
storeRef.current = store;
|
|
1210
|
+
useEffect3(() => {
|
|
1211
|
+
if (!conversationId) return;
|
|
1212
|
+
if (conversationId.startsWith("temp-")) return;
|
|
1213
|
+
let cancelled = false;
|
|
1214
|
+
const existingMessages = storeRef.current.getState().messages.get(conversationId);
|
|
1215
|
+
const hasSeed = !!existingMessages && existingMessages.length > 0;
|
|
1216
|
+
if (hasSeed) {
|
|
1217
|
+
storeRef.current.getState().clearUnread(conversationId);
|
|
1218
|
+
clientRef.current.widget.markSeen.mutate({ conversationId }).catch(() => {
|
|
1219
|
+
});
|
|
1220
|
+
return () => {
|
|
1221
|
+
cancelled = true;
|
|
1222
|
+
};
|
|
1223
|
+
}
|
|
1224
|
+
setIsLoading(true);
|
|
1225
|
+
async function load() {
|
|
1226
|
+
try {
|
|
1227
|
+
const data = await clientRef.current.widget.getTimeline.query({
|
|
1228
|
+
conversationId,
|
|
1229
|
+
limit: 50
|
|
1230
|
+
});
|
|
1231
|
+
if (cancelled) return;
|
|
1232
|
+
if (data.items.length > 0) {
|
|
1233
|
+
storeRef.current.getState().setMessages(conversationId, data.items);
|
|
1234
|
+
}
|
|
1235
|
+
setHasMore(data.hasMore);
|
|
1236
|
+
cursorRef.current = data.nextCursor ?? void 0;
|
|
1237
|
+
storeRef.current.getState().clearUnread(conversationId);
|
|
1238
|
+
clientRef.current.widget.markSeen.mutate({ conversationId }).catch(() => {
|
|
1239
|
+
});
|
|
1240
|
+
} catch (err) {
|
|
1241
|
+
console.error("[useTimeline] Failed to load:", err);
|
|
1242
|
+
} finally {
|
|
1243
|
+
if (!cancelled) setIsLoading(false);
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
load();
|
|
1247
|
+
return () => {
|
|
1248
|
+
cancelled = true;
|
|
1249
|
+
};
|
|
1250
|
+
}, [conversationId]);
|
|
1251
|
+
const loadMore = useCallback5(async () => {
|
|
1252
|
+
if (!conversationId || !hasMore || isLoading) return;
|
|
1253
|
+
setIsLoading(true);
|
|
1254
|
+
try {
|
|
1255
|
+
const data = await clientRef.current.widget.getTimeline.query({
|
|
1256
|
+
conversationId,
|
|
1257
|
+
cursor: cursorRef.current,
|
|
1258
|
+
limit: 50
|
|
1259
|
+
});
|
|
1260
|
+
const existing = storeRef.current.getState().messages.get(conversationId) ?? [];
|
|
1261
|
+
storeRef.current.getState().setMessages(conversationId, [...data.items, ...existing]);
|
|
1262
|
+
setHasMore(data.hasMore);
|
|
1263
|
+
cursorRef.current = data.nextCursor ?? void 0;
|
|
1264
|
+
} finally {
|
|
1265
|
+
setIsLoading(false);
|
|
1266
|
+
}
|
|
1267
|
+
}, [conversationId, hasMore, isLoading]);
|
|
1268
|
+
return { items: messages, isLoading, hasMore, loadMore };
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1299
1271
|
// src/hooks/use-unread.ts
|
|
1300
1272
|
function useUnread() {
|
|
1301
1273
|
const totalUnread = useWidgetStore(
|
|
@@ -1304,28 +1276,18 @@ function useUnread() {
|
|
|
1304
1276
|
return { totalUnread };
|
|
1305
1277
|
}
|
|
1306
1278
|
|
|
1307
|
-
// src/hooks/use-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
const
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
// src/hooks/use-i18n.ts
|
|
1320
|
-
import { useEffect as useEffect5, useState as useState5 } from "react";
|
|
1321
|
-
function useI18n() {
|
|
1322
|
-
const [rev, setRev] = useState5(() => getI18nRevision());
|
|
1323
|
-
useEffect5(() => {
|
|
1324
|
-
return onLocaleChange(() => {
|
|
1325
|
-
setRev(getI18nRevision());
|
|
1326
|
-
});
|
|
1327
|
-
}, []);
|
|
1328
|
-
return rev;
|
|
1279
|
+
// src/hooks/use-widget.ts
|
|
1280
|
+
function useWidget() {
|
|
1281
|
+
const { client } = useWidgetContext();
|
|
1282
|
+
const isReady = useWidgetStore((s) => s.isReady);
|
|
1283
|
+
const isConnecting = useWidgetStore((s) => s.isConnecting);
|
|
1284
|
+
const visitor = useWidgetStore((s) => s.visitor);
|
|
1285
|
+
const website = useWidgetStore((s) => s.website);
|
|
1286
|
+
const agentConfig = useWidgetStore((s) => s.agentConfig);
|
|
1287
|
+
async function identify(data) {
|
|
1288
|
+
return client.widget.identify.mutate(data);
|
|
1289
|
+
}
|
|
1290
|
+
return { isReady, isConnecting, visitor, website, agentConfig, identify };
|
|
1329
1291
|
}
|
|
1330
1292
|
|
|
1331
1293
|
// src/themed/composer.tsx
|
|
@@ -1359,7 +1321,7 @@ import { useCallback as useCallback6, useLayoutEffect, useRef as useRef6, useSta
|
|
|
1359
1321
|
import { createPortal } from "react-dom";
|
|
1360
1322
|
|
|
1361
1323
|
// src/themed/content/simple-markdown.tsx
|
|
1362
|
-
import { useEffect as
|
|
1324
|
+
import { useEffect as useEffect4, useRef as useRef5 } from "react";
|
|
1363
1325
|
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
1364
1326
|
function renderMarkdown(md) {
|
|
1365
1327
|
const codeBlocks = [];
|
|
@@ -1421,7 +1383,7 @@ function MarkdownContent({
|
|
|
1421
1383
|
}) {
|
|
1422
1384
|
const rootRef = useRef5(null);
|
|
1423
1385
|
const html = bodyHtml ?? (bodyMarkdown ? renderMarkdown(bodyMarkdown) : "");
|
|
1424
|
-
|
|
1386
|
+
useEffect4(() => {
|
|
1425
1387
|
const root = rootRef.current;
|
|
1426
1388
|
if (!root) return;
|
|
1427
1389
|
const handler = (e) => {
|
|
@@ -1929,7 +1891,7 @@ function extractFiles(data) {
|
|
|
1929
1891
|
}
|
|
1930
1892
|
|
|
1931
1893
|
// src/themed/debug-overlay.tsx
|
|
1932
|
-
import { useEffect as
|
|
1894
|
+
import { useEffect as useEffect5, useMemo, useRef as useRef8, useState as useState8 } from "react";
|
|
1933
1895
|
import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
1934
1896
|
var MAX_EVENTS = 60;
|
|
1935
1897
|
function isDebugEnabled() {
|
|
@@ -1956,7 +1918,7 @@ function WidgetDebugOverlay() {
|
|
|
1956
1918
|
const typingUsers = useWidgetStore((s) => s.typingUsers);
|
|
1957
1919
|
const aiProcessingMap = useWidgetStore((s) => s.aiProcessing);
|
|
1958
1920
|
const { store } = useWidgetContext();
|
|
1959
|
-
|
|
1921
|
+
useEffect5(() => {
|
|
1960
1922
|
setEnabled(isDebugEnabled());
|
|
1961
1923
|
}, []);
|
|
1962
1924
|
const pushEvent = useMemo(
|
|
@@ -1971,7 +1933,7 @@ function WidgetDebugOverlay() {
|
|
|
1971
1933
|
},
|
|
1972
1934
|
[]
|
|
1973
1935
|
);
|
|
1974
|
-
|
|
1936
|
+
useEffect5(() => {
|
|
1975
1937
|
if (!enabled) return;
|
|
1976
1938
|
if (typeof window === "undefined" || typeof window.fetch !== "function") return;
|
|
1977
1939
|
const originalFetch = window.fetch.bind(window);
|
|
@@ -2003,7 +1965,7 @@ function WidgetDebugOverlay() {
|
|
|
2003
1965
|
window.fetch = originalFetch;
|
|
2004
1966
|
};
|
|
2005
1967
|
}, [enabled, pushEvent]);
|
|
2006
|
-
|
|
1968
|
+
useEffect5(() => {
|
|
2007
1969
|
if (!enabled) return;
|
|
2008
1970
|
const original = store.getState().handleRealtimeEvent;
|
|
2009
1971
|
store.setState({
|
|
@@ -2022,7 +1984,7 @@ function WidgetDebugOverlay() {
|
|
|
2022
1984
|
[activeMessages]
|
|
2023
1985
|
);
|
|
2024
1986
|
const prevSigRef = useRef8(lastMsgSignature);
|
|
2025
|
-
|
|
1987
|
+
useEffect5(() => {
|
|
2026
1988
|
if (!enabled) return;
|
|
2027
1989
|
if (prevSigRef.current !== lastMsgSignature) {
|
|
2028
1990
|
prevSigRef.current = lastMsgSignature;
|
|
@@ -2175,10 +2137,10 @@ var styles = {
|
|
|
2175
2137
|
};
|
|
2176
2138
|
|
|
2177
2139
|
// src/themed/content/content-tab-view.tsx
|
|
2178
|
-
import { useCallback as useCallback11, useEffect as
|
|
2140
|
+
import { useCallback as useCallback11, useEffect as useEffect8 } from "react";
|
|
2179
2141
|
|
|
2180
2142
|
// src/hooks/use-content-tab.ts
|
|
2181
|
-
import { useCallback as useCallback8, useEffect as
|
|
2143
|
+
import { useCallback as useCallback8, useEffect as useEffect6, useState as useState9 } from "react";
|
|
2182
2144
|
var EMPTY_TABS = [];
|
|
2183
2145
|
function listKey(tabId, lang) {
|
|
2184
2146
|
return `flat:${tabId}:${lang}:list`;
|
|
@@ -2293,7 +2255,7 @@ function useContentTab(tabId) {
|
|
|
2293
2255
|
const clearDetail = useCallback8(() => {
|
|
2294
2256
|
setDetail(null);
|
|
2295
2257
|
}, []);
|
|
2296
|
-
|
|
2258
|
+
useEffect6(() => {
|
|
2297
2259
|
if (tab && entries.length === 0) {
|
|
2298
2260
|
fetchEntries({ page: 1, reset: true });
|
|
2299
2261
|
}
|
|
@@ -2439,7 +2401,7 @@ function ContentEntryCard({
|
|
|
2439
2401
|
}
|
|
2440
2402
|
|
|
2441
2403
|
// src/themed/content/grouped-content-tab-view.tsx
|
|
2442
|
-
import { useEffect as
|
|
2404
|
+
import { useEffect as useEffect7, useRef as useRef11 } from "react";
|
|
2443
2405
|
|
|
2444
2406
|
// src/hooks/use-grouped-content-tab.ts
|
|
2445
2407
|
import { useCallback as useCallback10, useRef as useRef10, useState as useState11 } from "react";
|
|
@@ -2683,16 +2645,16 @@ function GroupedContentTabView({
|
|
|
2683
2645
|
fetchDetail,
|
|
2684
2646
|
clearCategory
|
|
2685
2647
|
} = useGroupedContentTab(tab.id);
|
|
2686
|
-
|
|
2648
|
+
useEffect7(() => {
|
|
2687
2649
|
initFetch();
|
|
2688
2650
|
}, [initFetch]);
|
|
2689
|
-
|
|
2651
|
+
useEffect7(() => {
|
|
2690
2652
|
if (detailSlug && subView === "content-detail") {
|
|
2691
2653
|
fetchDetail(detailSlug);
|
|
2692
2654
|
}
|
|
2693
2655
|
}, [detailSlug, subView, fetchDetail]);
|
|
2694
2656
|
const prevSubView = useRef11(subView);
|
|
2695
|
-
|
|
2657
|
+
useEffect7(() => {
|
|
2696
2658
|
if (prevSubView.current === "content-category" && subView === null) {
|
|
2697
2659
|
clearCategory();
|
|
2698
2660
|
}
|
|
@@ -2786,7 +2748,7 @@ function FlatContentTabView({
|
|
|
2786
2748
|
loadMore,
|
|
2787
2749
|
fetchDetail
|
|
2788
2750
|
} = useContentTab(tab.id);
|
|
2789
|
-
|
|
2751
|
+
useEffect8(() => {
|
|
2790
2752
|
if (detailSlug) fetchDetail(detailSlug);
|
|
2791
2753
|
}, [detailSlug, fetchDetail]);
|
|
2792
2754
|
const handleSearch = useCallback11(
|
|
@@ -2829,7 +2791,7 @@ function ContentTabIconComponent({ icon }) {
|
|
|
2829
2791
|
}
|
|
2830
2792
|
|
|
2831
2793
|
// src/themed/message-list.tsx
|
|
2832
|
-
import React, { useEffect as
|
|
2794
|
+
import React, { useEffect as useEffect9, useRef as useRef12 } from "react";
|
|
2833
2795
|
import { jsx as jsx11, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
2834
2796
|
function parseParts(raw) {
|
|
2835
2797
|
if (!raw) return null;
|
|
@@ -2857,7 +2819,7 @@ function MessageList({
|
|
|
2857
2819
|
const prevItemCountRef = useRef12(items.length);
|
|
2858
2820
|
const isInitialLoadRef = useRef12(true);
|
|
2859
2821
|
const agentName = useWidgetStore((s) => s.agentConfig?.name) ?? t("widget.better_ai");
|
|
2860
|
-
|
|
2822
|
+
useEffect9(() => {
|
|
2861
2823
|
const prevCount = prevItemCountRef.current;
|
|
2862
2824
|
const newCount = items.length;
|
|
2863
2825
|
prevItemCountRef.current = newCount;
|
|
@@ -2872,12 +2834,12 @@ function MessageList({
|
|
|
2872
2834
|
isInitialLoadRef.current = false;
|
|
2873
2835
|
}
|
|
2874
2836
|
}, [items]);
|
|
2875
|
-
|
|
2837
|
+
useEffect9(() => {
|
|
2876
2838
|
if (isTyping || isAiProcessing) {
|
|
2877
2839
|
bottomRef.current?.scrollIntoView({ behavior: "smooth" });
|
|
2878
2840
|
}
|
|
2879
2841
|
}, [isTyping, isAiProcessing]);
|
|
2880
|
-
|
|
2842
|
+
useEffect9(() => {
|
|
2881
2843
|
if (!hasMore || !onLoadMore || !topRef.current) return;
|
|
2882
2844
|
const observer = new IntersectionObserver(
|
|
2883
2845
|
(entries) => {
|
|
@@ -3313,7 +3275,7 @@ function DefaultWidget({ initialOpen = false } = {}) {
|
|
|
3313
3275
|
handleClose();
|
|
3314
3276
|
}
|
|
3315
3277
|
}, [handleClose]);
|
|
3316
|
-
|
|
3278
|
+
useEffect10(() => {
|
|
3317
3279
|
if (!isOpen || !windowRef.current) return;
|
|
3318
3280
|
const vv = window.visualViewport;
|
|
3319
3281
|
if (!vv) return;
|
|
@@ -3330,7 +3292,7 @@ function DefaultWidget({ initialOpen = false } = {}) {
|
|
|
3330
3292
|
win.style.removeProperty("--hw-visible-height");
|
|
3331
3293
|
};
|
|
3332
3294
|
}, [isOpen]);
|
|
3333
|
-
|
|
3295
|
+
useEffect10(() => {
|
|
3334
3296
|
if (!isOpen || !windowRef.current) return;
|
|
3335
3297
|
const win = windowRef.current;
|
|
3336
3298
|
const handleKeyDown = (e) => {
|
|
@@ -3346,7 +3308,7 @@ function DefaultWidget({ initialOpen = false } = {}) {
|
|
|
3346
3308
|
win.addEventListener("keydown", handleKeyDown);
|
|
3347
3309
|
return () => win.removeEventListener("keydown", handleKeyDown);
|
|
3348
3310
|
}, [isOpen, subView, handleClose, goBack]);
|
|
3349
|
-
|
|
3311
|
+
useEffect10(() => {
|
|
3350
3312
|
if (!isOpen || !windowRef.current) return;
|
|
3351
3313
|
const win = windowRef.current;
|
|
3352
3314
|
const getFocusable = () => win.querySelectorAll(
|
|
@@ -4091,7 +4053,7 @@ function MessagePreview({ onOpen }) {
|
|
|
4091
4053
|
const dismissPreview = useWidgetStore((s) => s.dismissPreview);
|
|
4092
4054
|
const agentName = useWidgetStore((s) => s.agentConfig?.name) ?? "Support";
|
|
4093
4055
|
const [closing, setClosing] = useState12(false);
|
|
4094
|
-
|
|
4056
|
+
useEffect10(() => {
|
|
4095
4057
|
if (!preview) return;
|
|
4096
4058
|
const timer = setTimeout(() => {
|
|
4097
4059
|
setClosing(true);
|
|
@@ -4139,6 +4101,40 @@ function MessagePreview({ onOpen }) {
|
|
|
4139
4101
|
}
|
|
4140
4102
|
);
|
|
4141
4103
|
}
|
|
4104
|
+
|
|
4105
|
+
// src/helpway-widget.tsx
|
|
4106
|
+
import { jsx as jsx13 } from "react/jsx-runtime";
|
|
4107
|
+
function HelpwayWidget({
|
|
4108
|
+
apiKey,
|
|
4109
|
+
apiUrl,
|
|
4110
|
+
locale,
|
|
4111
|
+
initialConversationId,
|
|
4112
|
+
onReady,
|
|
4113
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
4114
|
+
src
|
|
4115
|
+
}) {
|
|
4116
|
+
const resolvedApiUrl = apiUrl ?? (src ? src.replace(/\/widget\.js$/, "") : void 0);
|
|
4117
|
+
return /* @__PURE__ */ jsx13(
|
|
4118
|
+
SupportProvider,
|
|
4119
|
+
{
|
|
4120
|
+
apiKey,
|
|
4121
|
+
apiUrl: resolvedApiUrl,
|
|
4122
|
+
locale,
|
|
4123
|
+
initialConversationId,
|
|
4124
|
+
onReady,
|
|
4125
|
+
children: /* @__PURE__ */ jsx13(DefaultWidget, {})
|
|
4126
|
+
}
|
|
4127
|
+
);
|
|
4128
|
+
}
|
|
4129
|
+
|
|
4130
|
+
// src/hooks/use-realtime.ts
|
|
4131
|
+
import { useEffect as useEffect11 } from "react";
|
|
4132
|
+
function useRealtime(handler) {
|
|
4133
|
+
const { ws } = useWidgetContext();
|
|
4134
|
+
useEffect11(() => {
|
|
4135
|
+
return ws.onEvent(handler);
|
|
4136
|
+
}, [ws, handler]);
|
|
4137
|
+
}
|
|
4142
4138
|
export {
|
|
4143
4139
|
Composer,
|
|
4144
4140
|
DefaultWidget,
|