quickblox-react-ui-kit 0.1.8 → 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.
Files changed (29) hide show
  1. package/dist/Presentation/Views/Base/BaseViewModel.d.ts +2 -1
  2. package/dist/Presentation/components/UI/Dialogs/MessagesView/AIWidgets/AIWidget.d.ts +8 -0
  3. package/dist/Presentation/components/UI/Dialogs/MessagesView/AIWidgets/ErrorMessageIcon.d.ts +11 -0
  4. package/dist/Presentation/components/UI/Dialogs/MessagesView/AIWidgets/UseDefaultAIAssistAnswerWidgetWithProxy.d.ts +9 -0
  5. package/dist/Presentation/components/UI/Dialogs/MessagesView/AIWidgets/UseDefaultTextInputWidget.d.ts +2 -0
  6. package/dist/Presentation/components/UI/Dialogs/MessagesView/AIWidgets/useDefaultVoiceInputWidget.d.ts +2 -0
  7. package/dist/Presentation/components/UI/Dialogs/MessagesView/ContextMenu.d.ts +10 -0
  8. package/dist/Presentation/components/UI/Dialogs/MessagesView/MessagesView.d.ts +4 -0
  9. package/dist/Presentation/components/layouts/Desktop/QuickBloxUIKitDesktopLayout.d.ts +9 -0
  10. package/dist/index-ui.js +47 -3
  11. package/dist/utils/utils.d.ts +3 -0
  12. package/package.json +1 -1
  13. package/src/App.tsx +0 -19
  14. package/src/Presentation/Views/Base/BaseViewModel.ts +11 -3
  15. package/src/Presentation/Views/Dialogs/Dialogs.tsx +0 -1
  16. package/src/Presentation/components/UI/Dialogs/MessagesView/AIWidgets/AIWidget.ts +13 -0
  17. package/src/Presentation/components/UI/Dialogs/MessagesView/AIWidgets/ErrorMessageIcon.tsx +98 -0
  18. package/src/Presentation/components/UI/Dialogs/MessagesView/AIWidgets/UseDefaultAIAssistAnswerWidgetWithProxy.tsx +136 -0
  19. package/src/Presentation/components/UI/Dialogs/MessagesView/{InputWidget → AIWidgets}/UseDefaultTextInputWidget.tsx +4 -4
  20. package/src/Presentation/components/UI/Dialogs/MessagesView/{InputWidget → AIWidgets}/useDefaultVoiceInputWidget.tsx +4 -4
  21. package/src/Presentation/components/UI/Dialogs/MessagesView/ContextMenu.tsx +96 -0
  22. package/src/Presentation/components/UI/Dialogs/MessagesView/MessagesView.tsx +117 -62
  23. package/src/Presentation/components/layouts/Desktop/QuickBloxUIKitDesktopLayout.tsx +68 -36
  24. package/src/QBconfig.ts +3 -4
  25. package/src/utils/utils.ts +39 -0
  26. package/dist/Presentation/components/UI/Dialogs/MessagesView/InputWidget/InputWidget.d.ts +0 -8
  27. package/dist/Presentation/components/UI/Dialogs/MessagesView/InputWidget/UseDefaultTextInputWidget.d.ts +0 -2
  28. package/dist/Presentation/components/UI/Dialogs/MessagesView/InputWidget/useDefaultVoiceInputWidget.d.ts +0 -2
  29. package/src/Presentation/components/UI/Dialogs/MessagesView/InputWidget/InputWidget.ts +0 -15
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "quickblox-react-ui-kit",
3
- "version": "0.1.8",
3
+ "version": "0.2.0",
4
4
  "main": "dist/index-ui.js",
5
5
  "license": "MIT",
6
6
  "dependencies": {
package/src/App.tsx CHANGED
@@ -194,18 +194,6 @@ function App() {
194
194
  prepareSDK(currentUser).catch();
195
195
  }, []);
196
196
 
197
- // const { apiKey } = QBConfig.configAIApi.AIAnswerAssistWidgetConfig;
198
- // // eslint-disable-next-line @typescript-eslint/no-unsafe-call
199
- // const openAIConfiguration: Configuration = new Configuration({
200
- // apiKey,
201
- // });
202
- //
203
- // // eslint-disable-next-line @typescript-eslint/no-unsafe-call
204
- // const openAIApi: OpenAIApi = new OpenAIApi(openAIConfiguration);
205
- // const defaultIncomingMessageWidget = UseDefaultIncomingMessageWidget({
206
- // openAIApi,
207
- // });
208
-
209
197
  // todo: uncomment authSecret
