@newfold/wp-module-ai-chat 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/README.md +98 -0
  2. package/package.json +51 -0
  3. package/src/components/chat/ChatHeader.jsx +63 -0
  4. package/src/components/chat/ChatHistoryDropdown.jsx +182 -0
  5. package/src/components/chat/ChatHistoryList.jsx +257 -0
  6. package/src/components/chat/ChatInput.jsx +157 -0
  7. package/src/components/chat/ChatMessage.jsx +157 -0
  8. package/src/components/chat/ChatMessages.jsx +137 -0
  9. package/src/components/chat/WelcomeScreen.jsx +115 -0
  10. package/src/components/icons/CloseIcon.jsx +27 -0
  11. package/src/components/icons/SparklesOutlineIcon.jsx +30 -0
  12. package/src/components/icons/index.js +5 -0
  13. package/src/components/ui/AILogo.jsx +47 -0
  14. package/src/components/ui/BluBetaHeading.jsx +18 -0
  15. package/src/components/ui/ErrorAlert.jsx +30 -0
  16. package/src/components/ui/HeaderBar.jsx +34 -0
  17. package/src/components/ui/SuggestionButton.jsx +28 -0
  18. package/src/components/ui/ToolExecutionList.jsx +264 -0
  19. package/src/components/ui/TypingIndicator.jsx +268 -0
  20. package/src/constants/nfdAgents/input.js +13 -0
  21. package/src/constants/nfdAgents/storageKeys.js +102 -0
  22. package/src/constants/nfdAgents/typingStatus.js +40 -0
  23. package/src/constants/nfdAgents/websocket.js +44 -0
  24. package/src/hooks/useAIChat.js +432 -0
  25. package/src/hooks/useNfdAgentsWebSocket.js +964 -0
  26. package/src/index.js +66 -0
  27. package/src/services/mcpClient.js +433 -0
  28. package/src/services/openaiClient.js +416 -0
  29. package/src/styles/_branding.scss +151 -0
  30. package/src/styles/_history.scss +180 -0
  31. package/src/styles/_input.scss +170 -0
  32. package/src/styles/_messages.scss +272 -0
  33. package/src/styles/_mixins.scss +21 -0
  34. package/src/styles/_typing-indicator.scss +162 -0
  35. package/src/styles/_ui.scss +173 -0
  36. package/src/styles/_vars.scss +103 -0
  37. package/src/styles/_welcome.scss +81 -0
  38. package/src/styles/app.scss +10 -0
  39. package/src/utils/helpers.js +75 -0
  40. package/src/utils/markdownParser.js +319 -0
  41. package/src/utils/nfdAgents/archiveConversation.js +82 -0
  42. package/src/utils/nfdAgents/chatHistoryList.js +130 -0
  43. package/src/utils/nfdAgents/configFetcher.js +137 -0
  44. package/src/utils/nfdAgents/greeting.js +55 -0
  45. package/src/utils/nfdAgents/jwtUtils.js +59 -0
  46. package/src/utils/nfdAgents/messageHandler.js +328 -0
  47. package/src/utils/nfdAgents/storage.js +112 -0
  48. package/src/utils/nfdAgents/typingIndicatorToolDisplay.js +180 -0
  49. package/src/utils/nfdAgents/url.js +101 -0
  50. package/src/utils/restApi.js +87 -0
  51. package/src/utils/sanitizeHtml.js +94 -0
