@droppii-org/chat-sdk 0.1.20 → 0.1.22

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.
Files changed (109) hide show
  1. package/dist/components/bizInbox/BizCommunityTabPlaceholder.d.ts +3 -0
  2. package/dist/components/bizInbox/BizCommunityTabPlaceholder.d.ts.map +1 -0
  3. package/dist/components/bizInbox/BizCommunityTabPlaceholder.js +10 -0
  4. package/dist/components/bizInbox/BizContactListSkeleton.d.ts +6 -0
  5. package/dist/components/bizInbox/BizContactListSkeleton.d.ts.map +1 -0
  6. package/dist/components/bizInbox/BizContactListSkeleton.js +8 -0
  7. package/dist/components/bizInbox/BizCreateGroupButton.d.ts +7 -0
  8. package/dist/components/bizInbox/BizCreateGroupButton.d.ts.map +1 -0
  9. package/dist/components/bizInbox/BizCreateGroupButton.js +11 -0
  10. package/dist/components/bizInbox/BizInboxHeader.d.ts +3 -0
  11. package/dist/components/bizInbox/BizInboxHeader.d.ts.map +1 -0
  12. package/dist/components/bizInbox/BizInboxHeader.js +8 -0
  13. package/dist/components/bizInbox/BizInboxTabs.d.ts +8 -0
  14. package/dist/components/bizInbox/BizInboxTabs.d.ts.map +1 -0
  15. package/dist/components/bizInbox/BizInboxTabs.js +27 -0
  16. package/dist/components/bizInbox/BizSearchInput.d.ts +9 -0
  17. package/dist/components/bizInbox/BizSearchInput.d.ts.map +1 -0
  18. package/dist/components/bizInbox/BizSearchInput.js +24 -0
  19. package/dist/components/bizInbox/BizThreadCard.d.ts +8 -0
  20. package/dist/components/bizInbox/BizThreadCard.d.ts.map +1 -0
  21. package/dist/components/bizInbox/BizThreadCard.js +52 -0
  22. package/dist/components/bizInbox/BizThreadList.d.ts +11 -0
  23. package/dist/components/bizInbox/BizThreadList.d.ts.map +1 -0
  24. package/dist/components/bizInbox/BizThreadList.js +17 -0
  25. package/dist/components/bizInbox/index.d.ts +6 -0
  26. package/dist/components/bizInbox/index.d.ts.map +1 -0
  27. package/dist/components/bizInbox/index.js +3 -0
  28. package/dist/components/bizInbox/states/BizThreadListEmpty.d.ts +7 -0
  29. package/dist/components/bizInbox/states/BizThreadListEmpty.d.ts.map +1 -0
  30. package/dist/components/bizInbox/states/BizThreadListEmpty.js +15 -0
  31. package/dist/components/bizInbox/states/BizThreadListError.d.ts +6 -0
  32. package/dist/components/bizInbox/states/BizThreadListError.d.ts.map +1 -0
  33. package/dist/components/bizInbox/states/BizThreadListError.js +11 -0
  34. package/dist/components/bizInbox/states/BizThreadListLoading.d.ts +6 -0
  35. package/dist/components/bizInbox/states/BizThreadListLoading.d.ts.map +1 -0
  36. package/dist/components/bizInbox/states/BizThreadListLoading.js +8 -0
  37. package/dist/components/message/footer/ActionBar.d.ts.map +1 -1
  38. package/dist/components/message/footer/ActionBar.js +4 -1
  39. package/dist/components/message/footer/EnterHandler.d.ts.map +1 -1
  40. package/dist/components/message/footer/EnterHandler.js +20 -2
  41. package/dist/components/message/footer/SessionClosedModal.d.ts +10 -0
  42. package/dist/components/message/footer/SessionClosedModal.d.ts.map +1 -0
  43. package/dist/components/message/footer/SessionClosedModal.js +9 -0
  44. package/dist/components/message/footer/index.d.ts.map +1 -1
  45. package/dist/components/message/footer/index.js +103 -18
  46. package/dist/components/message/item/SystemLogMessage.d.ts.map +1 -1
  47. package/dist/components/message/item/SystemLogMessage.js +9 -10
  48. package/dist/components/searchConversation/SearchDrawer.js +10 -10
  49. package/dist/constants/bizInbox.d.ts +37 -0
  50. package/dist/constants/bizInbox.d.ts.map +1 -0
  51. package/dist/constants/bizInbox.js +35 -0
  52. package/dist/hooks/biz/index.d.ts +4 -0
  53. package/dist/hooks/biz/index.d.ts.map +1 -0
  54. package/dist/hooks/biz/index.js +2 -0
  55. package/dist/hooks/biz/useBizConversationList.d.ts +14 -0
  56. package/dist/hooks/biz/useBizConversationList.d.ts.map +1 -0
  57. package/dist/hooks/biz/useBizConversationList.js +45 -0
  58. package/dist/hooks/biz/useCreateBizConversation.d.ts +8 -0
  59. package/dist/hooks/biz/useCreateBizConversation.d.ts.map +1 -0
  60. package/dist/hooks/biz/useCreateBizConversation.js +15 -0
  61. package/dist/hooks/session/useCreateNote.d.ts.map +1 -1
  62. package/dist/hooks/session/useCreateNote.js +4 -2
  63. package/dist/hooks/session/useGetLabelSession.d.ts.map +1 -1
  64. package/dist/hooks/session/useGetLabelSession.js +16 -13
  65. package/dist/hooks/session/useGetListSessionByConversation.d.ts.map +1 -1
  66. package/dist/hooks/session/useGetListSessionByConversation.js +24 -20
  67. package/dist/hooks/session/useReopenOrCreateSession.d.ts +4 -0
  68. package/dist/hooks/session/useReopenOrCreateSession.d.ts.map +1 -0
  69. package/dist/hooks/session/useReopenOrCreateSession.js +32 -0
  70. package/dist/hooks/user/useAuth.d.ts.map +1 -1
  71. package/dist/hooks/user/useAuth.js +2 -2
  72. package/dist/index.d.ts +5 -0
  73. package/dist/index.d.ts.map +1 -1
  74. package/dist/index.js +2 -0
  75. package/dist/locales/en/bizInbox.json +41 -0
  76. package/dist/locales/i18n.d.ts.map +1 -1
  77. package/dist/locales/i18n.js +4 -3
  78. package/dist/locales/i18n.ts +4 -3
  79. package/dist/locales/vi/bizInbox.json +12 -0
  80. package/dist/locales/vi/common.json +6 -1
  81. package/dist/screens/bizMessage/index.d.ts +9 -0
  82. package/dist/screens/bizMessage/index.d.ts.map +1 -0
  83. package/dist/screens/bizMessage/index.js +30 -0
  84. package/dist/services/biz/conversationService.d.ts +3 -0
  85. package/dist/services/biz/conversationService.d.ts.map +1 -0
  86. package/dist/services/biz/conversationService.js +10 -0
  87. package/dist/services/query.d.ts +4 -0
  88. package/dist/services/query.d.ts.map +1 -1
  89. package/dist/services/query.js +4 -0
  90. package/dist/services/routes.d.ts +4 -0
  91. package/dist/services/routes.d.ts.map +1 -1
  92. package/dist/services/routes.js +4 -0
  93. package/dist/styles/global.css +1 -1
  94. package/dist/tsconfig.tsbuildinfo +1 -1
  95. package/dist/types/biz.d.ts +45 -0
  96. package/dist/types/biz.d.ts.map +1 -0
  97. package/dist/types/biz.js +1 -0
  98. package/dist/types/chat.d.ts +3 -2
  99. package/dist/types/chat.d.ts.map +1 -1
  100. package/dist/types/chat.js +1 -0
  101. package/dist/utils/events.d.ts +1 -0
  102. package/dist/utils/events.d.ts.map +1 -1
  103. package/dist/utils/messageLog.d.ts +1 -1
  104. package/dist/utils/messageLog.d.ts.map +1 -1
  105. package/dist/utils/messageLog.js +23 -6
  106. package/dist/utils/queryHelpers.d.ts +1 -1
  107. package/dist/utils/queryHelpers.d.ts.map +1 -1
  108. package/dist/utils/queryHelpers.js +1 -1
  109. package/package.json +1 -1