210
198
  return (
211
199
  <QuickBloxUIKitProvider
@@ -233,13 +221,6 @@ function App() {
233
221
  <Route
234
222
  path="/desktop-test-mock"
235
223
  element={
236
- // <QuickBloxUIKitDesktopLayout
237
- // theme={new DefaultTheme()}
238
- // IncomingMessageWidgetToRightPlaceHolder={
239
- // defaultIncomingMessageWidget
240
- // }
241
- // />
242
-
243
224
  <QuickBloxUIKitDesktopLayout theme={new DefaultTheme()} />
244
225
  }
245
226
  />
@@ -69,7 +69,15 @@ export type FunctionTypeDialogEntityToBoolean = (
69
69
  ) => Promise<boolean>;
70
70
  export type FunctionTypeFileToFileEntity = (file: File) => Promise<FileEntity>;
71
71
  export type FunctionTypeJSXElement = () => JSX.Element;
72
- export type FunctionTypeChatMessagesToVoid = (
73
- lastMessage: string,
74
- messages: IChatMessage,
72
+ // export type FunctionTypeChatMessagesToVoid = (
73
+ // lastMessage: string,
74
+ // messages: IChatMessage,
75
+ // ) => void;
76
+ export type FunctionTypeFileWithContextToToVoid = (
77
+ file: File,
78
+ context: IChatMessage[],
79
+ ) => void;
80
+ export type FunctionTypeStringWithContextToVoid = (
81
+ value: string,
82
+ context: IChatMessage[],
75
83
  ) => void;
@@ -314,7 +314,6 @@ const DialogsComponent: React.FC<DialogsProps> = ({
314
314
  <ColumnContainer>
315
315
  {useUpContent && upHeaderContent}
316
316
  {useHeader && HeaderContent}
317
- {/* {showSearchDialogs ? renderSearchDialogs() : null} */}
318
317
  {useSubContent && subHeaderContent}
319
318
  {dialogsViewModel?.loading && (
320
319
  // <div style={{ maxHeight: '44px', minHeight: '44px', height: '44px' }}>
@@ -0,0 +1,13 @@
1
+ import {
2
+ FunctionTypeFileWithContextToToVoid,
3
+ FunctionTypeJSXElement,
4
+ FunctionTypeStringWithContextToVoid,
5
+ } from '../../../../../Views/Base/BaseViewModel';
6
+
7
+ export interface AIWidget {
8
+ renderWidget: FunctionTypeJSXElement;
9
+ textToWidget: FunctionTypeStringWithContextToVoid;
10
+ fileToWidget: FunctionTypeFileWithContextToToVoid;
11
+ textToContent: string | undefined;
12
+ fileToContent: File | undefined;
13
+ }
@@ -0,0 +1,98 @@
1
+ import React, { useState, CSSProperties } from 'react';
2
+
3
+ type ErrorDescription = {
4
+ title: string;
5
+ action: () => void;
6
+ };
7
+
8
+ type ErrorMessageIconProps = {
9
+ errorMessageText: string;
10
+ errorsDescriptions?: ErrorDescription[];
11
+ };
12
+
13
+ const errorMessageIconStyles: { [key: string]: CSSProperties } = {
14
+ errorIcon: {
15
+ display: 'inline-block',
16
+ position: 'relative',
17
+ width: '21px', // Уменьшен размер круга
18
+ height: '21px', // Уменьшен размер круга
19
+ cursor: 'pointer',
20
+ },
21
+ circle: {
22
+ width: '21px', // Уменьшен размер круга
23
+ height: '21px', // Уменьшен размер круга
24
+ backgroundColor: 'red',
25
+ borderRadius: '50%',
26
+ display: 'flex',
27
+ justifyContent: 'center',
28
+ alignItems: 'center',
29
+ },
30
+ exclamationMark: {
31
+ color: 'white',
32
+ fontSize: '12px', // Уменьшен размер восклицательного знака
33
+ },
34
+ errorMessage: {
35
+ position: 'absolute',
36
+ bottom: '100%', // Изменено выравнивание, чтобы текст был выше
37
+ left: '50%',
38
+ transform: 'translateX(-50%)',
39
+ backgroundColor: 'lightgray',
40
+ border: '1px solid gray',
41
+ padding: '6px',
42
+ borderRadius: '8px',
43
+ whiteSpace: 'nowrap',
44
+ display: 'inline-block',
45
+ zIndex: 1, // Установлен zIndex, чтобы текст был выше других элементов
46
+ },
47
+ };
48
+
49
+ function ErrorMessageIcon({
50
+ errorMessageText,
51
+ errorsDescriptions,
52
+ }: ErrorMessageIconProps) {
53
+ const [isHovered, setIsHovered] = useState(false);
54
+
55
+ const handleMouseEnter = () => {
56
+ setIsHovered(true);
57
+ };
58
+
59
+ const handleMouseLeave = () => {
60
+ setIsHovered(false);
61
+ };
62
+
63
+ return (
64
+ <div
65
+ style={{
66
+ ...errorMessageIconStyles.errorIcon,
67
+ ...(isHovered ? { backgroundColor: 'yellow' } : {}),
68
+ }}
69
+ onMouseEnter={handleMouseEnter}
70
+ onMouseLeave={handleMouseLeave}
71
+ >
72
+ <div style={errorMessageIconStyles.circle}>
73
+ <span style={errorMessageIconStyles.exclamationMark}>!</span>
74
+ </div>
75
+ {isHovered && (
76
+ <div style={errorMessageIconStyles.errorMessage}>
77
+ {errorMessageText}
78
+ {errorsDescriptions?.map((item, index) => (
79
+ <div
80
+ key={index}
81
+ style={{
82
+ padding: '4px',
83
+ cursor: 'pointer',
84
+ }}
85
+ onClick={() => {
86
+ item.action();
87
+ }}
88
+ >
89
+ {item.title}
90
+ </div>
91
+ ))}
92
+ </div>
93
+ )}
94
+ </div>
95
+ );
96
+ }
97
+
98
+ export default ErrorMessageIcon;
@@ -0,0 +1,136 @@
1
+ import { useState } from 'react';
2
+ import { AIWidget } from './AIWidget';
3
+ import AIWidgetIcon from '../../../svgs/Icons/Media/AIWidget';
4
+ import { IChatMessage } from '../../../../../Views/Base/BaseViewModel';
5
+ import { stringifyError } from '../../../../../../utils/parse';
6
+ import ErrorMessageIcon from './ErrorMessageIcon';
7
+
8
+ interface MessageWidgetProps {
9
+ // https://api.openai.com/v1/chat/completions'
10
+ // api: 'v1/chat/completions',
11
+ // servername: 'https://myproxy.com',
12
+ // https://func270519800.azurewebsites.net/api/TranslateTextToEng
13
+ servername: string;
14
+ api: string;
15
+ port: string;
16
+ sessionToken: string;
17
+ }
18
+ export default function UseDefaultAIAssistAnswerWidgetWithProxy({
19
+ servername,
20
+ api,
21
+ port,
22
+ sessionToken,
23
+ }: MessageWidgetProps): AIWidget {
24
+ const [errorMessage, setErrorMessage] = useState<string>('');
25
+
26
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars,@typescript-eslint/no-empty-function
27
+ const fileToWidget = (file: File, context: IChatMessage[]): void => {};
28
+
29
+ const renderWidget = (): JSX.Element => {
30
+ if (errorMessage && errorMessage.length > 0) {
31
+ const errorsDescriptions:
32
+ | { title: string; action: () => void }[]
33
+ | undefined = [];
34
+
35
+ return (
36
+ <ErrorMessageIcon
37
+ errorMessageText={errorMessage}
38
+ errorsDescriptions={errorsDescriptions}
39
+ />
40
+ );
41
+ }
42
+
43
+ return <AIWidgetIcon applyZoom color="green" />;
44
+ };
45
+
46
+ // async function getData(
47
+ // textToSend: string,
48
+ // dialogMessages: ChatCompletionRequestMessage[],
49
+ // ): Promise<string> {
50
+ // //
51
+ // const apiEndpoint = 'https://api.openai.com/v1/chat/completions';
52
+ // const { apiKey } = QBConfig.configAIApi.AIAnswerAssistWidgetConfig; // Замените на ваш реальный ключ API
53
+ // const model = 'gpt-3.5-turbo';
54
+ // const requestOptions = {
55
+ // method: 'POST',
56
+ // headers: {
57
+ // 'Content-Type': 'application/json',
58
+ // Authorization: `Bearer ${apiKey}`,
59
+ // },
60
+ // body: JSON.stringify({
61
+ // messages: [...dialogMessages, { role: 'user', content: textToSend }],
62
+ // model,
63
+ // temperature: 0.5,
64
+ // }),
65
+ // };
66
+ //
67
+ async function getData(
68
+ textToSend: string,
69
+ dialogMessages: IChatMessage[],
70
+ ): Promise<string> {
71
+ let outputMessage = '';
72
+ const apiEndpoint = `${servername}${port}${api}`;
73
+ const apiKey = sessionToken; // Замените на ваш реальный ключ API
74
+ const model = 'gpt-3.5-turbo';
75
+ const prompt = `Respond as a knowledgeable customer support specialist with access to ChatGPT features, and provide a simple and informative response to the inquiry :"${textToSend}"`;
76
+
77
+ const requestOptions = {
78
+ method: 'POST',
79
+ headers: {
80
+ 'Content-Type': 'application/json',
81
+ Authorization: `Bearer ${apiKey}`,
82
+ },
83
+ body: JSON.stringify({
84
+ messages: [...dialogMessages, { role: 'user', content: prompt }],
85
+ model,
86
+ temperature: 0.5,
87
+ }),
88
+ };
89
+
90
+ //
91
+ try {
92
+ const response = await fetch(apiEndpoint, requestOptions);
93
+ const data = await response.json();
94
+
95
+ outputMessage = data.choices[0].message?.content || '';
96
+ } catch (err) {
97
+ outputMessage = stringifyError(err);
98
+ setErrorMessage(outputMessage);
99
+ }
100
+
101
+ return outputMessage;
102
+ }
103
+
104
+ const [textFromWidgetToContent, setTextFromWidgetToContent] = useState('');
105
+ // const textToWidget = (value: string, context: IChatMessage[]): void => {
106
+ // if (value && value.length > 0) {
107
+ // // eslint-disable-next-line promise/catch-or-return
108
+ // getOpenAIApiData(value, context as ChatCompletionRequestMessage[]).then(
109
+ // // eslint-disable-next-line promise/always-return
110
+ // (data) => {
111
+ // setTextFromWidgetToContent(data);
112
+ // },
113
+ // );
114
+ // }
115
+ // };
116
+
117
+ const textToWidget = (value: string, context: IChatMessage[]): void => {
118
+ if (value && value.length > 0) {
119
+ // eslint-disable-next-line promise/catch-or-return
120
+ getData(value, context).then(
121
+ // eslint-disable-next-line promise/always-return
122
+ (data) => {
123
+ setTextFromWidgetToContent(data);
124
+ },
125
+ );
126
+ }
127
+ };
128
+
129
+ return {
130
+ fileToContent: undefined,
131
+ textToContent: textFromWidgetToContent,
132
+ fileToWidget,
133
+ renderWidget,
134
+ textToWidget,
135
+ };
136
+ }
@@ -1,7 +1,7 @@
1
1
  import { useState } from 'react';
2
- import { InputWidget } from './InputWidget';
2
+ import { AIWidget } from './AIWidget';
3
3
 
4
- export default function useDefaultTextInputWidget(): InputWidget {
4
+ export default function useDefaultTextInputWidget(): AIWidget {
5
5
  // eslint-disable-next-line @typescript-eslint/no-unused-vars,@typescript-eslint/no-empty-function
6
6
  const fileToWidget = (file: File): void => {};
7
7
 
@@ -51,8 +51,8 @@ export default function useDefaultTextInputWidget(): InputWidget {
51
51
  };
52
52
 
53
53
  return {
54
- fileToInput: undefined,
55
- textToInput: textFromWidgetToInput,
54
+ fileToContent: undefined,
55
+ textToContent: textFromWidgetToInput,
56
56
  fileToWidget,
57
57
  renderWidget,
58
58
  textToWidget,
@@ -1,9 +1,9 @@
1
1
  import { useState } from 'react';
2
- import { InputWidget } from './InputWidget';
2
+ import { AIWidget } from './AIWidget';
3
3
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
4
4
  import VoiceIcon from '../../../svgs/Icons/Actions/Voice';
5
5
 
6
- export default function useDefaultVoiceInputWidget(): InputWidget {
6
+ export default function useDefaultVoiceInputWidget(): AIWidget {
7
7
  const renderWidget = (): JSX.Element => {
8
8
  // return <VoiceIcon width="21" height="18" applyZoom color="red" />;
9
9
  return (
@@ -77,8 +77,8 @@ export default function useDefaultVoiceInputWidget(): InputWidget {
77
77
  };
78
78
 
79
79
  return {
80
- fileToInput: audioFromWidgetToInput,
81
- textToInput: textFromWidgetToInput,
80
+ fileToContent: audioFromWidgetToInput,
81
+ textToContent: textFromWidgetToInput,
82
82
  fileToWidget,
83
83
  renderWidget,
84
84
  textToWidget,
@@ -0,0 +1,96 @@
1
+ import React, { useState, CSSProperties, useRef, useEffect } from 'react';
2
+ import EditDots from '../../svgs/Icons/Actions/EditDots';
3
+
4
+ type MenuItem = {
5
+ title: string;
6
+ action: () => void;
7
+ };
8
+
9
+ type ContextMenuProps = {
10
+ items?: MenuItem[];
11
+ };
12
+
13
+ const ContextMenuStyles: { [key: string]: CSSProperties } = {
14
+ contextMenuIcon: {
15
+ display: 'inline-block',
16
+ position: 'relative',
17
+ width: '42px',
18
+ height: '42px',
19
+ cursor: 'pointer',
20
+ },
21
+ contextMenuContent: {
22
+ position: 'absolute',
23
+ top: '0',
24
+ left: '50%',
25
+ transform: 'translateX(-50%)',
26
+ backgroundColor: 'white',
27
+ border: '1px solid gray',
28
+ borderRadius: '8px',
29
+ padding: '4px',
30
+ zIndex: 1,
31
+ width: 'max-content',
32
+ },
33
+ };
34
+
35
+ function ContextMenu({ items }: ContextMenuProps) {
36
+ const [menuVisible, setMenuVisible] = useState(false);
37
+ const contextMenuRef = useRef<HTMLDivElement | null>(null);
38
+
39
+ const handleClick = () => {
40
+ setMenuVisible(!menuVisible);
41
+ };
42
+
43
+ const handleMenuItemClick = (action: () => void) => {
44
+ action();
45
+ setMenuVisible(false); // Закрыть контекстное меню после клика на пункт меню
46
+ };
47
+
48
+ useEffect(() => {
49
+ function handleClickOutside(event: MouseEvent) {
50
+ if (
51
+ contextMenuRef.current &&
52
+ !contextMenuRef.current.contains(event.target as Node)
53
+ ) {
54
+ setMenuVisible(false);
55
+ }
56
+ }
57
+
58
+ document.addEventListener('mousedown', handleClickOutside);
59
+
60
+ return () => {
61
+ document.removeEventListener('mousedown', handleClickOutside);
62
+ };
63
+ }, []);
64
+
65
+ return (
66
+ <div
67
+ style={{
68
+ ...ContextMenuStyles.contextMenuIcon,
69
+ }}
70
+ >
71
+ <div onClick={handleClick}>
72
+ <EditDots />
73
+ </div>
74
+ {menuVisible && (
75
+ <div ref={contextMenuRef} style={ContextMenuStyles.contextMenuContent}>
76
+ {items?.map((item, index) => (
77
+ <div
78
+ key={index}
79
+ style={{
80
+ padding: '4px',
81
+ cursor: 'pointer',
82
+ }}
83
+ onClick={() => {
84
+ handleMenuItemClick(item.action); // Используем новую функцию обработчика
85
+ }}
86
+ >
87
+ {item.title}
88
+ </div>
89
+ ))}
90
+ </div>
91
+ )}
92
+ </div>
93
+ );
94
+ }
95
+
96
+ export default ContextMenu;