@stack-spot/ai-chat-widget 1.7.2 → 1.8.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/app-metadata.json +2 -2
- package/dist/views/MessageInput/chat-entry-history.d.ts +11 -0
- package/dist/views/MessageInput/chat-entry-history.d.ts.map +1 -0
- package/dist/views/MessageInput/chat-entry-history.js +78 -0
- package/dist/views/MessageInput/chat-entry-history.js.map +1 -0
- package/dist/views/MessageInput/index.d.ts.map +1 -1
- package/dist/views/MessageInput/index.js +5 -2
- package/dist/views/MessageInput/index.js.map +1 -1
- package/package.json +1 -1
- package/src/app-metadata.json +2 -2
- package/src/views/MessageInput/chat-entry-history.ts +97 -0
- package/src/views/MessageInput/index.tsx +7 -1
package/dist/app-metadata.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stack-spot/ai-chat-widget",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"date": "Thu
|
|
3
|
+
"version": "1.8.0",
|
|
4
|
+
"date": "Thu Mar 13 2025 11:59:45 GMT-0300 (Brasilia Standard Time)",
|
|
5
5
|
"dependencies": [
|
|
6
6
|
{
|
|
7
7
|
"name": "@stack-spot/app-metadata",
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hook to manage keyboard shortcuts for the user's input history.
|
|
3
|
+
*
|
|
4
|
+
* This hook allows the user to navigate through the history of messages sent in a chat
|
|
5
|
+
* using the up arrow (`ArrowUp`) and down arrow (`ArrowDown`) keys.
|
|
6
|
+
*/
|
|
7
|
+
export declare const useUserEntryHistoryShortcut: () => {
|
|
8
|
+
handleKeyDown: (event: React.KeyboardEvent<HTMLTextAreaElement>) => void;
|
|
9
|
+
handleKeyUp: (event: React.KeyboardEvent<HTMLTextAreaElement>) => void;
|
|
10
|
+
};
|
|
11
|
+
//# sourceMappingURL=chat-entry-history.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat-entry-history.d.ts","sourceRoot":"","sources":["../../../src/views/MessageInput/chat-entry-history.ts"],"names":[],"mappings":"AAwCA;;;;;GAKG;AACH,eAAO,MAAM,2BAA2B;2BAiCI,KAAK,CAAC,aAAa,CAAC,mBAAmB,CAAC;yBAU1C,KAAK,CAAC,aAAa,CAAC,mBAAmB,CAAC;CAOjF,CAAA"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef } from 'react';
|
|
2
|
+
import { useCurrentChat } from '../../context/hooks.js';
|
|
3
|
+
const DEFAULT_POINTER = -1;
|
|
4
|
+
const useChatEntryHistory = () => {
|
|
5
|
+
const chat = useCurrentChat();
|
|
6
|
+
const historyPointer = useRef(DEFAULT_POINTER);
|
|
7
|
+
const messages = chat.getMessages();
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
historyPointer.current = chat.getMessages().length;
|
|
10
|
+
}, [chat.id, messages]);
|
|
11
|
+
const navigateEntry = useCallback((startIndex, direction, inputValue) => {
|
|
12
|
+
const chatEntries = chat.getMessages() || [];
|
|
13
|
+
let pointer = startIndex + direction;
|
|
14
|
+
while (pointer > DEFAULT_POINTER && pointer < chatEntries.length && chatEntries[pointer]?.getValue().agentType !== 'user') {
|
|
15
|
+
pointer += direction;
|
|
16
|
+
}
|
|
17
|
+
if (pointer >= chatEntries.length) {
|
|
18
|
+
return inputValue;
|
|
19
|
+
}
|
|
20
|
+
if (pointer <= DEFAULT_POINTER) {
|
|
21
|
+
return chatEntries[0]?.getValue().content ?? inputValue;
|
|
22
|
+
}
|
|
23
|
+
historyPointer.current = pointer;
|
|
24
|
+
return chatEntries[pointer]?.getValue().content ?? inputValue;
|
|
25
|
+
}, [chat]);
|
|
26
|
+
const getPreviousUserEntry = useCallback((inputValue) => navigateEntry(historyPointer.current, -1, inputValue), [navigateEntry]);
|
|
27
|
+
const getNextUserEntry = useCallback((inputValue) => navigateEntry(historyPointer.current, +1, inputValue), [navigateEntry]);
|
|
28
|
+
return { getNextUserEntry, getPreviousUserEntry };
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Hook to manage keyboard shortcuts for the user's input history.
|
|
32
|
+
*
|
|
33
|
+
* This hook allows the user to navigate through the history of messages sent in a chat
|
|
34
|
+
* using the up arrow (`ArrowUp`) and down arrow (`ArrowDown`) keys.
|
|
35
|
+
*/
|
|
36
|
+
export const useUserEntryHistoryShortcut = () => {
|
|
37
|
+
const { getNextUserEntry, getPreviousUserEntry } = useChatEntryHistory();
|
|
38
|
+
const chat = useCurrentChat();
|
|
39
|
+
const userEntryRef = useRef(chat.get('nextMessage') ?? '');
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
userEntryRef.current = chat.get('nextMessage') ?? '';
|
|
42
|
+
chat.onChange('nextMessage', (value) => {
|
|
43
|
+
if (value === '') {
|
|
44
|
+
userEntryRef.current = '';
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
}, [chat.id]);
|
|
48
|
+
const setNextMessageFromHistory = useCallback((event, getEntryHistory) => {
|
|
49
|
+
const textarea = event.target;
|
|
50
|
+
if (textarea.selectionStart !== 0)
|
|
51
|
+
return;
|
|
52
|
+
const entryHistory = getEntryHistory(userEntryRef.current);
|
|
53
|
+
if (entryHistory === chat.get('nextMessage'))
|
|
54
|
+
return;
|
|
55
|
+
chat.set('nextMessage', entryHistory);
|
|
56
|
+
requestAnimationFrame(() => {
|
|
57
|
+
textarea.selectionStart = 0;
|
|
58
|
+
textarea.selectionEnd = 0;
|
|
59
|
+
event.preventDefault();
|
|
60
|
+
event.stopPropagation();
|
|
61
|
+
});
|
|
62
|
+
}, [chat]);
|
|
63
|
+
const handleKeyDown = useCallback((event) => {
|
|
64
|
+
if (event.key === 'ArrowUp') {
|
|
65
|
+
setNextMessageFromHistory(event, getPreviousUserEntry);
|
|
66
|
+
}
|
|
67
|
+
if (event.key === 'ArrowDown') {
|
|
68
|
+
setNextMessageFromHistory(event, getNextUserEntry);
|
|
69
|
+
}
|
|
70
|
+
}, [getPreviousUserEntry, getNextUserEntry, setNextMessageFromHistory]);
|
|
71
|
+
const handleKeyUp = useCallback((event) => {
|
|
72
|
+
if (!event.key.startsWith('Arrow')) {
|
|
73
|
+
userEntryRef.current = chat.get('nextMessage') ?? '';
|
|
74
|
+
}
|
|
75
|
+
}, [chat]);
|
|
76
|
+
return { handleKeyDown, handleKeyUp };
|
|
77
|
+
};
|
|
78
|
+
//# sourceMappingURL=chat-entry-history.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat-entry-history.js","sourceRoot":"","sources":["../../../src/views/MessageInput/chat-entry-history.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/views/MessageInput/index.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/views/MessageInput/index.tsx"],"names":[],"mappings":"AAkBA;;;;GAIG;AACH,eAAO,MAAM,YAAY,+CA0ExB,CAAA"}
|
|
@@ -12,6 +12,7 @@ import { ButtonAgent } from './ButtonAgent.js';
|
|
|
12
12
|
import { ButtonGroup } from './ButtonGroup.js';
|
|
13
13
|
import { useMessageInputDictionary } from './dictionary.js';
|
|
14
14
|
import { InfoBar } from './InfoBar.js';
|
|
15
|
+
import { useUserEntryHistoryShortcut } from './chat-entry-history.js';
|
|
15
16
|
import { QuickCommandSelector } from './QuickCommandSelector.js';
|
|
16
17
|
import { MAX_INPUT_HEIGHT, MessageInputBox, MIN_INPUT_HEIGHT } from './styled.js';
|
|
17
18
|
/**
|
|
@@ -30,6 +31,7 @@ export const MessageInput = () => {
|
|
|
30
31
|
const isMinimized = useWidgetState('isMinimized');
|
|
31
32
|
const textAreaRef = useRef(null);
|
|
32
33
|
const agentLabel = useCurrentChatState('agent')?.label ?? 'Stackspot AI';
|
|
34
|
+
const { handleKeyDown, handleKeyUp } = useUserEntryHistoryShortcut();
|
|
33
35
|
const onSend = useCallback(async () => {
|
|
34
36
|
const message = chat.get('nextMessage');
|
|
35
37
|
if (!message)
|
|
@@ -46,12 +48,13 @@ export const MessageInput = () => {
|
|
|
46
48
|
event.preventDefault();
|
|
47
49
|
onSend();
|
|
48
50
|
}
|
|
49
|
-
|
|
51
|
+
handleKeyDown(event);
|
|
52
|
+
}, [onSend, handleKeyDown]);
|
|
50
53
|
useEffect(() => {
|
|
51
54
|
if (!isLoading)
|
|
52
55
|
textAreaRef.current?.focus();
|
|
53
56
|
}, [isLoading]);
|
|
54
|
-
return (_jsxs(MessageInputBox, { "aria-busy": isLoading, className: "message-input", children: [_jsx(ProgressBar, { visible: isLoading, shimmer: true }), _jsx(InfoBar, {}), _jsxs("div", { className: "wrapper-action", children: [_jsx(QuickCommandSelector, { inputRef: textAreaRef }), _jsx(AgentSelector, { inputRef: textAreaRef }), _jsxs("div", { className: listToClass(['action-box', focused && 'focused', isLoading && 'disabled']), children: [_jsx(ButtonAgent, {}), _jsx(AdaptiveTextArea, { ref: textAreaRef, disabled: isLoading, placeholder: interpolate(t.placeholder, agentLabel), onChange: e => chat.set('nextMessage', e.target.value), value: value, onFocus: () => setFocused(true), onBlur: () => setFocused(false), onKeyDown: onKeyDown, onIncreaseSize: () => setExpanded(false), onResetSize: () => !expansionLocked.current && setExpanded(true), maxHeight: isMinimized ? MIN_INPUT_HEIGHT : MAX_INPUT_HEIGHT }), _jsx(ButtonGroup, { onSend: onSend, onCancel: () => chat.abort(), expanded: expanded, isLoading: isLoading, setExpanded: (value) => {
|
|
57
|
+
return (_jsxs(MessageInputBox, { "aria-busy": isLoading, className: "message-input", children: [_jsx(ProgressBar, { visible: isLoading, shimmer: true }), _jsx(InfoBar, {}), _jsxs("div", { className: "wrapper-action", children: [_jsx(QuickCommandSelector, { inputRef: textAreaRef }), _jsx(AgentSelector, { inputRef: textAreaRef }), _jsxs("div", { className: listToClass(['action-box', focused && 'focused', isLoading && 'disabled']), children: [_jsx(ButtonAgent, {}), _jsx(AdaptiveTextArea, { ref: textAreaRef, disabled: isLoading, placeholder: interpolate(t.placeholder, agentLabel), onChange: e => chat.set('nextMessage', e.target.value), value: value, onFocus: () => setFocused(true), onBlur: () => setFocused(false), onKeyDown: onKeyDown, onKeyUp: handleKeyUp, onIncreaseSize: () => setExpanded(false), onResetSize: () => !expansionLocked.current && setExpanded(true), maxHeight: isMinimized ? MIN_INPUT_HEIGHT : MAX_INPUT_HEIGHT }), _jsx(ButtonGroup, { onSend: onSend, onCancel: () => chat.abort(), expanded: expanded, isLoading: isLoading, setExpanded: (value) => {
|
|
55
58
|
setExpanded(value);
|
|
56
59
|
expansionLocked.current = expanded;
|
|
57
60
|
} })] })] })] }));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/views/MessageInput/index.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAA;AAC1D,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAChE,OAAO,EAAE,gBAAgB,EAAE,MAAM,mCAAmC,CAAA;AACpE,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAA;AAC1D,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AACzF,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAC/C,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAA;AACjD,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAC3C,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAC3C,OAAO,EAAE,yBAAyB,EAAE,MAAM,cAAc,CAAA;AACxD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/views/MessageInput/index.tsx"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/app-metadata.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stack-spot/ai-chat-widget",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"date": "Thu
|
|
3
|
+
"version": "1.8.0",
|
|
4
|
+
"date": "Thu Mar 13 2025 11:59:45 GMT-0300 (Brasilia Standard Time)",
|
|
5
5
|
"dependencies": [
|
|
6
6
|
{
|
|
7
7
|
"name": "@stack-spot/app-metadata",
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef } from 'react'
|
|
2
|
+
import { useCurrentChat } from '../../context/hooks'
|
|
3
|
+
|
|
4
|
+
const DEFAULT_POINTER = -1
|
|
5
|
+
|
|
6
|
+
const useChatEntryHistory = () => {
|
|
7
|
+
const chat = useCurrentChat()
|
|
8
|
+
const historyPointer = useRef(DEFAULT_POINTER)
|
|
9
|
+
const messages = chat.getMessages()
|
|
10
|
+
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
historyPointer.current = chat.getMessages().length
|
|
13
|
+
}, [chat.id, messages])
|
|
14
|
+
|
|
15
|
+
const navigateEntry = useCallback((startIndex: number, direction: 1 | -1, inputValue: string) => {
|
|
16
|
+
const chatEntries = chat.getMessages() || []
|
|
17
|
+
let pointer = startIndex + direction
|
|
18
|
+
|
|
19
|
+
while (pointer > DEFAULT_POINTER && pointer < chatEntries.length && chatEntries[pointer]?.getValue().agentType !== 'user') {
|
|
20
|
+
pointer += direction
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (pointer >= chatEntries.length) {
|
|
24
|
+
return inputValue
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (pointer <= DEFAULT_POINTER) {
|
|
28
|
+
return chatEntries[0]?.getValue().content ?? inputValue
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
historyPointer.current = pointer
|
|
32
|
+
return chatEntries[pointer]?.getValue().content ?? inputValue
|
|
33
|
+
}, [chat])
|
|
34
|
+
|
|
35
|
+
const getPreviousUserEntry = useCallback((inputValue: string) => navigateEntry(historyPointer.current, -1, inputValue), [navigateEntry])
|
|
36
|
+
const getNextUserEntry = useCallback((inputValue: string) => navigateEntry(historyPointer.current, +1, inputValue), [navigateEntry])
|
|
37
|
+
|
|
38
|
+
return { getNextUserEntry, getPreviousUserEntry }
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Hook to manage keyboard shortcuts for the user's input history.
|
|
43
|
+
*
|
|
44
|
+
* This hook allows the user to navigate through the history of messages sent in a chat
|
|
45
|
+
* using the up arrow (`ArrowUp`) and down arrow (`ArrowDown`) keys.
|
|
46
|
+
*/
|
|
47
|
+
export const useUserEntryHistoryShortcut = () => {
|
|
48
|
+
const { getNextUserEntry, getPreviousUserEntry } = useChatEntryHistory()
|
|
49
|
+
const chat = useCurrentChat()
|
|
50
|
+
const userEntryRef = useRef(chat.get('nextMessage') ?? '')
|
|
51
|
+
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
userEntryRef.current = chat.get('nextMessage') ?? ''
|
|
54
|
+
chat.onChange('nextMessage', (value) => {
|
|
55
|
+
if (value === '') {
|
|
56
|
+
userEntryRef.current = ''
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
}, [chat.id])
|
|
60
|
+
|
|
61
|
+
const setNextMessageFromHistory = useCallback(
|
|
62
|
+
(event: React.KeyboardEvent<HTMLTextAreaElement>, getEntryHistory: typeof getNextUserEntry) => {
|
|
63
|
+
const textarea = event.target as HTMLTextAreaElement
|
|
64
|
+
if (textarea.selectionStart !== 0) return
|
|
65
|
+
|
|
66
|
+
const entryHistory = getEntryHistory(userEntryRef.current)
|
|
67
|
+
if (entryHistory === chat.get('nextMessage')) return
|
|
68
|
+
|
|
69
|
+
chat.set('nextMessage', entryHistory)
|
|
70
|
+
|
|
71
|
+
requestAnimationFrame(() => {
|
|
72
|
+
textarea.selectionStart = 0
|
|
73
|
+
textarea.selectionEnd = 0
|
|
74
|
+
event.preventDefault()
|
|
75
|
+
event.stopPropagation()
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
}, [chat])
|
|
79
|
+
|
|
80
|
+
const handleKeyDown = useCallback((event: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
|
81
|
+
if (event.key === 'ArrowUp') {
|
|
82
|
+
setNextMessageFromHistory(event, getPreviousUserEntry)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (event.key === 'ArrowDown') {
|
|
86
|
+
setNextMessageFromHistory(event, getNextUserEntry)
|
|
87
|
+
}
|
|
88
|
+
}, [getPreviousUserEntry, getNextUserEntry, setNextMessageFromHistory])
|
|
89
|
+
|
|
90
|
+
const handleKeyUp = useCallback((event: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
|
91
|
+
if (!event.key.startsWith('Arrow')) {
|
|
92
|
+
userEntryRef.current = chat.get('nextMessage') ?? ''
|
|
93
|
+
}
|
|
94
|
+
}, [chat])
|
|
95
|
+
|
|
96
|
+
return { handleKeyDown, handleKeyUp }
|
|
97
|
+
}
|
|
@@ -11,6 +11,8 @@ import { ButtonAgent } from './ButtonAgent'
|
|
|
11
11
|
import { ButtonGroup } from './ButtonGroup'
|
|
12
12
|
import { useMessageInputDictionary } from './dictionary'
|
|
13
13
|
import { InfoBar } from './InfoBar'
|
|
14
|
+
|
|
15
|
+
import { useUserEntryHistoryShortcut } from './chat-entry-history'
|
|
14
16
|
import { QuickCommandSelector } from './QuickCommandSelector'
|
|
15
17
|
import { MAX_INPUT_HEIGHT, MessageInputBox, MIN_INPUT_HEIGHT } from './styled'
|
|
16
18
|
|
|
@@ -30,6 +32,7 @@ export const MessageInput = () => {
|
|
|
30
32
|
const isMinimized = useWidgetState('isMinimized')
|
|
31
33
|
const textAreaRef = useRef<HTMLTextAreaElement>(null)
|
|
32
34
|
const agentLabel = useCurrentChatState('agent')?.label ?? 'Stackspot AI'
|
|
35
|
+
const { handleKeyDown, handleKeyUp } = useUserEntryHistoryShortcut()
|
|
33
36
|
|
|
34
37
|
const onSend = useCallback(async () => {
|
|
35
38
|
const message = chat.get('nextMessage')
|
|
@@ -47,7 +50,9 @@ export const MessageInput = () => {
|
|
|
47
50
|
event.preventDefault()
|
|
48
51
|
onSend()
|
|
49
52
|
}
|
|
50
|
-
|
|
53
|
+
|
|
54
|
+
handleKeyDown(event)
|
|
55
|
+
}, [onSend, handleKeyDown])
|
|
51
56
|
|
|
52
57
|
useEffect(() => {
|
|
53
58
|
if (!isLoading) textAreaRef.current?.focus()
|
|
@@ -71,6 +76,7 @@ export const MessageInput = () => {
|
|
|
71
76
|
onFocus={() => setFocused(true)}
|
|
72
77
|
onBlur={() => setFocused(false)}
|
|
73
78
|
onKeyDown={onKeyDown}
|
|
79
|
+
onKeyUp={handleKeyUp}
|
|
74
80
|
onIncreaseSize={() => setExpanded(false)}
|
|
75
81
|
onResetSize={() => !expansionLocked.current && setExpanded(true)}
|
|
76
82
|
maxHeight={isMinimized ? MIN_INPUT_HEIGHT : MAX_INPUT_HEIGHT}
|