@@ -0,0 +1,3 @@
1
+ declare const BizCommunityTabPlaceholder: () => import("react/jsx-runtime").JSX.Element;
2
+ export default BizCommunityTabPlaceholder;
3
+ //# sourceMappingURL=BizCommunityTabPlaceholder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BizCommunityTabPlaceholder.d.ts","sourceRoot":"","sources":["../../../src/components/bizInbox/BizCommunityTabPlaceholder.tsx"],"names":[],"mappings":"AAMA,QAAA,MAAM,0BAA0B,+CAkB/B,CAAC;AAEF,eAAe,0BAA0B,CAAC"}
@@ -0,0 +1,10 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useTranslation } from "react-i18next";
4
+ import { TeamOutlined } from "@ant-design/icons";
5
+ import { BIZ_INBOX_TEST_IDS } from "../../constants/bizInbox";
6
+ const BizCommunityTabPlaceholder = () => {
7
+ const { t } = useTranslation("bizInbox");
8
+ return (_jsxs("div", { role: "status", "data-testid": BIZ_INBOX_TEST_IDS.COMMUNITY_PLACEHOLDER, className: "flex flex-1 flex-col items-center justify-center gap-3 p-8 text-center", children: [_jsx(TeamOutlined, { "aria-hidden": true, className: "text-5xl text-primary-500" }), _jsx("div", { className: "text-lg font-semibold text-neutral-900", children: t("community_tab.coming_soon.title") }), _jsx("div", { className: "max-w-md text-sm text-neutral-500", children: t("community_tab.coming_soon.description") })] }));
9
+ };
10
+ export default BizCommunityTabPlaceholder;
@@ -0,0 +1,6 @@
1
+ export interface BizContactListSkeletonProps {
2
+ itemCount?: number;
3
+ }
4
+ declare const BizContactListSkeleton: ({ itemCount, }: BizContactListSkeletonProps) => import("react/jsx-runtime").JSX.Element;
5
+ export default BizContactListSkeleton;
6
+ //# sourceMappingURL=BizContactListSkeleton.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BizContactListSkeleton.d.ts","sourceRoot":"","sources":["../../../src/components/bizInbox/BizContactListSkeleton.tsx"],"names":[],"mappings":"AAOA,MAAM,WAAW,2BAA2B;IAC1C,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,QAAA,MAAM,sBAAsB,GAAI,gBAE7B,2BAA2B,4CAsB7B,CAAC;AAEF,eAAe,sBAAsB,CAAC"}
@@ -0,0 +1,8 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { BIZ_INBOX_DEFAULTS, BIZ_INBOX_TEST_IDS, } from "../../constants/bizInbox";
4
+ const BizContactListSkeleton = ({ itemCount = BIZ_INBOX_DEFAULTS.CONTACT_LIST_SKELETON_COUNT, }) => {
5
+ const items = Array.from({ length: itemCount }, (_, index) => index);
6
+ return (_jsx("div", { "data-testid": BIZ_INBOX_TEST_IDS.CONTACT_LIST_SKELETON, className: "flex h-full w-72 flex-col gap-3 border-r border-neutral-200 bg-neutral-50 p-4", children: items.map((index) => (_jsxs("div", { className: "flex items-center gap-3 rounded-lg bg-white p-3 shadow-sm", children: [_jsx("div", { className: "h-10 w-10 animate-pulse rounded-full bg-neutral-200" }), _jsxs("div", { className: "flex flex-1 flex-col gap-2", children: [_jsx("div", { className: "h-3 w-1/2 animate-pulse rounded bg-neutral-200" }), _jsx("div", { className: "h-2.5 w-1/3 animate-pulse rounded bg-neutral-200" })] })] }, index))) }));
7
+ };
8
+ export default BizContactListSkeleton;
@@ -0,0 +1,7 @@
1
+ export interface BizCreateGroupButtonProps {
2
+ onClick: () => void;
3
+ disabled?: boolean;
4
+ }
5
+ declare const BizCreateGroupButton: ({ onClick, disabled }: BizCreateGroupButtonProps) => import("react/jsx-runtime").JSX.Element;
6
+ export default BizCreateGroupButton;
7
+ //# sourceMappingURL=BizCreateGroupButton.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BizCreateGroupButton.d.ts","sourceRoot":"","sources":["../../../src/components/bizInbox/BizCreateGroupButton.tsx"],"names":[],"mappings":"AAMA,MAAM,WAAW,yBAAyB;IACxC,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,QAAA,MAAM,oBAAoB,GAAI,uBAA+B,yBAAyB,4CAiBrF,CAAC;AAEF,eAAe,oBAAoB,CAAC"}
@@ -0,0 +1,11 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useTranslation } from "react-i18next";
4
+ import { PlusOutlined } from "@ant-design/icons";
5
+ import { BIZ_INBOX_TEST_IDS } from "../../constants/bizInbox";
6
+ const BizCreateGroupButton = ({ onClick, disabled = false }) => {
7
+ const { t } = useTranslation("bizInbox");
8
+ const label = t("actions.create_group");
9
+ return (_jsxs("button", { type: "button", onClick: onClick, disabled: disabled, "aria-label": label, "data-testid": BIZ_INBOX_TEST_IDS.CREATE_GROUP_BUTTON, className: "inline-flex h-10 items-center justify-center gap-2 rounded-lg bg-primary px-4 text-sm font-medium text-white transition-colors hover:bg-primary-600 focus:outline-none focus:ring-2 focus:ring-primary disabled:cursor-not-allowed disabled:opacity-60", children: [_jsx(PlusOutlined, { "aria-hidden": true }), _jsx("span", { children: label })] }));
10
+ };
11
+ export default BizCreateGroupButton;
@@ -0,0 +1,3 @@
1
+ declare const BizInboxHeader: () => import("react/jsx-runtime").JSX.Element;
2
+ export default BizInboxHeader;
3
+ //# sourceMappingURL=BizInboxHeader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BizInboxHeader.d.ts","sourceRoot":"","sources":["../../../src/components/bizInbox/BizInboxHeader.tsx"],"names":[],"mappings":"AAIA,QAAA,MAAM,cAAc,+CAkBnB,CAAC;AAEF,eAAe,cAAc,CAAC"}
@@ -0,0 +1,8 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useTranslation } from "react-i18next";
4
+ const BizInboxHeader = () => {
5
+ const { t } = useTranslation("bizInbox");
6
+ return (_jsx("div", { "data-testid": "biz-inbox-header", className: "flex w-full flex-col border-b border-gray-100 bg-white", children: _jsx("div", { className: "flex h-12 items-end px-3", children: _jsxs("div", { className: "flex h-full flex-col items-center justify-end px-3", children: [_jsx("span", { className: "pb-1.5 text-center text-[16px] font-bold leading-[160%] tracking-[0.16px] text-[#1B3FE4]", children: t("tabs.messages") }), _jsx("span", { className: "h-[3px] w-full rounded-t bg-[#1B3FE4]" })] }) }) }));
7
+ };
8
+ export default BizInboxHeader;
@@ -0,0 +1,8 @@
1
+ import type { BizInboxTab } from "../../types/biz";
2
+ export interface BizInboxTabsProps {
3
+ activeTab: BizInboxTab;
4
+ onChange: (tab: BizInboxTab) => void;
5
+ }
6
+ declare const BizInboxTabs: ({ activeTab, onChange }: BizInboxTabsProps) => import("react/jsx-runtime").JSX.Element;
7
+ export default BizInboxTabs;
8
+ //# sourceMappingURL=BizInboxTabs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BizInboxTabs.d.ts","sourceRoot":"","sources":["../../../src/components/bizInbox/BizInboxTabs.tsx"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAEnD,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,WAAW,CAAC;IACvB,QAAQ,EAAE,CAAC,GAAG,EAAE,WAAW,KAAK,IAAI,CAAC;CACtC;AAqBD,QAAA,MAAM,YAAY,GAAI,yBAAyB,iBAAiB,4CAiC/D,CAAC;AAEF,eAAe,YAAY,CAAC"}
@@ -0,0 +1,27 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { useTranslation } from "react-i18next";
4
+ import clsx from "clsx";
5
+ import { BIZ_INBOX_TABS, BIZ_INBOX_TEST_IDS, } from "../../constants/bizInbox";
6
+ const TAB_CONFIGS = [
7
+ {
8
+ key: BIZ_INBOX_TABS.MESSAGES,
9
+ labelKey: "tabs.messages",
10
+ testId: BIZ_INBOX_TEST_IDS.TAB_MESSAGES,
11
+ },
12
+ {
13
+ key: BIZ_INBOX_TABS.COMMUNITY,
14
+ labelKey: "tabs.community",
15
+ testId: BIZ_INBOX_TEST_IDS.TAB_COMMUNITY,
16
+ },
17
+ ];
18
+ const BizInboxTabs = ({ activeTab, onChange }) => {
19
+ const { t } = useTranslation("bizInbox");
20
+ return (_jsx("div", { role: "tablist", "aria-label": t("tabs.messages"), className: "flex items-center gap-1 border-b border-neutral-200", children: TAB_CONFIGS.map((config) => {
21
+ const isActive = activeTab === config.key;
22
+ return (_jsx("button", { type: "button", role: "tab", "aria-selected": isActive, tabIndex: isActive ? 0 : -1, "data-testid": config.testId, onClick: () => onChange(config.key), className: clsx("h-12 px-4 text-sm font-medium transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-primary", isActive
23
+ ? "border-b-2 border-primary text-primary"
24
+ : "border-b-2 border-transparent text-neutral-700 hover:text-primary"), children: t(config.labelKey) }, config.key));
25
+ }) }));
26
+ };
27
+ export default BizInboxTabs;
@@ -0,0 +1,9 @@
1
+ export interface BizSearchInputProps {
2
+ value: string;
3
+ onChange: (value: string) => void;
4
+ debounceMs?: number;
5
+ placeholderKey?: string;
6
+ }
7
+ declare const BizSearchInput: ({ value, onChange, debounceMs, placeholderKey, }: BizSearchInputProps) => import("react/jsx-runtime").JSX.Element;
8
+ export default BizSearchInput;
9
+ //# sourceMappingURL=BizSearchInput.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BizSearchInput.d.ts","sourceRoot":"","sources":["../../../src/components/bizInbox/BizSearchInput.tsx"],"names":[],"mappings":"AAUA,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,QAAA,MAAM,cAAc,GAAI,kDAKrB,mBAAmB,4CA6BrB,CAAC;AAEF,eAAe,cAAc,CAAC"}
@@ -0,0 +1,24 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { useEffect, useRef, useState } from "react";
4
+ import { useDebounce } from "ahooks";
5
+ import { useTranslation } from "react-i18next";
6
+ import { BIZ_INBOX_DEFAULTS, BIZ_INBOX_TEST_IDS, } from "../../constants/bizInbox";
7
+ const BizSearchInput = ({ value, onChange, debounceMs = BIZ_INBOX_DEFAULTS.SEARCH_DEBOUNCE_MS, placeholderKey = "search.placeholder", }) => {
8
+ const { t } = useTranslation("bizInbox");
9
+ const [internalValue, setInternalValue] = useState(value);
10
+ const debouncedValue = useDebounce(internalValue, { wait: debounceMs });
11
+ const lastEmittedRef = useRef(value);
12
+ useEffect(() => {
13
+ setInternalValue(value);
14
+ lastEmittedRef.current = value;
15
+ }, [value]);
16
+ useEffect(() => {
17
+ if (debouncedValue !== lastEmittedRef.current) {
18
+ lastEmittedRef.current = debouncedValue;
19
+ onChange(debouncedValue);
20
+ }
21
+ }, [debouncedValue, onChange]);
22
+ return (_jsx("input", { type: "search", value: internalValue, onChange: (event) => setInternalValue(event.target.value), placeholder: t(placeholderKey), "aria-label": t("search.aria_label"), "data-testid": BIZ_INBOX_TEST_IDS.SEARCH_INPUT, className: "h-10 w-full rounded-lg border border-neutral-200 bg-white px-4 text-sm text-neutral-900 placeholder:text-neutral-500 focus:border-primary focus:outline-none" }));
23
+ };
24
+ export default BizSearchInput;
@@ -0,0 +1,8 @@
1
+ import type { BizConversationItem } from "../../types/biz";
2
+ export interface BizThreadCardProps {
3
+ item: BizConversationItem;
4
+ onClick: (item: BizConversationItem) => void;
5
+ }
6
+ declare const BizThreadCard: ({ item, onClick }: BizThreadCardProps) => import("react/jsx-runtime").JSX.Element;
7
+ export default BizThreadCard;
8
+ //# sourceMappingURL=BizThreadCard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BizThreadCard.d.ts","sourceRoot":"","sources":["../../../src/components/bizInbox/BizThreadCard.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAG3D,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,mBAAmB,CAAC;IAC1B,OAAO,EAAE,CAAC,IAAI,EAAE,mBAAmB,KAAK,IAAI,CAAC;CAC9C;AAgCD,QAAA,MAAM,aAAa,GAAI,mBAAmB,kBAAkB,4CAkF3D,CAAC;AAEF,eAAe,aAAa,CAAC"}
@@ -0,0 +1,52 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useMemo } from "react";
4
+ import { useTranslation } from "react-i18next";
5
+ import clsx from "clsx";
6
+ import { Icon } from "../icon";
7
+ import { formatTimestamp } from "../../utils/common";
8
+ const resolveDisplayName = (item, fallback) => {
9
+ var _a, _b, _c;
10
+ if (item.peer.user) {
11
+ const fullName = (_a = item.peer.user.fullName) === null || _a === void 0 ? void 0 : _a.trim();
12
+ if (fullName)
13
+ return fullName;
14
+ const username = (_b = item.peer.user.username) === null || _b === void 0 ? void 0 : _b.trim();
15
+ if (username)
16
+ return username;
17
+ }
18
+ if (item.peer.group) {
19
+ const groupName = (_c = item.peer.group.name) === null || _c === void 0 ? void 0 : _c.trim();
20
+ if (groupName)
21
+ return groupName;
22
+ }
23
+ return fallback;
24
+ };
25
+ const resolveAvatarUrl = (item) => {
26
+ var _a, _b, _c, _d;
27
+ return (_d = (_b = (_a = item.peer.user) === null || _a === void 0 ? void 0 : _a.avatar) !== null && _b !== void 0 ? _b : (_c = item.peer.group) === null || _c === void 0 ? void 0 : _c.avatar) !== null && _d !== void 0 ? _d : null;
28
+ };
29
+ const formatLatestMessageTime = (latestMessageAt) => {
30
+ if (!latestMessageAt)
31
+ return "";
32
+ const timestamp = new Date(latestMessageAt).getTime();
33
+ if (Number.isNaN(timestamp))
34
+ return "";
35
+ return formatTimestamp(timestamp, { hasTime: true });
36
+ };
37
+ const BizThreadCard = ({ item, onClick }) => {
38
+ const { t } = useTranslation("bizInbox");
39
+ const displayName = useMemo(() => resolveDisplayName(item, t("thread_card.fallback_name")), [item, t]);
40
+ const avatarUrl = resolveAvatarUrl(item);
41
+ const badgeLabel = item.chatCategory === "BIZ_MALL"
42
+ ? t("thread_card.badge.mall")
43
+ : item.chatCategory === "BIZ_BOT_PDP"
44
+ ? t("thread_card.badge.bot")
45
+ : "";
46
+ const latestMessageTime = formatLatestMessageTime(item.latestMessageAt);
47
+ const handleClick = () => {
48
+ onClick(item);
49
+ };
50
+ return (_jsxs("button", { type: "button", onClick: handleClick, "data-testid": "biz-inbox-thread-card", className: "flex w-full shrink-0 items-center gap-2 border-b border-gray-100 bg-white p-3 text-left transition-colors hover:bg-gray-50 focus:outline-none focus-visible:ring-2 focus-visible:ring-[#1677ff]", children: [_jsxs("div", { className: "relative h-12 w-12 shrink-0", children: [avatarUrl ? (_jsx("img", { src: avatarUrl, alt: displayName, className: "h-12 w-12 rounded-full object-cover" })) : (_jsx("div", { className: "flex h-12 w-12 items-center justify-center rounded-full bg-[#0C5CFF] text-white", children: item.chatCategory === "GROUP" ? (_jsx(Icon, { icon: "user-two-b", size: 20 })) : item.chatCategory === "BIZ_BOT_PDP" ? (_jsx(Icon, { icon: "droppii-o-1", size: 20 })) : (_jsx(Icon, { icon: "user-o", size: 20 })) })), badgeLabel && (_jsx("span", { className: clsx("absolute -bottom-1 left-1/2 max-w-[38px] -translate-x-1/2 rounded-md border border-white px-1.5 py-0.5 text-[10px] font-semibold leading-3 text-white shadow-[inset_2px_6px_8px_0_rgba(0,0,0,0.40)]", item.chatCategory === "BIZ_MALL" ? "bg-[#F28504]" : "bg-[#59ABFF]"), children: badgeLabel }))] }), _jsx("div", { className: "flex min-w-0 flex-1 flex-col gap-1 pt-0.5", children: _jsxs("div", { className: "flex min-w-0 items-center gap-1", children: [item.chatCategory === "GROUP" && (_jsx(Icon, { icon: "user-two-b", size: 12, className: "shrink-0 text-black" })), item.chatCategory === "BIZ_BOT_PDP" && (_jsx("span", { className: "flex h-4 w-4 shrink-0 items-center justify-center gap-0.5 rounded-full border border-white bg-[linear-gradient(93deg,#607CFB_0%,#1B3FE4_100%)] px-1 py-0.5 text-white", children: _jsx(Icon, { icon: "shield-done-b", size: 8 }) })), _jsx("span", { className: "truncate text-[16px] font-[510] leading-[160%] text-black", children: displayName })] }) }), latestMessageTime && (_jsx("span", { className: "shrink-0 text-[12px] font-normal leading-[160%] text-[#747B7E] [font-feature-settings:'liga'_off,'clig'_off]", children: latestMessageTime }))] }));
51
+ };
52
+ export default BizThreadCard;
@@ -0,0 +1,11 @@
1
+ import type { BizConversationItem } from "../../types/biz";
2
+ export interface BizThreadListProps {
3
+ items: BizConversationItem[];
4
+ isFetchingNextPage: boolean;
5
+ hasNextPage: boolean;
6
+ onLoadMore: () => void;
7
+ onSelectThread: (item: BizConversationItem) => void;
8
+ }
9
+ declare const BizThreadList: ({ items, isFetchingNextPage, hasNextPage, onLoadMore, onSelectThread, }: BizThreadListProps) => import("react/jsx-runtime").JSX.Element;
10
+ export default BizThreadList;
11
+ //# sourceMappingURL=BizThreadList.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BizThreadList.d.ts","sourceRoot":"","sources":["../../../src/components/bizInbox/BizThreadList.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAI3D,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,mBAAmB,EAAE,CAAC;IAC7B,kBAAkB,EAAE,OAAO,CAAC;IAC5B,WAAW,EAAE,OAAO,CAAC;IACrB,UAAU,EAAE,MAAM,IAAI,CAAC;IACvB,cAAc,EAAE,CAAC,IAAI,EAAE,mBAAmB,KAAK,IAAI,CAAC;CACrD;AAYD,QAAA,MAAM,aAAa,GAAI,yEAMpB,kBAAkB,4CA6CpB,CAAC;AAEF,eAAe,aAAa,CAAC"}
@@ -0,0 +1,17 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useCallback } from "react";
4
+ import { Empty } from "antd";
5
+ import InfiniteScroll from "react-infinite-scroll-component";
6
+ import { Icon } from "../icon";
7
+ import BizThreadCard from "./BizThreadCard";
8
+ const FooterSkeleton = () => (_jsxs("div", { className: "flex items-center gap-3 p-3", children: [_jsx("div", { className: "h-10 w-10 animate-pulse rounded-full bg-neutral-200" }), _jsxs("div", { className: "flex flex-1 flex-col gap-2", children: [_jsx("div", { className: "h-3 w-1/2 animate-pulse rounded bg-neutral-200" }), _jsx("div", { className: "h-2.5 w-1/3 animate-pulse rounded bg-neutral-200" })] })] }));
9
+ const BizThreadList = ({ items, isFetchingNextPage, hasNextPage, onLoadMore, onSelectThread, }) => {
10
+ const handleEndReached = useCallback(() => {
11
+ if (hasNextPage && !isFetchingNextPage) {
12
+ onLoadMore();
13
+ }
14
+ }, [hasNextPage, isFetchingNextPage, onLoadMore]);
15
+ return (_jsx("div", { id: "scrollableBizThreadsDiv", "data-testid": "biz-inbox-thread-list", className: "min-h-0 w-full flex-1 overflow-auto", children: _jsx(InfiniteScroll, { dataLength: items.length, next: handleEndReached, hasMore: hasNextPage, loader: isFetchingNextPage ? _jsx(FooterSkeleton, {}) : null, scrollableTarget: "scrollableBizThreadsDiv", children: items.length === 0 ? (_jsx("div", { className: "flex items-center justify-center py-12", children: _jsx(Empty, { image: _jsx(Icon, { icon: "chat-square-b", size: 80, className: "text-gray-300" }), description: "No conversations" }) })) : (items.map((item) => (_jsx(BizThreadCard, { item: item, onClick: onSelectThread }, item.conversationId)))) }) }));
16
+ };
17
+ export default BizThreadList;
@@ -0,0 +1,6 @@
1
+ export { default as BizInboxHeader } from "./BizInboxHeader";
2
+ export { default as BizThreadCard } from "./BizThreadCard";
3
+ export type { BizThreadCardProps } from "./BizThreadCard";
4
+ export { default as BizThreadList } from "./BizThreadList";
5
+ export type { BizThreadListProps } from "./BizThreadList";
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/bizInbox/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAE7D,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAC3D,YAAY,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAE1D,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAC3D,YAAY,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC"}
@@ -0,0 +1,3 @@
1
+ export { default as BizInboxHeader } from "./BizInboxHeader";
2
+ export { default as BizThreadCard } from "./BizThreadCard";
3
+ export { default as BizThreadList } from "./BizThreadList";
@@ -0,0 +1,7 @@
1
+ export type BizThreadListEmptyReason = "no_data" | "invalid_application_type";
2
+ export interface BizThreadListEmptyProps {
3
+ reason?: BizThreadListEmptyReason;
4
+ }
5
+ declare const BizThreadListEmpty: ({ reason }: BizThreadListEmptyProps) => import("react/jsx-runtime").JSX.Element;
6
+ export default BizThreadListEmpty;
7
+ //# sourceMappingURL=BizThreadListEmpty.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BizThreadListEmpty.d.ts","sourceRoot":"","sources":["../../../../src/components/bizInbox/states/BizThreadListEmpty.tsx"],"names":[],"mappings":"AAMA,MAAM,MAAM,wBAAwB,GAAG,SAAS,GAAG,0BAA0B,CAAC;AAE9E,MAAM,WAAW,uBAAuB;IACtC,MAAM,CAAC,EAAE,wBAAwB,CAAC;CACnC;AAOD,QAAA,MAAM,kBAAkB,GAAI,YAAwB,uBAAuB,4CAmB1E,CAAC;AAEF,eAAe,kBAAkB,CAAC"}
@@ -0,0 +1,15 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useTranslation } from "react-i18next";
4
+ import { InboxOutlined } from "@ant-design/icons";
5
+ import { BIZ_INBOX_TEST_IDS } from "../../../constants/bizInbox";
6
+ const REASON_KEY_MAP = {
7
+ no_data: "empty.thread_list",
8
+ invalid_application_type: "empty.invalid_application_type",
9
+ };
10
+ const BizThreadListEmpty = ({ reason = "no_data" }) => {
11
+ const { t } = useTranslation("bizInbox");
12
+ const baseKey = REASON_KEY_MAP[reason];
13
+ return (_jsxs("div", { role: "status", "data-testid": BIZ_INBOX_TEST_IDS.STATE_EMPTY, className: "flex h-full flex-col items-center justify-center gap-3 p-8 text-center", children: [_jsx(InboxOutlined, { "aria-hidden": true, className: "text-4xl text-neutral-500" }), _jsx("div", { className: "text-base font-semibold text-neutral-900", children: t(`${baseKey}.title`) }), _jsx("div", { className: "max-w-sm text-sm text-neutral-500", children: t(`${baseKey}.description`) })] }));
14
+ };
15
+ export default BizThreadListEmpty;
@@ -0,0 +1,6 @@
1
+ export interface BizThreadListErrorProps {
2
+ onRetry: () => void;
3
+ }
4
+ declare const BizThreadListError: ({ onRetry }: BizThreadListErrorProps) => import("react/jsx-runtime").JSX.Element;
5
+ export default BizThreadListError;
6
+ //# sourceMappingURL=BizThreadListError.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BizThreadListError.d.ts","sourceRoot":"","sources":["../../../../src/components/bizInbox/states/BizThreadListError.tsx"],"names":[],"mappings":"AAMA,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,QAAA,MAAM,kBAAkB,GAAI,aAAa,uBAAuB,4CA2B/D,CAAC;AAEF,eAAe,kBAAkB,CAAC"}
@@ -0,0 +1,11 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useTranslation } from "react-i18next";
4
+ import { ExclamationCircleOutlined } from "@ant-design/icons";
5
+ import { BIZ_INBOX_TEST_IDS } from "../../../constants/bizInbox";
6
+ const BizThreadListError = ({ onRetry }) => {
7
+ const { t } = useTranslation("bizInbox");
8
+ const retryLabel = t("actions.retry");
9
+ return (_jsxs("div", { role: "alert", "data-testid": BIZ_INBOX_TEST_IDS.STATE_ERROR, className: "flex h-full flex-col items-center justify-center gap-3 p-8 text-center", children: [_jsx(ExclamationCircleOutlined, { "aria-hidden": true, className: "text-4xl text-red-500" }), _jsx("div", { className: "text-base font-semibold text-neutral-900", children: t("error.thread_list.title") }), _jsx("div", { className: "max-w-sm text-sm text-neutral-500", children: t("error.thread_list.description") }), _jsx("button", { type: "button", onClick: onRetry, "aria-label": retryLabel, className: "mt-2 inline-flex h-9 items-center justify-center rounded-lg bg-primary px-4 text-sm font-medium text-white hover:bg-primary-600 focus:outline-none focus:ring-2 focus:ring-primary", children: retryLabel })] }));
10
+ };
11
+ export default BizThreadListError;
@@ -0,0 +1,6 @@
1
+ export interface BizThreadListLoadingProps {
2
+ itemCount?: number;
3
+ }
4
+ declare const BizThreadListLoading: ({ itemCount, }: BizThreadListLoadingProps) => import("react/jsx-runtime").JSX.Element;
5
+ export default BizThreadListLoading;
6
+ //# sourceMappingURL=BizThreadListLoading.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BizThreadListLoading.d.ts","sourceRoot":"","sources":["../../../../src/components/bizInbox/states/BizThreadListLoading.tsx"],"names":[],"mappings":"AAOA,MAAM,WAAW,yBAAyB;IACxC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,QAAA,MAAM,oBAAoB,GAAI,gBAE3B,yBAAyB,4CAoB3B,CAAC;AAEF,eAAe,oBAAoB,CAAC"}
@@ -0,0 +1,8 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { BIZ_INBOX_DEFAULTS, BIZ_INBOX_TEST_IDS, } from "../../../constants/bizInbox";
4
+ const BizThreadListLoading = ({ itemCount = BIZ_INBOX_DEFAULTS.THREAD_LIST_SKELETON_COUNT, }) => {
5
+ const items = Array.from({ length: itemCount }, (_, index) => index);
6
+ return (_jsx("div", { role: "status", "data-testid": BIZ_INBOX_TEST_IDS.STATE_LOADING, className: "flex h-full flex-col gap-2 p-4", children: items.map((index) => (_jsxs("div", { className: "flex items-center gap-3 rounded-lg p-3", children: [_jsx("div", { className: "h-10 w-10 animate-pulse rounded-full bg-neutral-200" }), _jsxs("div", { className: "flex flex-1 flex-col gap-2", children: [_jsx("div", { className: "h-3 w-1/2 animate-pulse rounded bg-neutral-200" }), _jsx("div", { className: "h-2.5 w-1/3 animate-pulse rounded bg-neutral-200" })] })] }, index))) }));
7
+ };
8
+ export default BizThreadListLoading;
@@ -1 +1 @@
1
- {"version":3,"file":"ActionBar.d.ts","sourceRoot":"","sources":["../../../../src/components/message/footer/ActionBar.tsx"],"names":[],"mappings":"AAsBA,QAAA,MAAM,SAAS,+CA2Gd,CAAC;AAEF,eAAe,SAAS,CAAC"}
1
+ {"version":3,"file":"ActionBar.d.ts","sourceRoot":"","sources":["../../../../src/components/message/footer/ActionBar.tsx"],"names":[],"mappings":"AAsBA,QAAA,MAAM,SAAS,+CA+Gd,CAAC;AAEF,eAAe,SAAS,CAAC"}
@@ -30,12 +30,15 @@ const ActionBar = () => {
30
30
  richText = $generateHtmlFromNodes(editor);
31
31
  });