@@ -0,0 +1,162 @@
1
+ /* Typing Indicator */
2
+ .nfd-ai-chat-typing-indicator {
3
+ display: flex;
4
+ align-items: center;
5
+ gap: var(--nfd-ai-chat-spacing-sm);
6
+ padding: var(--nfd-ai-chat-spacing-xs) 0;
7
+ }
8
+
9
+ .nfd-ai-chat-typing-indicator__dots {
10
+ display: flex;
11
+ align-items: center;
12
+ gap: var(--nfd-ai-chat-spacing-xs);
13
+
14
+ span {
15
+ width: 6px;
16
+ height: 6px;
17
+ border-radius: 50%;
18
+ background: var(--nfd-ai-chat-color-grey-dark);
19
+ animation: nfd-ai-chat-typing-bounce 1.4s ease-in-out infinite both;
20
+
21
+ &:nth-child(2) {
22
+ animation-delay: 0.2s;
23
+ }
24
+
25
+ &:nth-child(3) {
26
+ animation-delay: 0.4s;
27
+ }
28
+ }
29
+ }
30
+
31
+ @keyframes nfd-ai-chat-typing-bounce {
32
+
33
+ 0%,
34
+ 60%,
35
+ 100% {
36
+ transform: translateY(0);
37
+ }
38
+
39
+ 30% {
40
+ transform: translateY(-4px);
41
+ }
42
+ }
43
+
44
+ .nfd-ai-chat-typing-indicator__text {
45
+ font-size: var(--nfd-ai-chat-font-size-base);
46
+ color: var(--nfd-ai-chat-color-grey-dark);
47
+ min-width: 0;
48
+ }
49
+
50
+ .nfd-ai-chat-typing-indicator__reasoning {
51
+ font-size: var(--nfd-ai-chat-font-size-sm);
52
+ color: var(--nfd-ai-chat-color-grey-medium);
53
+ font-style: italic;
54
+ line-height: 1.4;
55
+ margin-top: 4px;
56
+ }
57
+
58
+ @keyframes nfd-ai-chat-spin {
59
+
60
+ from {
61
+ transform: rotate(0deg);
62
+ }
63
+
64
+ to {
65
+ transform: rotate(360deg);
66
+ }
67
+ }
68
+
69
+ /* Tool Execution */
70
+ .nfd-ai-chat-tool-execution {
71
+ width: 100%;
72
+ }
73
+
74
+ .nfd-ai-chat-tool-execution__header {
75
+ display: flex;
76
+ align-items: center;
77
+ gap: 6px;
78
+ padding: 4px 0;
79
+ background: none;
80
+ border: none;
81
+ cursor: pointer;
82
+ font-size: var(--nfd-ai-chat-font-size-sm);
83
+ color: var(--nfd-ai-chat-color-grey-dark);
84
+ width: 100%;
85
+ text-align: left;
86
+
87
+ &:hover {
88
+ color: var(--nfd-ai-chat-color-text);
89
+ }
90
+ }
91
+
92
+ .nfd-ai-chat-tool-execution__chevron {
93
+ flex-shrink: 0;
94
+ }
95
+
96
+ .nfd-ai-chat-tool-execution__header-count {
97
+ color: var(--nfd-ai-chat-color-grey-medium);
98
+ margin-left: 4px;
99
+ }
100
+
101
+ .nfd-ai-chat-tool-execution__list {
102
+ display: flex;
103
+ flex-direction: column;
104
+ }
105
+
106
+ .nfd-ai-chat-tool-execution__item {
107
+ display: flex;
108
+ flex-direction: column;
109
+ gap: 2px;
110
+ padding: 4px 0;
111
+ }
112
+
113
+ .nfd-ai-chat-tool-execution__item-header {
114
+ display: flex;
115
+ align-items: center;
116
+ gap: 6px;
117
+ }
118
+
119
+ .nfd-ai-chat-tool-execution__icon {
120
+ flex-shrink: 0;
121
+
122
+ &--active {
123
+ color: var(--nfd-ai-chat-color-primary);
124
+ animation: nfd-ai-chat-spin 1s linear infinite;
125
+ }
126
+
127
+ &--success {
128
+ color: var(--nfd-ai-chat-color-success);
129
+ }
130
+
131
+ &--error {
132
+ color: var(--nfd-ai-chat-color-error);
133
+ }
134
+
135
+ &--pending {
136
+ color: var(--nfd-ai-chat-color-grey-medium);
137
+ }
138
+ }
139
+
140
+ .nfd-ai-chat-tool-execution__item-title {
141
+ font-size: var(--nfd-ai-chat-font-size-sm);
142
+ color: var(--nfd-ai-chat-color-text);
143
+ }
144
+
145
+ .nfd-ai-chat-tool-execution__item-params {
146
+ font-size: var(--nfd-ai-chat-font-size-sm);
147
+ color: var(--nfd-ai-chat-color-grey-medium);
148
+ margin-left: 4px;
149
+ }
150
+
151
+ .nfd-ai-chat-tool-execution__item-progress {
152
+ font-size: var(--nfd-ai-chat-font-size-sm);
153
+ color: var(--nfd-ai-chat-color-grey-dark);
154
+ padding-left: 18px;
155
+ }
156
+
157
+ .nfd-ai-chat-tool-execution__item-summary {
158
+ font-size: var(--nfd-ai-chat-font-size-sm);
159
+ color: var(--nfd-ai-chat-color-grey-medium);
160
+ padding-left: 18px;
161
+ margin-top: 2px;
162
+ }
@@ -0,0 +1,173 @@
1
+ /* AI Avatar */
2
+ .nfd-ai-chat-avatar {
3
+ display: flex;
4
+ align-items: center;
5
+ justify-content: center;
6
+ border-radius: var(--nfd-ai-chat-radius-full);
7
+ background: linear-gradient(135deg, var(--nfd-ai-chat-color-primary) 0%, var(--nfd-ai-chat-color-primary-hover) 100%);
8
+ color: var(--nfd-ai-chat-color-white);
9
+ transition: transform 0.2s ease, box-shadow 0.2s ease;
10
+
11
+ svg {
12
+ fill: currentcolor;
13
+ }
14
+ }
15
+
16
+ /* Chat header - white background, AILogo + pill + actions */
17
+ .nfd-ai-chat-header {
18
+ display: flex;
19
+ align-items: center;
20
+ justify-content: space-between;
21
+ gap: 12px;
22
+ background-color: var(--nfd-ai-chat-color-white);
23
+ padding: 12px 16px;
24
+ flex-shrink: 0;
25
+ }
26
+
27
+ /* Header logo: no background, plain icon (override brand) */
28
+ .nfd-ai-chat-header .nfd-ai-chat-header__brand .nfd-ai-chat-avatar {
29
+ background: none;
30
+ color: var(--nfd-ai-chat-color-text, #1e1e1e);
31
+ box-shadow: none;
32
+ }
33
+
34
+ .nfd-ai-chat-header__brand {
35
+ display: flex;
36
+ align-items: center;
37
+ gap: 8px;
38
+ color: var(--nfd-ai-chat-color-text, #1e1e1e);
39
+ }
40
+
41
+ .nfd-ai-chat-header__title {
42
+ font-size: var(--nfd-ai-chat-font-size-base);
43
+ font-weight: 600;
44
+ color: var(--nfd-ai-chat-color-text, #1e1e1e);
45
+ line-height: 1;
46
+ }
47
+
48
+ .nfd-ai-chat-header__actions {
49
+ display: flex;
50
+ align-items: center;
51
+ gap: 8px;
52
+ }
53
+
54
+ .nfd-ai-chat-header__btn {
55
+ display: flex;
56
+ align-items: center;
57
+ justify-content: center;
58
+ width: 28px;
59
+ height: 28px;
60
+ padding: 0;
61
+ border: none;
62
+ background: transparent;
63
+ color: var(--nfd-ai-chat-color-text, #1e1e1e);
64
+ cursor: pointer;
65
+ border-radius: var(--nfd-ai-chat-radius-sm, 4px);
66
+ transition: background-color 0.15s ease, color 0.15s ease;
67
+ }
68
+
69
+ .nfd-ai-chat-header__btn:disabled {
70
+ opacity: 0.5;
71
+ cursor: not-allowed;
72
+ }
73
+
74
+ .nfd-ai-chat-header__btn:hover:not(:disabled) {
75
+ background-color: var(--nfd-ai-chat-color-background, #f5f5f5);
76
+ }
77
+
78
+ .nfd-ai-chat-header__btn--new {
79
+ font-size: 16px;
80
+ line-height: 1;
81
+ font-weight: 300;
82
+ }
83
+
84
+ .nfd-ai-chat-header__btn--close svg {
85
+ display: block;
86
+ }
87
+
88
+ /* BETA badge - solid dark blue pill (matches editor-chat sidebar) */
89
+ .nfd-ai-chat-blu-beta-badge {
90
+ display: inline-flex;
91
+ align-items: center;
92
+ justify-content: center;
93
+ font-size: 10px;
94
+ font-weight: 600;
95
+ padding: 4px 8px;
96
+ border-radius: 4px;
97
+ background-color: var(--nfd-ai-chat-color-primary);
98
+ color: var(--nfd-ai-chat-color-white);
99
+ text-transform: uppercase;
100
+ letter-spacing: 0.25px;
101
+ line-height: 1;
102
+ }
103
+
104
+ /* Error Alert */
105
+ .nfd-ai-chat-error-alert {
106
+ display: flex;
107
+ align-items: flex-start;
108
+ gap: 12px;
109
+ padding: 12px 16px;
110
+ background: rgba(214, 54, 56, 0.08);
111
+ border: 1px solid var(--nfd-ai-chat-color-error);
112
+ border-radius: 8px;
113
+ margin: 8px 0;
114
+ }
115
+
116
+ .nfd-ai-chat-error-alert__icon {
117
+ flex-shrink: 0;
118
+ color: var(--nfd-ai-chat-color-error);
119
+ margin-top: 2px;
120
+ }
121
+
122
+ .nfd-ai-chat-error-alert__content {
123
+ flex: 1;
124
+ min-width: 0;
125
+ overflow: hidden;
126
+ }
127
+
128
+ .nfd-ai-chat-error-alert__message {
129
+ font-size: var(--nfd-ai-chat-font-size-base);
130
+ color: var(--nfd-ai-chat-color-error);
131
+ line-height: 1.5;
132
+ overflow-wrap: break-word;
133
+ word-break: break-word;
134
+ min-width: 0;
135
+ }
136
+
137
+ /* Suggestion Button */
138
+ .components-button.nfd-ai-chat-suggestion {
139
+ display: flex;
140
+ align-items: center;
141
+ gap: var(--nfd-ai-chat-spacing-sm);
142
+ padding: var(--nfd-ai-chat-spacing-sm) var(--nfd-ai-chat-spacing-md);
143
+ background: var(--nfd-ai-chat-color-white);
144
+ border: 1px solid var(--nfd-ai-chat-color-border);
145
+ border-radius: var(--nfd-ai-chat-radius-full);
146
+ font-size: var(--nfd-ai-chat-font-size-sm);
147
+ color: var(--nfd-ai-chat-color-text-secondary);
148
+ cursor: pointer;
149
+ transition: all 0.15s ease;
150
+ box-shadow: var(--nfd-ai-chat-shadow-sm);
151
+
152
+ &:hover {
153
+ background: var(--nfd-ai-chat-color-background);
154
+ border-color: var(--nfd-ai-chat-color-primary);
155
+ color: var(--nfd-ai-chat-color-primary);
156
+ transform: translateY(-1px);
157
+ box-shadow: var(--nfd-ai-chat-shadow-md);
158
+ }
159
+
160
+ &:active {
161
+ transform: translateY(0);
162
+ }
163
+ }
164
+
165
+ .nfd-ai-chat-suggestion__icon {
166
+ display: flex;
167
+ align-items: center;
168
+ justify-content: center;
169
+ }
170
+
171
+ .nfd-ai-chat-suggestion__text {
172
+ white-space: nowrap;
173
+ }
@@ -0,0 +1,103 @@
1
+ /* -------------------------------------------------------------------------- */
2
+
3
+ /* AI Chat CSS Variables */
4
+
5
+ /* -------------------------------------------------------------------------- */
6
+
7
+ :root {
8
+
9
+ /* Default/Base colors - can be overridden by brand-specific variables */
10
+ --nfd-ai-chat-color-primary: #2271b1;
11
+ --nfd-ai-chat-color-primary-hover: #135e96;
12
+ --nfd-ai-chat-color-primary-active: #0e4b72;
13
+ --nfd-ai-chat-color-primary-light-8: rgba(34, 113, 177, 0.08);
14
+ --nfd-ai-chat-color-primary-light-12: rgba(34, 113, 177, 0.12);
15
+ --nfd-ai-chat-color-primary-light-16: rgba(34, 113, 177, 0.16);
16
+ --nfd-ai-chat-color-primary-light-20: rgba(34, 113, 177, 0.2);
17
+
18
+ /* Grey colors */
19
+ --nfd-ai-chat-color-grey-dark: #666;
20
+ --nfd-ai-chat-color-grey-medium: #999;
21
+ --nfd-ai-chat-color-grey-light: #ddd;
22
+ --nfd-ai-chat-color-grey-placeholder: #9e9e9e;
23
+
24
+ /* Error/Danger colors */
25
+ --nfd-ai-chat-color-error: #d63638;
26
+ --nfd-ai-chat-color-error-hover: #b32d2e;
27
+ --nfd-ai-chat-color-error-active: #8a2424;
28
+ --nfd-ai-chat-color-error-alpha-10: rgba(214, 54, 56, 0.1);
29
+ --nfd-ai-chat-color-error-alpha-15: rgba(214, 54, 56, 0.15);
30
+
31
+ /* Success colors */
32
+ --nfd-ai-chat-color-success: #00a32a;
33
+
34
+ /* UI colors */
35
+ --nfd-ai-chat-color-border: #e0e0e0;
36
+ --nfd-ai-chat-color-background: #f5f5f5;
37
+ --nfd-ai-chat-color-text: #1e1e1e;
38
+ --nfd-ai-chat-color-text-secondary: #424242;
39
+ --nfd-ai-chat-color-white: #fff;
40
+
41
+ /* Typography */
42
+ --nfd-ai-chat-font-size-base: 14px;
43
+ --nfd-ai-chat-font-size-xl: 22px;
44
+ --nfd-ai-chat-font-size-lg: 20px;
45
+ --nfd-ai-chat-font-size-md: 16px;
46
+ --nfd-ai-chat-font-size-sm: 12px;
47
+ --nfd-ai-chat-font-size-xs: 10px;
48
+
49
+ /* Spacing */
50
+ --nfd-ai-chat-spacing-xs: 4px;
51
+ --nfd-ai-chat-spacing-sm: 8px;
52
+ --nfd-ai-chat-spacing-md: 16px;
53
+ --nfd-ai-chat-spacing-lg: 24px;
54
+ --nfd-ai-chat-spacing-xl: 32px;
55
+
56
+ /* Border radius */
57
+ --nfd-ai-chat-radius-sm: 4px;
58
+ --nfd-ai-chat-radius-md: 8px;
59
+ --nfd-ai-chat-radius-lg: 12px;
60
+ --nfd-ai-chat-radius-xl: 16px;
61
+ --nfd-ai-chat-radius-full: 9999px;
62
+
63
+ /* Shadows */
64
+ --nfd-ai-chat-shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
65
+ --nfd-ai-chat-shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
66
+ --nfd-ai-chat-shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
67
+ }
68
+
69
+ /* -------------------------------------------------------------------------- */
70
+
71
+ /* Brand-Specific Variables */
72
+
73
+ /* -------------------------------------------------------------------------- */
74
+
75
+ /* Bluehost Brand */
76
+ .nfd-brand-bluehost,
77
+ [data-brand="bluehost"] {
78
+
79
+ /* Primary Bluehost Blue - #0075FA */
80
+ --nfd-ai-chat-color-primary: #0075fa;
81
+ --nfd-ai-chat-color-primary-hover: #0066e0;
82
+ --nfd-ai-chat-color-primary-active: #0055c2;
83
+ --nfd-ai-chat-color-primary-light-8: rgba(0, 117, 250, 0.08);
84
+ --nfd-ai-chat-color-primary-light-12: rgba(0, 117, 250, 0.12);
85
+ --nfd-ai-chat-color-primary-light-16: rgba(0, 117, 250, 0.16);
86
+ --nfd-ai-chat-color-primary-light-20: rgba(0, 117, 250, 0.2);
87
+
88
+ /* Enhanced UI colors for professional look */
89
+ --nfd-ai-chat-color-background: #f8f9fa;
90
+ --nfd-ai-chat-color-border: #e5e7eb;
91
+ --nfd-ai-chat-color-text: #111827;
92
+ --nfd-ai-chat-color-text-secondary: #6b7280;
93
+ }
94
+
95
+ /* Add more brands here as needed */
96
+
97
+ /* Example:
98
+ .nfd-brand-hostgator,
99
+ [data-brand="hostgator"] {
100
+ --nfd-ai-chat-color-primary: #FF6600;
101
+ ...
102
+ }
103
+ */
@@ -0,0 +1,81 @@
1
+ .nfd-ai-chat-welcome {
2
+ display: flex;
3
+ flex-direction: column;
4
+ align-items: center;
5
+ justify-content: center;
6
+ text-align: center;
7
+ padding: var(--nfd-ai-chat-spacing-xl) var(--nfd-ai-chat-spacing-md);
8
+ flex: 1;
9
+ background: var(--nfd-ai-chat-color-white);
10
+ }
11
+
12
+ .nfd-ai-chat-welcome__content {
13
+ display: flex;
14
+ flex-direction: column;
15
+ align-items: center;
16
+ gap: 16px;
17
+ }
18
+
19
+ .nfd-ai-chat-welcome__avatar {
20
+ display: flex;
21
+ align-items: center;
22
+ justify-content: center;
23
+ margin-bottom: 24px;
24
+ }
25
+
26
+ /* Purplish gradient logo on welcome — higher specificity to override brand */
27
+ .nfd-ai-chat-welcome .nfd-ai-chat-welcome__avatar .nfd-ai-chat-avatar {
28
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
29
+ color: #fff;
30
+ box-shadow: none;
31
+ }
32
+
33
+ .nfd-ai-chat-welcome__message {
34
+ display: flex;
35
+ flex-direction: column;
36
+ gap: 4px;
37
+ }
38
+
39
+ .nfd-ai-chat-welcome__title {
40
+ font-size: var(--nfd-ai-chat-font-size-lg);
41
+ font-weight: 600;
42
+ color: var(--nfd-ai-chat-color-text);
43
+ }
44
+
45
+ .nfd-ai-chat-welcome__subtitle {
46
+ font-size: var(--nfd-ai-chat-font-size-base);
47
+ color: var(--nfd-ai-chat-color-text-secondary);
48
+ line-height: 1.5;
49
+ }
50
+
51
+ .nfd-ai-chat-welcome__cursor {
52
+ display: inline-block;
53
+ width: 2px;
54
+ height: 1em;
55
+ margin-left: 2px;
56
+ vertical-align: text-bottom;
57
+ background: currentcolor;
58
+ animation: nfd-ai-chat-cursor-blink 0.8s step-end infinite;
59
+ }
60
+
61
+ @keyframes nfd-ai-chat-cursor-blink {
62
+
63
+ 0%,
64
+ 50% {
65
+ opacity: 1;
66
+ }
67
+
68
+ 51%,
69
+ 100% {
70
+ opacity: 0;
71
+ }
72
+ }
73
+
74
+ .nfd-ai-chat-suggestions {
75
+ display: flex;
76
+ flex-wrap: wrap;
77
+ justify-content: center;
78
+ gap: var(--nfd-ai-chat-spacing-sm);
79
+ margin-top: var(--nfd-ai-chat-spacing-lg);
80
+ max-width: 400px;
81
+ }
@@ -0,0 +1,10 @@
1
+ /* AI Chat Core Styles */
2
+ @use "vars";
3
+ @use "mixins";
4
+ @use "messages";
5
+ @use "input";
6
+ @use "welcome";
7
+ @use "ui";
8
+ @use "typing-indicator";
9
+ @use "branding";
10
+ @use "history";
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Unescape AI response text so display shows normal quotes instead of \"
3
+ * Handles JSON-style escaped quotes that can appear in model/tool output.
4
+ *
5
+ * @param {string} text - Raw message text that may contain \" or \'
6
+ * @return {string} Text with escaped quotes replaced for display
7
+ */
8
+ export const unescapeAiResponse = (text) => {
9
+ if (!text || typeof text !== "string") {
10
+ return text;
11
+ }
12
+ return text.replace(/\\"/g, '"').replace(/\\'/g, "'");
13
+ };
14
+
15
+ /**
16
+ * Simple hash function to create a unique identifier from a string
17
+ * Uses a variation of the djb2 hash algorithm
18
+ *
19
+ * @param {string} str - The string to hash
20
+ * @return {string} A hexadecimal hash string
21
+ */
22
+ export const simpleHash = (str) => {
23
+ // Handle null, undefined, or empty strings
24
+ if (!str || typeof str !== "string") {
25
+ return "0";
26
+ }
27
+
28
+ let hash = 5381;
29
+ for (let i = 0; i < str.length; i++) {
30
+ // eslint-disable-next-line no-bitwise
31
+ hash = (hash << 5) + hash + str.charCodeAt(i); // hash * 33 + c
32
+ // eslint-disable-next-line no-bitwise
33
+ hash = hash | 0; // Convert to 32-bit integer
34
+ }
35
+ // Convert to unsigned and then to hex
36
+ // eslint-disable-next-line no-bitwise
37
+ return (hash >>> 0).toString(16);
38
+ };
39
+
40
+ /**
41
+ * Generate a unique session ID
42
+ *
43
+ * @return {string} New session ID
44
+ */
45
+ export const generateSessionId = () => {
46
+ return crypto.randomUUID
47
+ ? crypto.randomUUID()
48
+ : `${Date.now()}-${Math.random().toString(36).substring(2, 15)}`;
49
+ };
50
+
51
+ /**
52
+ * Debounce function
53
+ *
54
+ * @param {Function} func - Function to debounce
55
+ * @param {number} wait - Wait time in milliseconds
56
+ * @return {Function} Debounced function
57
+ */
58
+ export const debounce = (func, wait) => {
59
+ let timeout;
60
+ return function executedFunction(...args) {
61
+ const later = () => {
62
+ clearTimeout(timeout);
63
+ func(...args);
64
+ };
65
+ clearTimeout(timeout);
66
+ timeout = setTimeout(later, wait);
67
+ };
68
+ };
69
+
70
+ export default {
71
+ unescapeAiResponse,
72
+ simpleHash,
73
+ generateSessionId,
74
+ debounce,
75
+ };