@helpway/react 0.1.0 → 0.2.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.ts +44 -34
- package/dist/index.js +269 -248
- package/dist/index.js.map +1 -1
- package/package.json +15 -20
- package/src/themed/styles.css +26 -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,65 @@ function MessagePreview({ onOpen }) {
|
|
|
4139
4101
|
}
|
|
4140
4102
|
);
|
|
4141
4103
|
}
|
|
4104
|
+
|
|
4105
|
+
// #style-inject:#style-inject
|
|
4106
|
+
function styleInject(css, { insertAt } = {}) {
|
|
4107
|
+
if (!css || typeof document === "undefined") return;
|
|
4108
|
+
const head = document.head || document.getElementsByTagName("head")[0];
|
|
4109
|
+
const style = document.createElement("style");
|
|
4110
|
+
style.type = "text/css";
|
|
4111
|
+
if (insertAt === "top") {
|
|
4112
|
+
if (head.firstChild) {
|
|
4113
|
+
head.insertBefore(style, head.firstChild);
|
|
4114
|
+
} else {
|
|
4115
|
+
head.appendChild(style);
|
|
4116
|
+
}
|
|
4117
|
+
} else {
|
|
4118
|
+
head.appendChild(style);
|
|
4119
|
+
}
|
|
4120
|
+
if (style.styleSheet) {
|
|
4121
|
+
style.styleSheet.cssText = css;
|
|
4122
|
+
} else {
|
|
4123
|
+
style.appendChild(document.createTextNode(css));
|
|
4124
|
+
}
|
|
4125
|
+
}
|
|
4126
|
+
|
|
4127
|
+
// src/themed/styles.css
|
|
4128
|
+
styleInject(':host {\n all: initial;\n font-family:\n -apple-system,\n BlinkMacSystemFont,\n "Segoe UI",\n Roboto,\n "Helvetica Neue",\n Arial,\n sans-serif;\n font-size: 14px;\n line-height: 1.5;\n color: #1a1a1a;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\n.hw-widget {\n font-family:\n -apple-system,\n BlinkMacSystemFont,\n "Segoe UI",\n Roboto,\n "Helvetica Neue",\n Arial,\n sans-serif;\n font-size: 14px;\n line-height: 1.5;\n color: #1a1a1a;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\n.hw-widget *,\n.hw-widget *::before,\n.hw-widget *::after {\n box-sizing: border-box;\n margin: 0;\n padding: 0;\n}\n.hw-widget {\n --hw-primary: #171717;\n --hw-primary-hover: #2a2a2a;\n --hw-focus-border: #9ca3af;\n --hw-focus-ring: rgba(0, 0, 0, 0.06);\n --hw-accent: #6366f1;\n --hw-bg: #ffffff;\n --hw-bg-subtle: #fafafa;\n --hw-bg-hover: #f5f5f5;\n --hw-border: #f0f0f0;\n --hw-border-md: #e5e5e5;\n --hw-text: #111;\n --hw-text-1: #111;\n --hw-text-2: #666;\n --hw-text-3: #999;\n --hw-text-4: #bbb;\n --hw-radius: 16px;\n --hw-visitor-bubble-bg: #f0f0f0;\n --hw-visitor-bubble-text: #1a1a1a;\n --hw-font:\n -apple-system,\n BlinkMacSystemFont,\n "Segoe UI",\n Roboto,\n "Helvetica Neue",\n Arial,\n sans-serif;\n}\n.hw-agent-img {\n width: 100%;\n height: 100%;\n object-fit: cover;\n}\n.hw-agent-icon {\n width: 100%;\n height: 100%;\n border-radius: inherit;\n}\n.hw-widget :focus-visible {\n outline: 2px solid var(--hw-primary);\n outline-offset: 2px;\n}\n.hw-widget :focus:not(:focus-visible) {\n outline: none;\n}\n.hw-widget {\n position: fixed;\n bottom: 20px;\n right: var(--hw-position-right, 20px);\n left: var(--hw-position-left, auto);\n z-index: 999999;\n font-family: var(--hw-font);\n}\n.hw-bubble {\n width: 48px;\n height: 48px;\n border-radius: 14px;\n background: var(--hw-primary);\n color: white;\n border: none;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);\n transition: transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1), box-shadow 0.2s ease;\n position: relative;\n overflow: visible;\n}\n.hw-bubble--rounded {\n border-radius: 14px;\n}\n.hw-bubble--square {\n border-radius: 8px;\n}\n.hw-bubble--pill {\n border-radius: 24px;\n}\n.hw-bubble--launcher-bubble,\n.hw-bubble--launcher-help {\n width: auto;\n padding: 0 16px 0 12px;\n gap: 8px;\n border-radius: 24px;\n}\n.hw-bubble--launcher-bubble.hw-bubble--square,\n.hw-bubble--launcher-help.hw-bubble--square {\n border-radius: 8px;\n}\n.hw-bubble--launcher-bubble.hw-bubble--rounded,\n.hw-bubble--launcher-help.hw-bubble--rounded {\n border-radius: 14px;\n}\n.hw-bubble-label {\n font-size: 14px;\n font-weight: 600;\n white-space: nowrap;\n line-height: 1;\n position: static;\n opacity: 1;\n transform: none;\n}\n.hw-bubble--launcher-bubble .hw-bubble-icon,\n.hw-bubble--launcher-help .hw-bubble-icon {\n position: static;\n}\n.hw-bubble:hover {\n transform: scale(1.05);\n box-shadow: 0 6px 20px rgba(0, 0, 0, 0.2);\n}\n.hw-bubble:active {\n transform: scale(0.95);\n}\n.hw-bubble--open {\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);\n}\n.hw-bubble:focus-visible {\n outline: 2px solid white;\n outline-offset: 2px;\n}\n.hw-bubble-icon {\n display: flex;\n align-items: center;\n justify-content: center;\n position: absolute;\n transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1), opacity 0.2s ease;\n}\n.hw-bubble-icon svg {\n width: 22px;\n height: 22px;\n}\n.hw-bubble-icon--hidden {\n transform: scale(0.3) rotate(90deg);\n opacity: 0;\n}\n.hw-bubble-icon--bot {\n}\n.hw-bubble--avatar .hw-bubble-icon--bot {\n width: 100%;\n height: 100%;\n}\n.hw-bubble--avatar .hw-bubble-icon--bot .hw-agent-img {\n width: 100%;\n height: 100%;\n border-radius: 0;\n}\n.hw-bubble-icon--close svg {\n width: 22px;\n height: 22px;\n}\n.hw-bubble-badge {\n position: absolute;\n top: -4px;\n right: -4px;\n min-width: 20px;\n height: 20px;\n border-radius: 10px;\n background: var(--hw-primary);\n color: white;\n font-size: 11px;\n font-weight: 700;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 0 5px;\n border: 2px solid white;\n animation: hw-badge-pop 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);\n}\n@keyframes hw-badge-pop {\n from {\n transform: scale(0);\n }\n to {\n transform: scale(1);\n }\n}\n.hw-bubble-badge::after {\n content: "";\n position: absolute;\n inset: -2px;\n border-radius: inherit;\n background: var(--hw-primary);\n opacity: 0;\n animation: hw-badge-pulse 2s ease-out infinite;\n}\n@keyframes hw-badge-pulse {\n 0% {\n opacity: 0.5;\n transform: scale(1);\n }\n 100% {\n opacity: 0;\n transform: scale(1.8);\n }\n}\n.hw-window {\n position: absolute;\n bottom: 72px;\n right: var(--hw-window-right, 0);\n left: var(--hw-window-left, auto);\n width: 440px;\n height: 640px;\n background: var(--hw-bg-subtle);\n color: var(--hw-text);\n border-radius: var(--hw-radius);\n box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12), 0 0 0 1px rgba(0, 0, 0, 0.04);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n animation: hw-window-open 0.35s cubic-bezier(0.34, 1.56, 0.64, 1);\n}\n@keyframes hw-window-open {\n from {\n opacity: 0;\n transform: translateY(12px) scale(0.96);\n }\n to {\n opacity: 1;\n transform: translateY(0) scale(1);\n }\n}\n.hw-window--closing {\n animation: hw-window-close 0.2s ease forwards;\n}\n@keyframes hw-window-close {\n from {\n opacity: 1;\n transform: translateY(0) scale(1);\n }\n to {\n opacity: 0;\n transform: translateY(8px) scale(0.98);\n }\n}\n.hw-content {\n flex: 1;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n min-height: 0;\n}\n@media (prefers-reduced-motion: reduce) {\n .hw-window,\n .hw-window--closing,\n .hw-content,\n .hw-msg,\n .hw-bubble-icon,\n .hw-bubble-badge,\n .hw-bubble-badge::after,\n .hw-typing-dot,\n .hw-loading-dot {\n animation-duration: 0.01ms !important;\n transition-duration: 0.01ms !important;\n }\n}\n.hw-hero {\n flex-shrink: 0;\n background:\n linear-gradient(\n 160deg,\n color-mix(in srgb, var(--hw-primary) 60%, black) 0%,\n var(--hw-primary) 50%,\n color-mix(in srgb, var(--hw-primary) 40%, black) 100%);\n color: white;\n padding: 20px 22px 40px;\n}\n.hw-hero-top {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 16px;\n}\n.hw-hero-close {\n width: 30px;\n height: 30px;\n border-radius: 50%;\n border: none;\n background: rgba(255, 255, 255, 0.1);\n color: rgba(255, 255, 255, 0.6);\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: background 0.15s, color 0.15s;\n}\n.hw-hero-close:hover {\n background: rgba(255, 255, 255, 0.2);\n color: white;\n}\n.hw-hero-close:active {\n transform: scale(0.92);\n transition:\n transform 0.14s cubic-bezier(0.23, 1, 0.32, 1),\n background 0.15s,\n color 0.15s;\n}\n.hw-hero-close svg {\n width: 20px;\n height: 20px;\n}\n.hw-hero-bot-icon {\n width: 36px;\n height: 36px;\n flex-shrink: 0;\n color: white;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: 10px;\n overflow: hidden;\n}\n.hw-hero-bot-icon svg {\n width: 32px;\n height: 32px;\n}\n.hw-hero-greeting {\n font-size: 26px;\n font-weight: 700;\n line-height: 1.15;\n margin-bottom: 6px;\n}\n.hw-hero-greeting-name {\n font-weight: 300;\n}\n.hw-hero-greeting-question {\n font-weight: 700;\n}\n.hw-hero-subtitle {\n font-size: 15px;\n color: rgba(255, 255, 255, 0.5);\n line-height: 1.35;\n}\n.hw-sheet {\n flex: 1;\n overflow-y: auto;\n min-height: 0;\n background: #ffffff;\n border-radius: 20px 20px 0 0;\n margin-top: -24px;\n box-shadow: 0 -4px 16px rgba(0, 0, 0, 0.08);\n position: relative;\n z-index: 1;\n}\n.hw-sheet-inner {\n padding: 20px 16px;\n display: flex;\n flex-direction: column;\n gap: 12px;\n}\n.hw-header {\n display: flex;\n align-items: center;\n padding: 12px 16px;\n background: var(--hw-primary);\n color: white;\n flex-shrink: 0;\n gap: 10px;\n}\n.hw-header--sub {\n justify-content: space-between;\n background: transparent;\n color: var(--hw-text);\n border-bottom: 1px solid var(--hw-border);\n}\n.hw-header-back {\n width: 28px;\n height: 28px;\n border-radius: 8px;\n border: none;\n background: transparent;\n color: var(--hw-text-3);\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: color 0.15s, background 0.15s;\n flex-shrink: 0;\n}\n.hw-header-back svg {\n width: 18px;\n height: 18px;\n}\n.hw-header-back:hover {\n color: var(--hw-text);\n background: var(--hw-bg-subtle);\n}\n.hw-header-identity {\n display: flex;\n align-items: center;\n gap: 10px;\n flex: 1;\n min-width: 0;\n}\n.hw-header-bot-icon {\n width: 28px;\n height: 28px;\n flex-shrink: 0;\n color: var(--hw-text);\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: 8px;\n overflow: hidden;\n}\n.hw-header-bot-icon svg {\n width: 24px;\n height: 24px;\n}\n.hw-header-info {\n flex: 1;\n min-width: 0;\n}\n.hw-header-name {\n font-size: 14px;\n font-weight: 600;\n line-height: 1.3;\n}\n.hw-header-status {\n font-size: 12px;\n color: var(--hw-text-3);\n line-height: 1.3;\n}\n.hw-header-close {\n width: 28px;\n height: 28px;\n border-radius: 8px;\n border: none;\n background: transparent;\n color: var(--hw-text-3);\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n transition:\n background-color 140ms cubic-bezier(0.23, 1, 0.32, 1),\n color 140ms cubic-bezier(0.23, 1, 0.32, 1),\n transform 140ms cubic-bezier(0.23, 1, 0.32, 1);\n flex-shrink: 0;\n}\n.hw-header-close svg {\n width: 20px;\n height: 20px;\n}\n.hw-header-close:hover {\n background: var(--hw-bg-subtle);\n color: var(--hw-text);\n}\n.hw-header-close:active {\n transform: scale(0.92);\n}\n.hw-header--centered {\n justify-content: space-between;\n background: transparent;\n color: var(--hw-text);\n border-bottom: 1px solid var(--hw-border);\n}\n.hw-header--centered .hw-header-title {\n position: absolute;\n left: 50%;\n transform: translateX(-50%);\n max-width: calc(100% - 88px);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n text-align: center;\n}\n.hw-header-spacer {\n width: 28px;\n flex-shrink: 0;\n}\n.hw-header--messages {\n justify-content: space-between;\n background: transparent;\n color: var(--hw-text);\n border-bottom: 1px solid var(--hw-border);\n}\n.hw-header-title {\n font-size: 16px;\n font-weight: 600;\n}\n.hw-header-actions {\n display: flex;\n align-items: center;\n gap: 6px;\n}\n.hw-header-action {\n width: 28px;\n height: 28px;\n border-radius: 8px;\n border: none;\n background: transparent;\n color: var(--hw-text-3);\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n transition:\n background-color 140ms cubic-bezier(0.23, 1, 0.32, 1),\n color 140ms cubic-bezier(0.23, 1, 0.32, 1),\n transform 140ms cubic-bezier(0.23, 1, 0.32, 1);\n}\n.hw-header-action svg {\n width: 16px;\n height: 16px;\n}\n.hw-header-action:hover {\n background: var(--hw-bg-subtle);\n color: var(--hw-text);\n}\n.hw-header-action:active {\n transform: scale(0.94);\n}\n.hw-card {\n display: flex;\n align-items: center;\n gap: 12px;\n width: 100%;\n padding: 14px 16px;\n border-radius: 14px;\n background: var(--hw-bg);\n border: 1px solid rgba(0, 0, 0, 0.06);\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06), 0 0 0 1px rgba(0, 0, 0, 0.03);\n cursor: pointer;\n transition: box-shadow 180ms cubic-bezier(0.23, 1, 0.32, 1), transform 180ms cubic-bezier(0.23, 1, 0.32, 1);\n text-align: left;\n color: var(--hw-text);\n font-family: inherit;\n}\n.hw-card:hover {\n box-shadow: 0 6px 20px rgba(0, 0, 0, 0.08), 0 0 0 1px rgba(0, 0, 0, 0.05);\n transform: translateY(-1px);\n}\n.hw-card:active {\n transform: translateY(0) scale(0.99);\n transition-duration: 120ms;\n}\n.hw-card-body {\n flex: 1;\n min-width: 0;\n}\n.hw-card-title {\n font-size: 14px;\n font-weight: 600;\n line-height: 1.3;\n color: var(--hw-text);\n}\n.hw-card-meta {\n font-size: 12px;\n color: var(--hw-text-3);\n line-height: 1.3;\n margin-top: 1px;\n}\n.hw-card-arrow {\n width: 32px;\n height: 32px;\n border-radius: 50%;\n background: var(--hw-primary);\n color: white;\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n}\n.hw-card-arrow svg {\n width: 14px;\n height: 14px;\n}\n.hw-msgs-tab {\n flex: 1;\n overflow-y: auto;\n padding: 12px 16px;\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n.hw-recent-card {\n border-radius: 14px;\n background: var(--hw-bg);\n border: 1px solid rgba(0, 0, 0, 0.06);\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06), 0 0 0 1px rgba(0, 0, 0, 0.03);\n overflow: hidden;\n}\n.hw-recent-header {\n font-size: 12px;\n font-weight: 600;\n color: var(--hw-text-2);\n padding: 12px 14px 0;\n}\n.hw-recent-row {\n display: flex;\n align-items: center;\n gap: 10px;\n width: 100%;\n padding: 10px 14px;\n border: none;\n border-bottom: 1px solid var(--hw-border);\n background: none;\n cursor: pointer;\n text-align: left;\n font-family: inherit;\n color: var(--hw-text);\n transition: background-color 140ms cubic-bezier(0.23, 1, 0.32, 1), transform 140ms cubic-bezier(0.23, 1, 0.32, 1);\n position: relative;\n}\n.hw-recent-row:hover {\n background: rgba(0, 0, 0, 0.03);\n}\n.hw-recent-row:active {\n transform: scale(0.995);\n}\n.hw-recent-row--last {\n border-bottom: none;\n}\n.hw-recent-row .hw-conv-card-avatar {\n width: 32px;\n height: 32px;\n border-radius: 8px;\n}\n.hw-recent-row .hw-conv-card-avatar svg {\n width: 18px;\n height: 18px;\n}\n.hw-recent-row .hw-conv-card-name {\n font-size: 13px;\n}\n.hw-recent-row .hw-conv-card-preview {\n font-size: 12px;\n}\n.hw-recent-row--unread .hw-conv-card-name {\n color: var(--hw-text);\n}\n.hw-recent-row--unread .hw-conv-card-time {\n color: var(--hw-accent);\n font-weight: 500;\n}\n.hw-recent-row-badge {\n min-width: 18px;\n height: 18px;\n border-radius: 9px;\n background: var(--hw-primary);\n color: white;\n font-size: 11px;\n font-weight: 600;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 0 5px;\n flex-shrink: 0;\n font-variant-numeric: tabular-nums;\n}\n.hw-recent-see-all {\n display: block;\n width: 100%;\n padding: 10px 14px;\n text-align: center;\n font-size: 13px;\n font-weight: 500;\n color: var(--hw-text);\n background: none;\n border: none;\n border-top: 1px solid var(--hw-border);\n cursor: pointer;\n font-family: inherit;\n transition: background 0.1s;\n}\n.hw-recent-see-all:hover {\n background: rgba(0, 0, 0, 0.02);\n}\n.hw-conv-card {\n position: relative;\n display: flex;\n flex-direction: column;\n gap: 4px;\n width: 100%;\n padding: 14px 16px;\n border-radius: 14px;\n background: var(--hw-bg);\n border: 1px solid rgba(0, 0, 0, 0.06);\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06), 0 0 0 1px rgba(0, 0, 0, 0.03);\n cursor: pointer;\n transition: box-shadow 0.15s, transform 0.1s;\n text-align: left;\n font-family: inherit;\n color: var(--hw-text);\n}\n.hw-conv-card:hover {\n box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1), 0 0 0 1px rgba(0, 0, 0, 0.04);\n}\n.hw-conv-card:active {\n transform: scale(0.99);\n}\n.hw-conv-card-row {\n display: flex;\n align-items: center;\n gap: 12px;\n}\n.hw-conv-card-avatar {\n width: 36px;\n height: 36px;\n border-radius: 10px;\n background: var(--hw-bg-subtle);\n color: var(--hw-text-3);\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n overflow: hidden;\n}\n.hw-conv-card-avatar svg {\n width: 20px;\n height: 20px;\n}\n.hw-conv-card-content {\n flex: 1;\n min-width: 0;\n}\n.hw-conv-card-top {\n display: flex;\n align-items: baseline;\n gap: 8px;\n}\n.hw-conv-card-name {\n flex: 1;\n font-size: 14px;\n font-weight: 600;\n color: var(--hw-text);\n}\n.hw-conv-card-time {\n font-size: 11px;\n color: var(--hw-text-3);\n flex-shrink: 0;\n font-variant-numeric: tabular-nums;\n}\n.hw-conv-card-preview {\n font-size: 13px;\n color: var(--hw-text-2);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n line-height: 1.4;\n margin-top: 1px;\n}\n.hw-conv-card--unread .hw-conv-card-name {\n color: var(--hw-text);\n}\n.hw-conv-card--unread .hw-conv-card-time {\n color: var(--hw-accent);\n font-weight: 500;\n}\n.hw-conv-card-badge {\n position: absolute;\n top: 12px;\n right: 12px;\n min-width: 18px;\n height: 18px;\n border-radius: 9px;\n background: var(--hw-primary);\n color: white;\n font-size: 11px;\n font-weight: 600;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 0 5px;\n font-variant-numeric: tabular-nums;\n}\n.hw-empty-state {\n flex: 1;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 32px;\n text-align: center;\n}\n.hw-empty-icon-wrap {\n width: 48px;\n height: 48px;\n border-radius: 50%;\n background: var(--hw-bg-subtle);\n display: flex;\n align-items: center;\n justify-content: center;\n margin-bottom: 16px;\n color: var(--hw-text-3);\n}\n.hw-empty-icon-wrap svg {\n width: 24px;\n height: 24px;\n}\n.hw-empty-title {\n font-size: 15px;\n font-weight: 600;\n color: var(--hw-text);\n margin-bottom: 4px;\n}\n.hw-empty-desc {\n font-size: 13px;\n color: var(--hw-text-3);\n margin-bottom: 20px;\n}\n.hw-empty-btn {\n padding: 8px 20px;\n border-radius: 10px;\n background: var(--hw-primary);\n color: white;\n border: none;\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n font-family: inherit;\n transition: background 0.15s;\n}\n.hw-empty-btn:hover {\n background: var(--hw-primary-hover);\n}\n.hw-tabs {\n display: flex;\n border-top: 1px solid var(--hw-border);\n flex-shrink: 0;\n background: var(--hw-bg-subtle);\n}\n.hw-tabs-item {\n flex: 1;\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 2px;\n padding: 8px 0;\n border: none;\n background: none;\n cursor: pointer;\n font-family: inherit;\n font-size: 11px;\n font-weight: 500;\n color: var(--hw-text-3);\n transition: color 0.15s;\n}\n.hw-tabs-item svg {\n width: 22px;\n height: 22px;\n}\n.hw-tabs-item--active {\n color: var(--hw-text);\n}\n.hw-tabs-item:hover {\n color: var(--hw-text-2);\n}\n.hw-tabs-item:active {\n transform: scale(0.94);\n transition: transform 0.14s cubic-bezier(0.23, 1, 0.32, 1), color 0.15s;\n}\n.hw-tabs-icon-wrap {\n position: relative;\n display: inline-flex;\n}\n.hw-tabs-badge {\n position: absolute;\n top: -6px;\n right: -10px;\n min-width: 16px;\n height: 16px;\n border-radius: 8px;\n background: var(--hw-primary);\n color: white;\n font-size: 10px;\n font-weight: 700;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 0 4px;\n font-variant-numeric: tabular-nums;\n}\n.hw-messages {\n flex: 1;\n overflow-y: auto;\n overflow-x: hidden;\n padding: 16px 20px;\n background: var(--hw-bg-subtle);\n display: flex;\n flex-direction: column;\n gap: 2px;\n overscroll-behavior: contain;\n min-width: 0;\n}\n.hw-empty {\n flex: 1;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 32px;\n color: var(--hw-text-3);\n text-align: center;\n}\n.hw-empty-icon {\n width: 40px;\n height: 40px;\n margin-bottom: 12px;\n opacity: 0.3;\n}\n.hw-date-sep {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 12px 0;\n}\n.hw-date-sep-line {\n flex: 1;\n height: 1px;\n background: var(--hw-border-md);\n}\n.hw-date-sep-text {\n font-size: 11px;\n color: var(--hw-text-3);\n font-weight: 500;\n white-space: nowrap;\n}\n.hw-msg {\n display: flex;\n padding: 4px 0;\n max-width: 75%;\n min-width: 0;\n animation: hw-msg-appear 0.25s ease-out;\n}\n@keyframes hw-msg-appear {\n from {\n opacity: 0;\n transform: translateY(6px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n.hw-msg--visitor {\n align-self: flex-end;\n}\n.hw-msg--team,\n.hw-msg--ai {\n align-self: flex-start;\n}\n.hw-msg-body {\n display: flex;\n flex-direction: column;\n min-width: 0;\n max-width: 100%;\n}\n.hw-msg-bubble {\n padding: 10px 14px;\n border-radius: 18px;\n font-size: 14px;\n line-height: 1.45;\n white-space: pre-wrap;\n word-break: break-word;\n overflow-wrap: anywhere;\n max-width: 100%;\n}\n.hw-msg--visitor .hw-msg-bubble {\n background: var(--hw-primary);\n color: white;\n border-bottom-right-radius: 4px;\n}\n.hw-msg--team .hw-msg-bubble {\n background: var(--hw-visitor-bubble-bg);\n color: var(--hw-visitor-bubble-text);\n border-bottom-left-radius: 4px;\n}\n.hw-msg--ai .hw-msg-bubble {\n background: var(--hw-visitor-bubble-bg);\n color: var(--hw-text);\n border-bottom-left-radius: 4px;\n white-space: normal;\n}\n.hw-msg-meta {\n font-size: 11px;\n color: var(--hw-text-3);\n margin-top: 3px;\n padding: 0 4px;\n opacity: 0;\n transition: opacity 0.15s;\n font-variant-numeric: tabular-nums;\n}\n.hw-msg:hover .hw-msg-meta,\n.hw-msg:focus-within .hw-msg-meta {\n opacity: 1;\n}\n.hw-msg--visitor .hw-msg-meta {\n text-align: right;\n}\n.hw-msg--team .hw-msg-body,\n.hw-msg--ai .hw-msg-body {\n align-items: flex-start;\n}\n.hw-msg--visitor .hw-msg-body {\n align-items: flex-end;\n}\n.hw-msg-sender {\n font-size: 11px;\n font-weight: 500;\n color: var(--hw-text-3);\n margin-bottom: 3px;\n padding: 0 4px;\n}\n.hw-typing {\n display: flex;\n flex-direction: column;\n gap: 3px;\n padding: 8px 0;\n width: fit-content;\n animation: hw-msg-appear 0.25s ease-out;\n}\n.hw-typing-label {\n font-size: 11px;\n font-weight: 500;\n color: var(--hw-text-3);\n padding: 0 4px;\n}\n.hw-typing-dots {\n display: flex;\n gap: 3px;\n padding: 10px 14px;\n width: fit-content;\n background: var(--hw-visitor-bubble-bg);\n border-radius: var(--hw-radius);\n border-bottom-left-radius: 4px;\n}\n.hw-typing-dot {\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: var(--hw-text-3);\n animation: hw-typing-bounce 1.4s infinite;\n}\n.hw-typing-dot:nth-child(2) {\n animation-delay: 0.2s;\n}\n.hw-typing-dot:nth-child(3) {\n animation-delay: 0.4s;\n}\n@keyframes hw-typing-bounce {\n 0%, 60%, 100% {\n transform: translateY(0);\n }\n 30% {\n transform: translateY(-4px);\n }\n}\n.hw-loading {\n flex: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n.hw-loading-dots {\n display: flex;\n gap: 4px;\n}\n.hw-loading-dot {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: var(--hw-text-4);\n animation: hw-loading-pulse 1.4s infinite;\n}\n.hw-loading-dot:nth-child(2) {\n animation-delay: 0.2s;\n}\n.hw-loading-dot:nth-child(3) {\n animation-delay: 0.4s;\n}\n@keyframes hw-loading-pulse {\n 0%, 60%, 100% {\n opacity: 0.3;\n transform: scale(0.8);\n }\n 30% {\n opacity: 1;\n transform: scale(1);\n }\n}\n@keyframes hw-skeleton-shimmer {\n 0% {\n background-position: -200px 0;\n }\n 100% {\n background-position: calc(200px + 100%) 0;\n }\n}\n.hw-skeleton {\n background:\n linear-gradient(\n 90deg,\n var(--hw-bg-subtle) 25%,\n rgba(0, 0, 0, 0.04) 50%,\n var(--hw-bg-subtle) 75%);\n background-size: 200px 100%;\n animation: hw-skeleton-shimmer 1.5s ease-in-out infinite;\n border-radius: 6px;\n}\n.hw-skeleton-messages {\n flex: 1;\n display: flex;\n flex-direction: column;\n padding: 16px;\n gap: 16px;\n}\n.hw-skeleton-msg {\n display: flex;\n gap: 10px;\n align-items: flex-start;\n}\n.hw-skeleton-msg--right {\n flex-direction: row-reverse;\n}\n.hw-skeleton-avatar {\n width: 28px;\n height: 28px;\n border-radius: 8px;\n flex-shrink: 0;\n}\n.hw-skeleton-bubble {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n.hw-skeleton-line {\n height: 12px;\n border-radius: 6px;\n}\n.hw-skeleton-convs {\n display: flex;\n flex-direction: column;\n padding: 8px 0;\n}\n.hw-skeleton-conv {\n display: flex;\n gap: 10px;\n align-items: center;\n padding: 10px 16px;\n}\n.hw-skeleton-conv-avatar {\n width: 36px;\n height: 36px;\n border-radius: 8px;\n flex-shrink: 0;\n}\n.hw-skeleton-conv-lines {\n flex: 1;\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n.hw-skeleton-content {\n display: flex;\n flex-direction: column;\n padding: 12px 16px;\n gap: 12px;\n}\n.hw-skeleton-content-item {\n display: flex;\n flex-direction: column;\n gap: 6px;\n padding: 12px;\n border-radius: 10px;\n background: var(--hw-bg);\n}\n.hw-email-card {\n margin: 0 12px;\n padding: 14px 16px 10px;\n border: 1px solid var(--hw-border, rgba(0,0,0,0.08));\n border-radius: 14px;\n background: var(--hw-surface, #fff);\n box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06), 0 4px 12px rgba(0, 0, 0, 0.03);\n animation: hw-email-slide-in 0.25s ease-out;\n}\n@keyframes hw-email-slide-in {\n from {\n opacity: 0;\n transform: translateY(8px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n.hw-email-card-header {\n display: flex;\n align-items: flex-start;\n gap: 10px;\n margin-bottom: 12px;\n}\n.hw-email-card-icon {\n flex-shrink: 0;\n width: 32px;\n height: 32px;\n border-radius: 8px;\n background: var(--hw-brand, #0a0a0a);\n display: flex;\n align-items: center;\n justify-content: center;\n color: #fff;\n}\n.hw-email-card-title {\n font-size: 13px;\n font-weight: 600;\n color: var(--hw-text, #1a1a1a);\n line-height: 1.3;\n margin: 0 0 2px;\n}\n.hw-email-card-subtitle {\n font-size: 12px;\n color: var(--hw-text-muted, #888);\n line-height: 1.4;\n margin: 0;\n}\n.hw-email-card-input {\n width: 100%;\n padding: 9px 12px;\n border-radius: 10px;\n border: 1px solid var(--hw-border, rgba(0,0,0,0.1));\n background: var(--hw-bg, #fafafa);\n color: var(--hw-text, #1a1a1a);\n font-size: 13px;\n outline: none;\n margin-bottom: 8px;\n box-sizing: border-box;\n transition: border-color 0.15s;\n}\n.hw-email-card-input:focus {\n border-color: var(--hw-brand, #0a0a0a);\n box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.06);\n}\n.hw-email-card-input--error {\n border-color: #ef4444;\n}\n.hw-email-card-submit {\n width: 100%;\n padding: 9px;\n border-radius: 10px;\n border: none;\n background: var(--hw-brand, #0a0a0a);\n color: #fff;\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n transition: opacity 0.15s;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n}\n.hw-email-card-submit:hover:not(:disabled) {\n opacity: 0.88;\n}\n.hw-email-card-submit:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n.hw-email-card--success {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n padding: 10px 16px;\n font-size: 13px;\n color: var(--hw-text-muted, #888);\n animation: hw-email-slide-in 0.2s ease-out;\n}\n.hw-email-card--success svg {\n flex-shrink: 0;\n}\n.hw-composer {\n display: flex;\n flex-direction: column;\n padding: 12px 16px;\n background: transparent;\n flex-shrink: 0;\n}\n.hw-composer--compact {\n padding: 8px 12px;\n}\n.hw-composer-box {\n display: flex;\n flex-direction: column;\n border: 2px solid var(--hw-border-md);\n border-radius: 16px;\n overflow: hidden;\n transition: border-color 0.2s;\n background: var(--hw-bg);\n}\n.hw-composer-box:focus-within {\n border-color: var(--hw-focus-border);\n box-shadow: 0 0 0 3px var(--hw-focus-ring);\n}\n.hw-composer-input {\n flex: 1;\n resize: none;\n border: none;\n padding: 12px 16px 4px;\n font-size: 14px;\n font-family: inherit;\n line-height: 1.4;\n outline: none;\n min-height: 40px;\n max-height: 120px;\n background: transparent;\n color: var(--hw-text);\n}\n.hw-composer-input:focus {\n outline: none;\n}\n.hw-composer-input:focus-visible {\n outline: none;\n}\n.hw-composer-input::placeholder {\n color: var(--hw-text-4);\n}\n.hw-composer-toolbar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 4px 10px 10px;\n}\n.hw-composer-toolbar-left {\n display: flex;\n align-items: center;\n gap: 4px;\n}\n.hw-composer-send {\n width: 32px;\n height: 32px;\n border-radius: 50%;\n border: none;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n transition:\n background-color 140ms cubic-bezier(0.23, 1, 0.32, 1),\n color 140ms cubic-bezier(0.23, 1, 0.32, 1),\n transform 140ms cubic-bezier(0.23, 1, 0.32, 1);\n}\n.hw-composer-send:active:not(:disabled) {\n transform: scale(0.9);\n}\n.hw-composer-send--active {\n background: var(--hw-primary);\n color: white;\n}\n.hw-composer-send--inactive {\n background: var(--hw-border);\n color: var(--hw-text-4);\n cursor: default;\n}\n.hw-composer-send svg {\n width: 14px;\n height: 14px;\n}\n.hw-composer-charcount {\n font-size: 11px;\n color: var(--hw-text-3);\n font-variant-numeric: tabular-nums;\n opacity: 1;\n transform: translateY(0);\n transition:\n color 180ms cubic-bezier(0.23, 1, 0.32, 1),\n opacity 180ms cubic-bezier(0.23, 1, 0.32, 1),\n transform 180ms cubic-bezier(0.23, 1, 0.32, 1);\n}\n@starting-style {\n .hw-composer-charcount {\n opacity: 0;\n transform: translateY(3px);\n }\n}\n.hw-composer-charcount--over {\n color: #ef4444;\n font-weight: 600;\n}\n@media (prefers-reduced-motion: reduce) {\n .hw-composer-charcount {\n transform: none;\n transition: color 160ms ease, opacity 160ms ease;\n }\n @starting-style {\n .hw-composer-charcount {\n transform: none;\n }\n }\n}\n.hw-composer-previews {\n display: flex;\n gap: 6px;\n padding: 8px 10px 0;\n overflow-x: auto;\n scrollbar-width: none;\n}\n.hw-composer-previews::-webkit-scrollbar {\n display: none;\n}\n.hw-composer-preview {\n position: relative;\n width: 48px;\n height: 48px;\n border-radius: 8px;\n overflow: hidden;\n flex-shrink: 0;\n background: var(--hw-border);\n cursor: pointer;\n}\n.hw-composer-preview--error {\n outline: 2px solid #ef4444;\n outline-offset: -2px;\n}\n.hw-composer-preview-img {\n width: 100%;\n height: 100%;\n object-fit: cover;\n}\n.hw-composer-preview-file {\n width: 100%;\n height: 100%;\n display: flex;\n align-items: center;\n justify-content: center;\n background: var(--hw-bg-subtle);\n}\n.hw-composer-preview-ext {\n font-size: 10px;\n font-weight: 700;\n color: var(--hw-text-3);\n text-transform: uppercase;\n}\n.hw-composer-preview-loader {\n position: absolute;\n inset: 0;\n background: rgba(0, 0, 0, 0.35);\n display: flex;\n align-items: center;\n justify-content: center;\n}\n.hw-composer-preview-loader::after {\n content: "";\n width: 16px;\n height: 16px;\n border: 2px solid rgba(255, 255, 255, 0.3);\n border-top-color: white;\n border-radius: 50%;\n animation: hw-spin 0.6s linear infinite;\n}\n@keyframes hw-spin {\n to {\n transform: rotate(360deg);\n }\n}\n.hw-composer-preview-badge {\n position: absolute;\n top: 2px;\n right: 2px;\n width: 16px;\n height: 16px;\n border-radius: 50%;\n background: #ef4444;\n color: white;\n font-size: 10px;\n font-weight: 700;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n.hw-composer-preview-remove {\n position: absolute;\n top: 2px;\n right: 2px;\n width: 18px;\n height: 18px;\n border-radius: 50%;\n border: none;\n background: rgba(0, 0, 0, 0.55);\n color: white;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n opacity: 0;\n transition: opacity 0.15s;\n}\n.hw-composer-preview-remove svg {\n width: 10px;\n height: 10px;\n}\n.hw-composer-preview:hover .hw-composer-preview-remove {\n opacity: 1;\n}\n.hw-composer-attach {\n width: 28px;\n height: 28px;\n border: none;\n background: none;\n color: var(--hw-text-3);\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: 6px;\n opacity: 0.7;\n transition: opacity 0.15s;\n}\n.hw-composer-attach:hover {\n opacity: 1;\n}\n.hw-composer-attach svg {\n width: 16px;\n height: 16px;\n}\n.hw-msg-images {\n display: flex;\n flex-direction: column;\n gap: 4px;\n margin-bottom: 4px;\n}\n.hw-msg-image-link {\n display: block;\n border-radius: 14px;\n overflow: hidden;\n line-height: 0;\n}\n.hw-msg-image {\n max-width: 140px;\n max-height: 80px;\n object-fit: cover;\n border-radius: 8px;\n outline: 1px solid rgba(0, 0, 0, 0.08);\n outline-offset: -1px;\n}\n.hw-msg-files {\n display: flex;\n flex-direction: column;\n gap: 4px;\n margin-top: 4px;\n}\n.hw-msg-file {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n border-radius: 10px;\n background: var(--hw-bg-subtle);\n text-decoration: none;\n color: var(--hw-text);\n transition: background 0.15s;\n}\n.hw-msg-file:hover {\n background: var(--hw-border);\n}\n.hw-msg-file-icon {\n flex-shrink: 0;\n width: 20px;\n height: 20px;\n color: var(--hw-text-3);\n}\n.hw-msg-file-icon svg {\n width: 100%;\n height: 100%;\n}\n.hw-msg-file-info {\n display: flex;\n flex-direction: column;\n min-width: 0;\n}\n.hw-msg-file-name {\n font-size: 13px;\n font-weight: 500;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.hw-msg-file-size {\n font-size: 11px;\n color: var(--hw-text-3);\n}\n.hw-msg-image-link {\n cursor: pointer;\n border: none;\n padding: 0;\n background: none;\n display: block;\n line-height: 0;\n}\n.hw-hover-preview {\n position: fixed;\n z-index: 99998;\n padding: 6px;\n border-radius: 14px;\n background: white;\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.18), 0 0 0 1px rgba(0, 0, 0, 0.06);\n cursor: pointer;\n animation: hw-hover-in 0.15s ease-out;\n}\n@media (prefers-color-scheme: dark) {\n .hw-hover-preview {\n background: #1c1c1e;\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(255, 255, 255, 0.08);\n }\n}\n@keyframes hw-hover-in {\n from {\n opacity: 0;\n transform: scale(0.96) translateY(4px);\n }\n to {\n opacity: 1;\n transform: scale(1) translateY(0);\n }\n}\n.hw-hover-preview-img {\n width: 100%;\n max-height: 200px;\n object-fit: contain;\n border-radius: 10px;\n display: block;\n outline: 1px solid rgba(0, 0, 0, 0.08);\n outline-offset: -1px;\n}\n.hw-hover-preview--doc {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 10px 14px;\n}\n.hw-hover-preview-doc-icon {\n width: 28px;\n height: 28px;\n flex-shrink: 0;\n}\n.hw-hover-preview-doc-icon svg {\n width: 100%;\n height: 100%;\n}\n.hw-hover-preview-doc-info {\n display: flex;\n flex-direction: column;\n min-width: 0;\n}\n.hw-hover-preview-doc-name {\n font-size: 13px;\n font-weight: 500;\n color: var(--hw-text);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.hw-hover-preview-doc-hint {\n font-size: 11px;\n color: var(--hw-text-3);\n}\n.hw-event {\n display: flex;\n justify-content: center;\n padding: 8px 16px;\n}\n.hw-event-text {\n font-size: 12px;\n color: var(--hw-text-3);\n text-align: center;\n}\n.hw-event--card {\n justify-content: flex-start;\n align-items: center;\n gap: 10px;\n background: var(--hw-bg-subtle);\n padding: 10px 14px;\n border-radius: 12px;\n margin: 4px 16px;\n}\n.hw-event-avatar {\n width: 32px;\n height: 32px;\n border-radius: 50%;\n background: #e0e0e0;\n color: #555;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 11px;\n font-weight: 600;\n flex-shrink: 0;\n overflow: hidden;\n}\n.hw-event-avatar-img {\n width: 100%;\n height: 100%;\n object-fit: cover;\n}\n.hw-event-body {\n font-size: 13px;\n line-height: 1.3;\n}\n.hw-event-name {\n font-weight: 600;\n color: var(--hw-text);\n}\n.hw-event-action {\n color: var(--hw-text-2);\n}\n.hw-resolved-banner {\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 12px 16px;\n border-top: 1px solid var(--hw-border);\n background: var(--hw-bg-subtle);\n color: var(--hw-text-2);\n font-size: 13px;\n flex-shrink: 0;\n}\n.hw-new {\n flex: 1;\n display: flex;\n flex-direction: column;\n background: var(--hw-bg-subtle);\n}\n.hw-new-body {\n flex: 1;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 32px 24px;\n text-align: center;\n}\n.hw-new-welcome {\n flex: 1;\n padding: 16px 20px;\n overflow-y: auto;\n}\n.hw-new-heading {\n font-size: 17px;\n font-weight: 600;\n color: var(--hw-text);\n margin-bottom: 6px;\n}\n.hw-new-desc {\n font-size: 13px;\n color: var(--hw-text-2);\n margin-bottom: 24px;\n}\n.hw-powered {\n text-align: center;\n padding: 8px;\n font-size: 11px;\n color: var(--hw-text-4);\n flex-shrink: 0;\n}\n.hw-powered a {\n color: var(--hw-text-2);\n text-decoration: none;\n font-weight: 500;\n}\n.hw-powered a:hover {\n color: var(--hw-text);\n}\n@media (max-width: 479px) {\n .hw-widget {\n bottom: 16px;\n right: 16px;\n left: auto;\n }\n .hw-window {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n width: 100%;\n height: var(--hw-visible-height, 100%);\n border-radius: 0;\n animation: hw-mob-up 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);\n }\n .hw-window--closing {\n animation: hw-mob-down 0.2s ease forwards;\n }\n @keyframes hw-mob-up {\n from {\n opacity: 0;\n transform: translateY(100%);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n }\n @keyframes hw-mob-down {\n from {\n opacity: 1;\n transform: translateY(0);\n }\n to {\n opacity: 0;\n transform: translateY(100%);\n }\n }\n .hw-hero {\n padding-top: calc(20px + env(safe-area-inset-top, 0px));\n }\n .hw-header {\n padding-top: env(safe-area-inset-top, 0px);\n }\n .hw-tabs {\n padding-bottom: env(safe-area-inset-bottom, 0px);\n }\n .hw-composer {\n padding-bottom: calc(8px + env(safe-area-inset-bottom, 0px));\n }\n .hw-powered {\n padding-bottom: calc(5px + env(safe-area-inset-bottom, 0px));\n }\n .hw-header-close,\n .hw-header-back,\n .hw-header-action {\n width: 34px;\n height: 34px;\n }\n .hw-composer-send {\n width: 40px;\n height: 40px;\n }\n .hw-window ~ .hw-bubble {\n display: none;\n }\n .hw-messages {\n padding: 12px 14px;\n }\n .hw-msg {\n max-width: 82%;\n }\n}\n.hw-preview {\n position: absolute;\n bottom: calc(100% + 12px);\n right: 0;\n width: 300px;\n max-width: calc(100vw - 40px);\n background: var(--hw-bg);\n border: 1px solid var(--hw-border);\n border-radius: 16px;\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12), 0 2px 8px rgba(0, 0, 0, 0.06);\n animation: hw-preview-in 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);\n overflow: hidden;\n cursor: pointer;\n transition: transform 0.15s, box-shadow 0.15s;\n}\n.hw-preview:hover {\n transform: translateY(-2px);\n box-shadow: 0 12px 40px rgba(0, 0, 0, 0.15), 0 4px 12px rgba(0, 0, 0, 0.08);\n}\n.hw-preview--closing {\n animation: hw-preview-out 0.2s ease forwards;\n}\n@keyframes hw-preview-in {\n from {\n opacity: 0;\n transform: translateY(8px) scale(0.95);\n }\n to {\n opacity: 1;\n transform: translateY(0) scale(1);\n }\n}\n@keyframes hw-preview-out {\n from {\n opacity: 1;\n transform: translateY(0) scale(1);\n }\n to {\n opacity: 0;\n transform: translateY(8px) scale(0.95);\n }\n}\n.hw-preview-header {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 12px 14px 0;\n}\n.hw-preview-avatar {\n width: 28px;\n height: 28px;\n border-radius: 50%;\n background:\n linear-gradient(\n 135deg,\n var(--hw-primary),\n color-mix(in srgb, var(--hw-primary) 50%, black));\n display: flex;\n align-items: center;\n justify-content: center;\n color: white;\n flex-shrink: 0;\n overflow: hidden;\n}\n.hw-preview-avatar svg {\n width: 16px;\n height: 16px;\n}\n.hw-preview-name {\n font-size: 13px;\n font-weight: 600;\n color: var(--hw-text);\n flex: 1;\n}\n.hw-preview-close {\n width: 24px;\n height: 24px;\n border: none;\n background: transparent;\n color: var(--hw-text-3);\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: 50%;\n transition: background 0.15s, color 0.15s;\n flex-shrink: 0;\n}\n.hw-preview-close:hover {\n background: var(--hw-bg-subtle);\n color: var(--hw-text-2);\n}\n.hw-preview-close:active {\n transform: scale(0.90);\n transition:\n transform 0.14s cubic-bezier(0.23, 1, 0.32, 1),\n background 0.15s,\n color 0.15s;\n}\n.hw-preview-close svg {\n width: 12px;\n height: 12px;\n}\n.hw-preview-body {\n padding: 8px 14px 14px;\n font-size: 14px;\n line-height: 1.45;\n color: var(--hw-text-2);\n display: -webkit-box;\n -webkit-line-clamp: 3;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n.hw-content-search {\n padding: 10px 16px;\n}\n.hw-content-search-box {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n background: var(--hw-bg);\n border: 1px solid var(--hw-border-md);\n border-radius: 10px;\n transition: border-color 0.15s, box-shadow 0.15s;\n}\n.hw-content-search-box:focus-within {\n border-color: var(--hw-focus-border);\n box-shadow: 0 0 0 3px var(--hw-focus-ring);\n}\n.hw-content-search-input {\n flex: 1;\n border: none !important;\n outline: none !important;\n box-shadow: none !important;\n background: none;\n font-size: 13px;\n color: var(--hw-text-1);\n font-family: inherit;\n line-height: 1;\n -webkit-appearance: none;\n}\n.hw-content-search-input:focus,\n.hw-content-search-input:focus-visible {\n outline: none !important;\n box-shadow: none !important;\n border: none !important;\n}\n.hw-content-search-input::placeholder {\n color: var(--hw-text-3);\n}\n.hw-content-search-icon {\n width: 15px;\n height: 15px;\n color: var(--hw-text-3);\n flex-shrink: 0;\n}\n.hw-content-list {\n display: flex;\n flex-direction: column;\n height: 100%;\n}\n.hw-content-entries {\n flex: 1;\n overflow-y: auto;\n overscroll-behavior: contain;\n}\n.hw-content-entry {\n display: block;\n width: 100%;\n padding: 14px 16px;\n border: none;\n border-bottom: 1px solid var(--hw-border);\n background: none;\n cursor: pointer;\n text-align: left;\n transition: background-color 0.15s ease;\n -webkit-tap-highlight-color: transparent;\n}\n.hw-content-entry:hover {\n background: var(--hw-bg-hover);\n}\n.hw-content-entry:active {\n background: var(--hw-border);\n}\n.hw-content-entry:last-child {\n border-bottom: none;\n}\n.hw-content-entry-main {\n display: flex;\n align-items: center;\n gap: 12px;\n}\n.hw-content-entry-text {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 3px;\n}\n.hw-content-entry-title {\n font-size: 14px;\n font-weight: 600;\n color: var(--hw-text-1);\n line-height: 1.3;\n}\n.hw-content-entry-desc {\n font-size: 13px;\n color: var(--hw-text-2);\n line-height: 1.4;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n.hw-content-entry-meta {\n display: flex;\n align-items: center;\n gap: 6px;\n margin-top: 2px;\n}\n.hw-content-entry-badge {\n font-size: 11px;\n font-weight: 500;\n color: var(--hw-text-3);\n background: var(--hw-bg-hover);\n padding: 1px 6px;\n border-radius: 4px;\n}\n.hw-content-entry-date {\n font-size: 11px;\n color: var(--hw-text-3);\n}\n.hw-content-entry-chevron {\n width: 16px;\n height: 16px;\n color: var(--hw-text-3);\n flex-shrink: 0;\n opacity: 0.5;\n}\n.hw-content-detail {\n padding: 16px;\n overflow-y: auto;\n overscroll-behavior: contain;\n height: 100%;\n}\n.hw-content-detail-header {\n margin-bottom: 16px;\n}\n.hw-content-detail-title {\n font-size: 17px;\n font-weight: 700;\n color: var(--hw-text-1);\n margin: 0 0 6px;\n line-height: 1.3;\n display: flex;\n align-items: center;\n gap: 6px;\n}\n.hw-content-detail-meta {\n display: flex;\n align-items: center;\n gap: 8px;\n flex-wrap: wrap;\n}\n.hw-content-detail-date {\n font-size: 12px;\n color: var(--hw-text-3);\n}\n.hw-content-detail-body {\n font-size: 14px;\n line-height: 1.6;\n color: var(--hw-text-1);\n}\n.hw-markdown h1 {\n font-size: 17px;\n font-weight: 700;\n margin: 20px 0 8px;\n}\n.hw-markdown h2 {\n font-size: 15px;\n font-weight: 600;\n margin: 16px 0 6px;\n}\n.hw-markdown h3 {\n font-size: 14px;\n font-weight: 600;\n margin: 12px 0 4px;\n}\n.hw-markdown p {\n margin: 0 0 12px;\n}\n.hw-markdown ul {\n margin: 0 0 12px;\n padding-left: 20px;\n}\n.hw-markdown li {\n margin: 4px 0;\n}\n.hw-markdown code {\n font-family:\n ui-monospace,\n "SF Mono",\n monospace;\n font-size: 12px;\n background: var(--hw-bg-hover);\n padding: 1px 5px;\n border-radius: 4px;\n}\n.hw-code {\n margin: 10px 0;\n border-radius: 10px;\n overflow: hidden;\n border: 1px solid var(--hw-border, rgba(0, 0, 0, 0.08));\n background: var(--hw-bg-hover, #f5f5f4);\n font-family:\n ui-monospace,\n "SF Mono",\n "Menlo",\n monospace;\n}\n.hw-code-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 6px 12px;\n font-size: 11px;\n color: var(--hw-text-2, #6b7280);\n background: rgba(0, 0, 0, 0.03);\n border-bottom: 1px solid var(--hw-border, rgba(0, 0, 0, 0.06));\n}\n.hw-code-lang {\n text-transform: lowercase;\n letter-spacing: 0.02em;\n font-weight: 500;\n}\n.hw-code-copy {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 2px 8px;\n font-size: 11px;\n font-family: inherit;\n color: var(--hw-text-2, #6b7280);\n background: transparent;\n border: 1px solid transparent;\n border-radius: 5px;\n cursor: pointer;\n transition: background 120ms ease, color 120ms ease;\n}\n.hw-code-copy:hover {\n color: var(--hw-text, #111);\n background: rgba(0, 0, 0, 0.06);\n}\n.hw-code-copy--copied {\n color: #16a34a;\n}\n.hw-code-copy--failed {\n color: #dc2626;\n}\n.hw-code pre {\n margin: 0;\n padding: 10px 12px;\n background: transparent;\n border-radius: 0;\n overflow-x: auto;\n max-width: 100%;\n}\n.hw-code pre code {\n display: block;\n background: transparent;\n padding: 0;\n font-size: 12px;\n line-height: 1.55;\n white-space: pre;\n color: var(--hw-text, #111);\n}\n@media (prefers-color-scheme: dark) {\n .hw-code {\n background: rgba(255, 255, 255, 0.04);\n border-color: rgba(255, 255, 255, 0.08);\n }\n .hw-code-header {\n background: rgba(255, 255, 255, 0.03);\n border-bottom-color: rgba(255, 255, 255, 0.06);\n }\n .hw-code-copy:hover {\n background: rgba(255, 255, 255, 0.08);\n }\n}\n.hw-markdown pre {\n margin: 8px 0;\n padding: 10px 12px;\n background: var(--hw-bg-hover);\n border-radius: 8px;\n overflow-x: auto;\n max-width: 100%;\n}\n.hw-markdown pre code {\n display: block;\n background: transparent;\n padding: 0;\n border-radius: 0;\n font-size: 12px;\n line-height: 1.5;\n white-space: pre;\n word-break: normal;\n color: inherit;\n}\n.hw-msg-bubble pre {\n margin: 6px 0;\n padding: 8px 10px;\n background: rgba(0, 0, 0, 0.06);\n border-radius: 6px;\n overflow-x: auto;\n max-width: 100%;\n}\n.hw-msg-bubble pre code {\n background: transparent;\n padding: 0;\n font-size: 12px;\n line-height: 1.5;\n white-space: pre;\n}\n.hw-msg--team .hw-msg-bubble pre,\n.hw-msg--visitor .hw-msg-bubble pre {\n background: rgba(255, 255, 255, 0.12);\n}\n.hw-markdown blockquote {\n margin: 0 0 12px;\n padding: 8px 12px;\n border-left: 3px solid var(--hw-border);\n color: var(--hw-text-2);\n font-style: italic;\n}\n.hw-markdown a {\n color: var(--hw-primary);\n text-decoration: underline;\n text-underline-offset: 2px;\n}\n.hw-markdown img {\n max-width: 100%;\n border-radius: 8px;\n margin: 8px 0;\n outline: 1px solid rgba(0, 0, 0, 0.08);\n outline-offset: -1px;\n}\n.hw-markdown hr {\n border: none;\n border-top: 1px solid var(--hw-border);\n margin: 16px 0;\n}\n.hw-content-empty {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 40px 20px;\n text-align: center;\n}\n.hw-content-empty-title {\n font-size: 14px;\n font-weight: 600;\n color: var(--hw-text-1);\n margin-bottom: 4px;\n}\n.hw-content-empty-desc {\n font-size: 12px;\n color: var(--hw-text-3);\n}\n.hw-content-loading {\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 40px 20px;\n}\n.hw-content-load-more {\n display: block;\n width: 100%;\n padding: 10px;\n border: none;\n background: none;\n color: var(--hw-primary);\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n text-align: center;\n}\n.hw-content-load-more:hover {\n background: var(--hw-bg-hover);\n}\n.hw-content-load-more:disabled {\n opacity: 0.5;\n cursor: default;\n}\n@media (hover: none) {\n .hw-composer-preview-remove {\n opacity: 0.7;\n }\n .hw-msg-meta {\n opacity: 1;\n }\n}\n.hw-composer--dragging .hw-composer-box {\n border-color: var(--hw-primary);\n background: color-mix(in srgb, var(--hw-primary) 4%, transparent);\n}\n.hw-composer-drop-overlay {\n position: absolute;\n inset: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n background: color-mix(in srgb, var(--hw-bg) 90%, transparent);\n border: 2px dashed var(--hw-primary);\n border-radius: 12px;\n z-index: 10;\n pointer-events: none;\n animation: hw-drop-pulse 1.5s ease-in-out infinite;\n}\n.hw-composer-drop-text {\n font-size: 13px;\n font-weight: 500;\n color: var(--hw-primary);\n}\n@keyframes hw-drop-pulse {\n 0%, 100% {\n opacity: 0.8;\n }\n 50% {\n opacity: 1;\n }\n}\n.hw-msg-copy {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 20px;\n height: 20px;\n margin-left: 4px;\n border: none;\n border-radius: 4px;\n background: none;\n color: var(--hw-text-4);\n cursor: pointer;\n vertical-align: middle;\n transition: color 0.15s, background 0.15s;\n padding: 0;\n flex-shrink: 0;\n}\n.hw-msg-copy:hover {\n background: var(--hw-bg-hover);\n color: var(--hw-text-2);\n}\n.hw-msg-copy svg {\n width: 12px;\n height: 12px;\n}\n.hw-msg-copy--done {\n color: #22c55e;\n}\n.hw-disconnect {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n padding: 6px 12px;\n background: #fef3c7;\n color: #92400e;\n font-size: 12px;\n font-weight: 500;\n flex-shrink: 0;\n animation: hw-msg-appear 0.25s ease-out;\n}\n.hw-disconnect-dot {\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: #f59e0b;\n animation: hw-loading-pulse 1.4s infinite;\n}\n.hw-error-state {\n flex: 1;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 32px 24px;\n text-align: center;\n gap: 12px;\n}\n.hw-error-icon {\n width: 48px;\n height: 48px;\n border-radius: 50%;\n background: #fef2f2;\n color: #ef4444;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n.hw-error-icon svg {\n width: 24px;\n height: 24px;\n}\n.hw-error-title {\n font-size: 15px;\n font-weight: 600;\n color: var(--hw-text);\n}\n.hw-error-desc {\n font-size: 13px;\n color: var(--hw-text-2);\n}\n.hw-error-btn {\n padding: 8px 20px;\n border: 1px solid var(--hw-border-md);\n border-radius: 8px;\n background: var(--hw-bg);\n color: var(--hw-text);\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n transition: background 0.15s;\n}\n.hw-error-btn:hover {\n background: var(--hw-bg-hover);\n}\n.hw-send-error {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n background: #fef2f2;\n color: #dc2626;\n font-size: 12px;\n font-weight: 500;\n flex-shrink: 0;\n animation: hw-msg-appear 0.25s ease-out;\n}\n.hw-send-error-retry {\n margin-left: auto;\n padding: 2px 8px;\n border: 1px solid #fca5a5;\n border-radius: 4px;\n background: none;\n color: #dc2626;\n font-size: 11px;\n font-weight: 600;\n cursor: pointer;\n}\n.hw-send-error-retry:hover {\n background: #fee2e2;\n}\n.hw-resolved {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 12px;\n padding: 16px 20px;\n border-top: 1px solid var(--hw-border);\n flex-shrink: 0;\n}\n.hw-resolved-text {\n font-size: 13px;\n color: var(--hw-text-3);\n font-weight: 500;\n}\n.hw-resolved-actions {\n display: flex;\n gap: 8px;\n}\n.hw-resolved-reopen {\n padding: 6px 16px;\n border: 1px solid var(--hw-border-md);\n border-radius: 8px;\n background: var(--hw-bg);\n color: var(--hw-text);\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n transition: background 0.15s;\n}\n.hw-resolved-reopen:hover {\n background: var(--hw-bg-hover);\n}\n.hw-csat {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 8px;\n}\n.hw-csat-label {\n font-size: 12px;\n color: var(--hw-text-3);\n}\n.hw-csat-buttons {\n display: flex;\n gap: 8px;\n}\n.hw-csat-btn {\n width: 36px;\n height: 36px;\n border: 1px solid var(--hw-border-md);\n border-radius: 8px;\n background: var(--hw-bg);\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 18px;\n transition:\n transform 0.15s,\n border-color 0.15s,\n background 0.15s;\n}\n.hw-csat-btn:hover {\n transform: scale(1.1);\n border-color: var(--hw-text-3);\n}\n.hw-csat-btn--selected {\n border-color: var(--hw-primary);\n background: var(--hw-bg-hover);\n transform: scale(1.15);\n}\n.hw-csat-thanks {\n font-size: 12px;\n color: #22c55e;\n font-weight: 500;\n animation: hw-msg-appear 0.25s ease-out;\n}\n.hw-load-more-trigger {\n display: flex;\n justify-content: center;\n min-height: 1px;\n}\n.hw-load-more-trigger:has(.hw-load-more-spinner) {\n padding: 8px 0;\n}\n.hw-load-more-spinner {\n display: flex;\n gap: 3px;\n}\n.hw-load-more-spinner > div {\n width: 5px;\n height: 5px;\n border-radius: 50%;\n background: var(--hw-text-4);\n animation: hw-loading-pulse 1.4s infinite;\n}\n.hw-load-more-spinner > div:nth-child(2) {\n animation-delay: 0.2s;\n}\n.hw-load-more-spinner > div:nth-child(3) {\n animation-delay: 0.4s;\n}\n.hw-drag-handle {\n display: none;\n}\n@media (max-width: 479px) {\n .hw-drag-handle {\n display: flex;\n justify-content: center;\n padding: 8px 0 4px;\n flex-shrink: 0;\n }\n .hw-drag-handle-pill {\n width: 32px;\n height: 4px;\n border-radius: 2px;\n background: var(--hw-text-4);\n }\n}\n.hw-msg-bubble p {\n margin: 0 0 8px;\n}\n.hw-msg-bubble p:last-child {\n margin-bottom: 0;\n}\n.hw-msg-bubble strong {\n font-weight: 600;\n}\n.hw-msg-bubble em {\n font-style: italic;\n}\n.hw-msg-bubble a {\n color: var(--hw-primary);\n text-decoration: underline;\n text-underline-offset: 2px;\n word-break: break-all;\n}\n.hw-msg-bubble a:hover {\n opacity: 0.8;\n}\n.hw-msg--visitor .hw-msg-bubble a {\n color: rgba(255, 255, 255, 0.9);\n}\n.hw-msg--visitor .hw-msg-bubble a:hover {\n color: white;\n}\n.hw-msg-bubble ul,\n.hw-msg-bubble ol {\n margin: 4px 0 8px;\n padding-left: 20px;\n}\n.hw-msg-bubble li {\n margin: 2px 0;\n}\n.hw-msg-bubble code {\n padding: 1px 4px;\n border-radius: 3px;\n background: rgba(0, 0, 0, 0.06);\n font-size: 0.9em;\n font-family:\n ui-monospace,\n SFMono-Regular,\n Menlo,\n monospace;\n}\n.hw-msg-bubble pre {\n margin: 4px 0 8px;\n padding: 8px 10px;\n border-radius: 6px;\n background: rgba(0, 0, 0, 0.06);\n overflow-x: auto;\n font-size: 0.85em;\n}\n.hw-msg-bubble pre code {\n padding: 0;\n background: none;\n}\n.hw-msg-bubble blockquote {\n margin: 4px 0 8px;\n padding: 4px 0 4px 12px;\n border-left: 3px solid var(--hw-border-md);\n color: var(--hw-text-2);\n}\n.hw-widget[data-theme=dark] {\n --hw-bg: #1c1c1e;\n --hw-bg-subtle: #161618;\n --hw-bg-hover: #2c2c2e;\n --hw-border: rgba(255, 255, 255, 0.08);\n --hw-border-md: rgba(255, 255, 255, 0.14);\n --hw-text: #f5f5f5;\n --hw-text-1: #f5f5f5;\n --hw-text-2: #a1a1aa;\n --hw-text-3: #71717a;\n --hw-text-4: #52525b;\n --hw-visitor-bubble-bg: #2c2c2e;\n --hw-visitor-bubble-text: #f5f5f5;\n --hw-focus-border: rgba(255, 255, 255, 0.28);\n --hw-focus-ring: rgba(255, 255, 255, 0.08);\n}\n@media (prefers-color-scheme: dark) {\n .hw-widget:not([data-theme=light]) {\n --hw-bg: #1c1c1e;\n --hw-bg-subtle: #161618;\n --hw-bg-hover: #2c2c2e;\n --hw-border: rgba(255, 255, 255, 0.08);\n --hw-border-md: rgba(255, 255, 255, 0.14);\n --hw-text: #f5f5f5;\n --hw-text-1: #f5f5f5;\n --hw-text-2: #a1a1aa;\n --hw-text-3: #71717a;\n --hw-text-4: #52525b;\n --hw-visitor-bubble-bg: #2c2c2e;\n --hw-visitor-bubble-text: #f5f5f5;\n --hw-focus-border: rgba(255, 255, 255, 0.28);\n --hw-focus-ring: rgba(255, 255, 255, 0.08);\n }\n}\n@media (prefers-color-scheme: dark) {\n .hw-widget:not([data-theme=light]) .hw-header {\n background: var(--hw-bg);\n border-color: var(--hw-border);\n }\n .hw-header-bot-icon {\n background: var(--hw-bg-hover);\n }\n .hw-header-name,\n .hw-header-title {\n color: var(--hw-text);\n }\n .hw-header-status {\n color: var(--hw-text-2);\n }\n .hw-header-close,\n .hw-header-back {\n color: var(--hw-text-2);\n }\n .hw-header-close:hover,\n .hw-header-back:hover {\n background: var(--hw-bg-hover);\n color: var(--hw-text);\n }\n .hw-content-entry {\n background: var(--hw-bg);\n border-color: var(--hw-border);\n }\n .hw-content-entry:hover {\n background: var(--hw-bg-hover);\n border-color: var(--hw-border-md);\n }\n .hw-content-empty,\n .hw-empty {\n color: var(--hw-text-3);\n }\n .hw-conv-card {\n border-color: var(--hw-border);\n }\n .hw-recent-row {\n border-color: var(--hw-border);\n }\n .hw-composer-drop-overlay {\n background: rgba(0, 0, 0, 0.6);\n color: var(--hw-text);\n }\n .hw-composer--dragging .hw-composer-box {\n border-color: var(--hw-focus-border);\n box-shadow: 0 0 0 3px var(--hw-focus-ring);\n }\n .hw-hero {\n background:\n linear-gradient(\n 160deg,\n color-mix(in srgb, var(--hw-primary) 40%, black) 0%,\n var(--hw-primary) 50%,\n color-mix(in srgb, var(--hw-primary) 25%, black) 100%);\n }\n .hw-sheet {\n background: var(--hw-bg);\n }\n .hw-card {\n background: var(--hw-bg-hover);\n border-color: var(--hw-border);\n }\n .hw-card:hover {\n background: #333;\n }\n .hw-window {\n box-shadow: 0 8px 30px rgba(0, 0, 0, 0.4), 0 0 0 1px rgba(255, 255, 255, 0.06);\n }\n .hw-bubble:focus-visible {\n outline-color: var(--hw-text-3);\n }\n .hw-msg--visitor .hw-msg-bubble {\n background: var(--hw-primary);\n color: white;\n }\n .hw-msg-bubble code {\n background: rgba(255, 255, 255, 0.08);\n }\n .hw-msg-bubble pre {\n background: rgba(255, 255, 255, 0.08);\n }\n .hw-composer-box {\n background: var(--hw-bg);\n border-color: var(--hw-border);\n }\n .hw-composer-input {\n color: var(--hw-text);\n }\n .hw-tabs {\n background: var(--hw-bg);\n border-color: var(--hw-border);\n }\n .hw-conv-card:hover,\n .hw-recent-row:hover {\n background: var(--hw-bg-hover);\n }\n .hw-disconnect {\n background: #422006;\n color: #fcd34d;\n }\n .hw-send-error {\n background: #450a0a;\n color: #fca5a5;\n }\n .hw-error-icon {\n background: #450a0a;\n color: #f87171;\n }\n .hw-msg-copy:hover {\n background: var(--hw-bg-hover);\n color: var(--hw-text-2);\n }\n .hw-resolved {\n border-color: var(--hw-border);\n }\n .hw-resolved-reopen,\n .hw-csat-btn {\n background: var(--hw-bg-hover);\n border-color: var(--hw-border-md);\n color: var(--hw-text);\n }\n .hw-hover-preview {\n background: #1c1c1e;\n border-color: var(--hw-border);\n }\n .hw-content-search-box {\n background: var(--hw-bg-hover);\n border-color: var(--hw-border-md);\n }\n .hw-preview {\n background: var(--hw-bg);\n border-color: var(--hw-border);\n }\n}\n');
|
|
4129
|
+
|
|
4130
|
+
// src/helpway-widget.tsx
|
|
4131
|
+
import { jsx as jsx13 } from "react/jsx-runtime";
|
|
4132
|
+
function HelpwayWidget({
|
|
4133
|
+
apiKey,
|
|
4134
|
+
apiUrl,
|
|
4135
|
+
locale,
|
|
4136
|
+
initialConversationId,
|
|
4137
|
+
onReady,
|
|
4138
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
4139
|
+
src
|
|
4140
|
+
}) {
|
|
4141
|
+
const resolvedApiUrl = apiUrl ?? (src ? src.replace(/\/widget\.js$/, "") : void 0);
|
|
4142
|
+
return /* @__PURE__ */ jsx13(
|
|
4143
|
+
SupportProvider,
|
|
4144
|
+
{
|
|
4145
|
+
apiKey,
|
|
4146
|
+
apiUrl: resolvedApiUrl,
|
|
4147
|
+
locale,
|
|
4148
|
+
initialConversationId,
|
|
4149
|
+
onReady,
|
|
4150
|
+
children: /* @__PURE__ */ jsx13(DefaultWidget, {})
|
|
4151
|
+
}
|
|
4152
|
+
);
|
|
4153
|
+
}
|
|
4154
|
+
|
|
4155
|
+
// src/hooks/use-realtime.ts
|
|
4156
|
+
import { useEffect as useEffect11 } from "react";
|
|
4157
|
+
function useRealtime(handler) {
|
|
4158
|
+
const { ws } = useWidgetContext();
|
|
4159
|
+
useEffect11(() => {
|
|
4160
|
+
return ws.onEvent(handler);
|
|
4161
|
+
}, [ws, handler]);
|
|
4162
|
+
}
|
|
4142
4163
|
export {
|
|
4143
4164
|
Composer,
|
|
4144
4165
|
DefaultWidget,
|