32
32
  if (canSend) {
33
- onSendMessage({
33
+ const sent = onSendMessage({
34
34
  plainText,
35
35
  richText,
36
36
  type: listUploadFiles.length > 0 ? "file" : "text",
37
37
  isInternal,
38
38
  });
39
+ if (!sent) {
40
+ return;
41
+ }
39
42
  }
40
43
  editor.update(() => {
41
44
  const root = $getRoot();
@@ -1 +1 @@
1
- {"version":3,"file":"EnterHandler.d.ts","sourceRoot":"","sources":["../../../../src/components/message/footer/EnterHandler.tsx"],"names":[],"mappings":"AAuBA,MAAM,CAAC,OAAO,UAAU,YAAY,SAiHnC"}
1
+ {"version":3,"file":"EnterHandler.d.ts","sourceRoot":"","sources":["../../../../src/components/message/footer/EnterHandler.tsx"],"names":[],"mappings":"AAuBA,MAAM,CAAC,OAAO,UAAU,YAAY,SAsInC"}
@@ -7,7 +7,7 @@ import { $isListNode, INSERT_ORDERED_LIST_COMMAND, INSERT_UNORDERED_LIST_COMMAND
7
7
  import { $isQuoteNode } from "@lexical/rich-text";
8
8
  import { useMessageFooterContext } from ".";
9
9
  import useConversationStore from "../../../store/conversation";
10
- import { emit } from "../../../utils/events";
10
+ import emitter, { emit } from "../../../utils/events";
11
11
  export default function EnterHandler() {
12
12
  const [editor] = useLexicalComposerContext();
13
13
  const { onSendMessage, isInternal, listUploadFiles, isOpenCanned, activeCannedIndex, } = useMessageFooterContext();
@@ -35,12 +35,15 @@ export default function EnterHandler() {
35
35
  const hasQuote = !!(quotedMessage === null || quotedMessage === void 0 ? void 0 : quotedMessage.clientMsgID);
36
36
  const canSend = hasQuote ? hasText : hasText || hasFiles;
37
37
  if (canSend) {
38
- onSendMessage({
38
+ const sent = onSendMessage({
39
39
  plainText,
40
40
  richText,
41
41
  type: hasFiles ? "file" : "text",
42
42
  isInternal,
43
43
  });
44
+ if (!sent) {
45
+ return true;
46
+ }
44
47
  }
45
48
  editor.update(() => {
46
49
  const root = $getRoot();
@@ -95,5 +98,20 @@ export default function EnterHandler() {
95
98
  isOpenCanned,
96
99
  activeCannedIndex,
97
100
  ]);
101
+ useEffect(() => {
102
+ const clearEditor = () => {
103
+ editor.update(() => {
104
+ const root = $getRoot();
105
+ root.clear();
106
+ const paragraph = $createParagraphNode();
107
+ root.append(paragraph);
108
+ paragraph.select();
109
+ });
110
+ };
111
+ emitter.on("CLEAR_COMPOSER", clearEditor);
112
+ return () => {
113
+ emitter.off("CLEAR_COMPOSER", clearEditor);
114
+ };
115
+ }, [editor]);
98
116
  return null;
99
117
  }
@@ -0,0 +1,10 @@
1
+ interface SessionClosedModalProps {
2
+ open: boolean;
3
+ loading: boolean;
4
+ onReopen: () => void;
5
+ onCreateNew: () => void;
6
+ onClose: () => void;
7
+ }
8
+ declare const SessionClosedModal: ({ open, loading, onReopen, onCreateNew, onClose, }: SessionClosedModalProps) => import("react/jsx-runtime").JSX.Element;
9
+ export default SessionClosedModal;
10
+ //# sourceMappingURL=SessionClosedModal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SessionClosedModal.d.ts","sourceRoot":"","sources":["../../../../src/components/message/footer/SessionClosedModal.tsx"],"names":[],"mappings":"AAKA,UAAU,uBAAuB;IAC/B,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,WAAW,EAAE,MAAM,IAAI,CAAC;IACxB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,QAAA,MAAM,kBAAkB,GAAI,oDAMzB,uBAAuB,4CAmDzB,CAAC;AAEF,eAAe,kBAAkB,CAAC"}
@@ -0,0 +1,9 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Modal, Button } from "antd";
4
+ import { useTranslation } from "react-i18next";
5
+ const SessionClosedModal = ({ open, loading, onReopen, onCreateNew, onClose, }) => {
6
+ const { t } = useTranslation();
7
+ return (_jsx(Modal, { open: open, onCancel: onClose, footer: null, closable: !loading, centered: true, width: 400, maskClosable: !loading, keyboard: !loading, forceRender: true, children: _jsxs("div", { className: "flex flex-col gap-4 pt-2", role: "alertdialog", "aria-label": t("session_closed_modal_title"), children: [_jsx("p", { className: "text-base font-semibold", children: t("session_closed_modal_title") }), _jsx("p", { className: "text-sm text-gray-600", children: t("session_closed_modal_description") }), _jsxs("div", { className: "flex gap-3 w-full", children: [_jsx(Button, { type: "primary", className: "flex-1", size: "large", onClick: onReopen, loading: loading, disabled: loading, children: t("session_closed_modal_reopen") }), _jsx(Button, { type: "primary", className: "flex-1", size: "large", onClick: onCreateNew, loading: loading, disabled: loading, children: t("session_closed_modal_create_new") })] })] }) }));
8
+ };
9
+ export default SessionClosedModal;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/message/footer/index.tsx"],"names":[],"mappings":"AAgBA,OAAO,EAAe,wBAAwB,EAAE,MAAM,qBAAqB,CAAC;AAK5E,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAYtD,UAAU,kBAAkB;IAC1B,cAAc,CAAC,EAAE,gBAAgB,CAAC;IAClC,qBAAqB,CAAC,EAAE,MAAM,IAAI,CAAC;CACpC;AASD,eAAO,MAAM,oBAAoB,mDAY/B,CAAC;AAEH,eAAO,MAAM,uBAAuB,gCAAyC,CAAC;AAE9E,QAAA,MAAM,qBAAqB,GAAI,4CAG5B,kBAAkB,4CA+JpB,CAAC;AAEF,eAAe,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/message/footer/index.tsx"],"names":[],"mappings":"AAgBA,OAAO,EAAe,wBAAwB,EAAE,MAAM,qBAAqB,CAAC;AAK5E,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAkBtD,UAAU,kBAAkB;IAC1B,cAAc,CAAC,EAAE,gBAAgB,CAAC;IAClC,qBAAqB,CAAC,EAAE,MAAM,IAAI,CAAC;CACpC;AASD,eAAO,MAAM,oBAAoB,mDAY/B,CAAC;AAEH,eAAO,MAAM,uBAAuB,gCAAyC,CAAC;AAE9E,QAAA,MAAM,qBAAqB,GAAI,4CAG5B,kBAAkB,4CA8QpB,CAAC;AAEF,eAAe,qBAAqB,CAAC"}