@gravity-ui/aikit 2.24.1-beta.883c85d04b93d8521b2733baa3e86da20249b4e7.0 → 2.25.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/build/cjs/components/molecules/PromptInputBody/PromptInputBody.d.ts +1 -0
- package/build/cjs/components/molecules/PromptInputBody/PromptInputBody.js +2 -8
- package/build/cjs/components/molecules/PromptInputBody/PromptInputBody.js.map +1 -1
- package/build/cjs/components/organisms/PromptInput/PromptInput.css +37 -0
- package/build/cjs/components/organisms/PromptInput/PromptInput.d.ts +13 -2
- package/build/cjs/components/organisms/PromptInput/PromptInput.js +64 -3
- package/build/cjs/components/organisms/PromptInput/PromptInput.js.map +1 -1
- package/build/cjs/components/organisms/PromptInput/PromptInputCommands.d.ts +9 -0
- package/build/cjs/components/organisms/PromptInput/PromptInputCommands.js +39 -0
- package/build/cjs/components/organisms/PromptInput/PromptInputCommands.js.map +1 -0
- package/build/cjs/components/organisms/PromptInput/PromptInputFull.d.ts +2 -0
- package/build/cjs/components/organisms/PromptInput/PromptInputFull.js +4 -4
- package/build/cjs/components/organisms/PromptInput/PromptInputFull.js.map +1 -1
- package/build/cjs/components/organisms/PromptInput/PromptInputSimple.d.ts +2 -0
- package/build/cjs/components/organisms/PromptInput/PromptInputSimple.js +3 -3
- package/build/cjs/components/organisms/PromptInput/PromptInputSimple.js.map +1 -1
- package/build/cjs/components/organisms/PromptInput/commandUtils.d.ts +13 -0
- package/build/cjs/components/organisms/PromptInput/commandUtils.js +27 -0
- package/build/cjs/components/organisms/PromptInput/commandUtils.js.map +1 -0
- package/build/cjs/components/organisms/PromptInput/i18n/en.json +8 -0
- package/build/cjs/components/organisms/PromptInput/i18n/index.d.ts +13 -0
- package/build/cjs/components/organisms/PromptInput/i18n/index.js +10 -0
- package/build/cjs/components/organisms/PromptInput/i18n/index.js.map +1 -0
- package/build/cjs/components/organisms/PromptInput/i18n/ru.json +8 -0
- package/build/cjs/components/organisms/PromptInput/index.d.ts +2 -1
- package/build/cjs/components/organisms/PromptInput/index.js.map +1 -1
- package/build/cjs/components/organisms/PromptInput/types.d.ts +1 -0
- package/build/cjs/components/organisms/PromptInput/types.js.map +1 -1
- package/build/cjs/components/organisms/PromptInput/usePromptCommands.d.ts +41 -0
- package/build/cjs/components/organisms/PromptInput/usePromptCommands.js +222 -0
- package/build/cjs/components/organisms/PromptInput/usePromptCommands.js.map +1 -0
- package/build/cjs/components/organisms/PromptInput/usePromptInput.d.ts +10 -1
- package/build/cjs/components/organisms/PromptInput/usePromptInput.js +100 -11
- package/build/cjs/components/organisms/PromptInput/usePromptInput.js.map +1 -1
- package/build/cjs/components/pages/AIStudioChat/AIStudioChat.js +139 -52
- package/build/cjs/components/pages/AIStudioChat/AIStudioChat.js.map +1 -1
- package/build/cjs/components/pages/AIStudioChat/request.d.ts +4 -0
- package/build/cjs/components/pages/AIStudioChat/request.js +11 -0
- package/build/cjs/components/pages/AIStudioChat/request.js.map +1 -0
- package/build/cjs/components/pages/AIStudioChat/types.d.ts +5 -5
- package/build/cjs/components/pages/AIStudioChat/types.js.map +1 -1
- package/build/cjs/components/pages/ChatContainer/ChatContainer.js +10 -4
- package/build/cjs/components/pages/ChatContainer/ChatContainer.js.map +1 -1
- package/build/cjs/components/pages/ChatContainer/types.d.ts +1 -1
- package/build/cjs/components/pages/ChatContainer/types.js.map +1 -1
- package/build/cjs/components/pages/ChatContainer/useChatContainer.d.ts +1 -0
- package/build/cjs/components/pages/ChatContainer/useChatContainer.js +5 -1
- package/build/cjs/components/pages/ChatContainer/useChatContainer.js.map +1 -1
- package/build/cjs/package.json +1 -1
- package/build/cjs/types/index.d.ts +1 -0
- package/build/cjs/types/index.js +1 -0
- package/build/cjs/types/index.js.map +1 -1
- package/build/cjs/types/messages.d.ts +3 -0
- package/build/cjs/types/messages.js.map +1 -1
- package/build/cjs/types/promptCommands.d.ts +44 -0
- package/build/cjs/types/promptCommands.js +3 -0
- package/build/cjs/types/promptCommands.js.map +1 -0
- package/build/docs/components/molecules/PromptInputBody.md +16 -15
- package/build/docs/components/organisms/PromptInput.md +151 -0
- package/build/docs/components/pages/AIStudioChat.md +38 -0
- package/build/esm/components/molecules/PromptInputBody/PromptInputBody.d.ts +1 -0
- package/build/esm/components/molecules/PromptInputBody/PromptInputBody.js +2 -8
- package/build/esm/components/molecules/PromptInputBody/PromptInputBody.js.map +1 -1
- package/build/esm/components/organisms/PromptInput/PromptInput.css +37 -0
- package/build/esm/components/organisms/PromptInput/PromptInput.d.ts +13 -2
- package/build/esm/components/organisms/PromptInput/PromptInput.js +65 -4
- package/build/esm/components/organisms/PromptInput/PromptInput.js.map +1 -1
- package/build/esm/components/organisms/PromptInput/PromptInputCommands.d.ts +9 -0
- package/build/esm/components/organisms/PromptInput/PromptInputCommands.js +36 -0
- package/build/esm/components/organisms/PromptInput/PromptInputCommands.js.map +1 -0
- package/build/esm/components/organisms/PromptInput/PromptInputFull.d.ts +2 -0
- package/build/esm/components/organisms/PromptInput/PromptInputFull.js +4 -4
- package/build/esm/components/organisms/PromptInput/PromptInputFull.js.map +1 -1
- package/build/esm/components/organisms/PromptInput/PromptInputSimple.d.ts +2 -0
- package/build/esm/components/organisms/PromptInput/PromptInputSimple.js +3 -3
- package/build/esm/components/organisms/PromptInput/PromptInputSimple.js.map +1 -1
- package/build/esm/components/organisms/PromptInput/commandUtils.d.ts +13 -0
- package/build/esm/components/organisms/PromptInput/commandUtils.js +23 -0
- package/build/esm/components/organisms/PromptInput/commandUtils.js.map +1 -0
- package/build/esm/components/organisms/PromptInput/i18n/en.json +8 -0
- package/build/esm/components/organisms/PromptInput/i18n/index.d.ts +13 -0
- package/build/esm/components/organisms/PromptInput/i18n/index.js +6 -0
- package/build/esm/components/organisms/PromptInput/i18n/index.js.map +1 -0
- package/build/esm/components/organisms/PromptInput/i18n/ru.json +8 -0
- package/build/esm/components/organisms/PromptInput/index.d.ts +2 -1
- package/build/esm/components/organisms/PromptInput/index.js.map +1 -1
- package/build/esm/components/organisms/PromptInput/types.d.ts +1 -0
- package/build/esm/components/organisms/PromptInput/types.js.map +1 -1
- package/build/esm/components/organisms/PromptInput/usePromptCommands.d.ts +41 -0
- package/build/esm/components/organisms/PromptInput/usePromptCommands.js +219 -0
- package/build/esm/components/organisms/PromptInput/usePromptCommands.js.map +1 -0
- package/build/esm/components/organisms/PromptInput/usePromptInput.d.ts +10 -1
- package/build/esm/components/organisms/PromptInput/usePromptInput.js +101 -12
- package/build/esm/components/organisms/PromptInput/usePromptInput.js.map +1 -1
- package/build/esm/components/pages/AIStudioChat/AIStudioChat.js +139 -52
- package/build/esm/components/pages/AIStudioChat/AIStudioChat.js.map +1 -1
- package/build/esm/components/pages/AIStudioChat/request.d.ts +4 -0
- package/build/esm/components/pages/AIStudioChat/request.js +8 -0
- package/build/esm/components/pages/AIStudioChat/request.js.map +1 -0
- package/build/esm/components/pages/AIStudioChat/types.d.ts +5 -5
- package/build/esm/components/pages/AIStudioChat/types.js.map +1 -1
- package/build/esm/components/pages/ChatContainer/ChatContainer.js +12 -6
- package/build/esm/components/pages/ChatContainer/ChatContainer.js.map +1 -1
- package/build/esm/components/pages/ChatContainer/types.d.ts +1 -1
- package/build/esm/components/pages/ChatContainer/types.js.map +1 -1
- package/build/esm/components/pages/ChatContainer/useChatContainer.d.ts +1 -0
- package/build/esm/components/pages/ChatContainer/useChatContainer.js +5 -1
- package/build/esm/components/pages/ChatContainer/useChatContainer.js.map +1 -1
- package/build/esm/package.json +1 -1
- package/build/esm/types/index.d.ts +1 -0
- package/build/esm/types/index.js +1 -0
- package/build/esm/types/index.js.map +1 -1
- package/build/esm/types/messages.d.ts +3 -0
- package/build/esm/types/messages.js.map +1 -1
- package/build/esm/types/promptCommands.d.ts +44 -0
- package/build/esm/types/promptCommands.js +2 -0
- package/build/esm/types/promptCommands.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { useEffect, useId, useRef, useState } from 'react';
|
|
2
|
+
import { findCommandMatch, replaceCommand } from "./commandUtils.js";
|
|
3
|
+
const EMPTY_SELECTIONS = [];
|
|
4
|
+
export function usePromptCommands({ config, resetKey, value, onChange, inputRef, disabled, maxLength, }) {
|
|
5
|
+
var _a;
|
|
6
|
+
const [internalSelections, setInternalSelections] = useState([]);
|
|
7
|
+
const selections = config ? ((_a = config.selections) !== null && _a !== void 0 ? _a : internalSelections) : EMPTY_SELECTIONS;
|
|
8
|
+
const [session, setSession] = useState();
|
|
9
|
+
const [result, setResult] = useState();
|
|
10
|
+
const [activeIndex, setActiveIndex] = useState(0);
|
|
11
|
+
const [selectionError, setSelectionError] = useState(false);
|
|
12
|
+
const suppressed = useRef();
|
|
13
|
+
const selectionRevision = useRef(0);
|
|
14
|
+
const previousResetKey = useRef(resetKey);
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
if (previousResetKey.current === resetKey)
|
|
17
|
+
return;
|
|
18
|
+
previousResetKey.current = resetKey;
|
|
19
|
+
selectionRevision.current++;
|
|
20
|
+
setInternalSelections([]);
|
|
21
|
+
setSession(undefined);
|
|
22
|
+
setResult(undefined);
|
|
23
|
+
setSelectionError(false);
|
|
24
|
+
suppressed.current = undefined;
|
|
25
|
+
}, [resetKey]);
|
|
26
|
+
const listId = useId();
|
|
27
|
+
const source = config === null || config === void 0 ? void 0 : config.sources.find((item) => item.id === (session === null || session === void 0 ? void 0 : session.sourceId));
|
|
28
|
+
const open = Boolean(session && source && !disabled);
|
|
29
|
+
const items = result && result.source === source && result.session === session ? result.items : [];
|
|
30
|
+
const loading = open && ((result === null || result === void 0 ? void 0 : result.session) !== session || (result === null || result === void 0 ? void 0 : result.source) !== source);
|
|
31
|
+
const error = selectionError ||
|
|
32
|
+
(result && result.source === source && result.session === session && result.error);
|
|
33
|
+
const mounted = useRef(true);
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
mounted.current = true;
|
|
36
|
+
return () => {
|
|
37
|
+
mounted.current = false;
|
|
38
|
+
};
|
|
39
|
+
}, []);
|
|
40
|
+
const latest = useRef({ selections, config });
|
|
41
|
+
if (latest.current.selections.length !== selections.length ||
|
|
42
|
+
latest.current.selections.some((item, index) => item !== selections[index])) {
|
|
43
|
+
selectionRevision.current++;
|
|
44
|
+
}
|
|
45
|
+
latest.current = { selections, config };
|
|
46
|
+
const updateSelections = (next) => {
|
|
47
|
+
var _a, _b;
|
|
48
|
+
if (!mounted.current)
|
|
49
|
+
return;
|
|
50
|
+
selectionRevision.current++;
|
|
51
|
+
latest.current.selections = next;
|
|
52
|
+
setInternalSelections(next);
|
|
53
|
+
(_b = (_a = latest.current.config) === null || _a === void 0 ? void 0 : _a.onSelectionsChange) === null || _b === void 0 ? void 0 : _b.call(_a, next);
|
|
54
|
+
};
|
|
55
|
+
const clearSubmitted = (submitted) => {
|
|
56
|
+
updateSelections(latest.current.selections.filter((item) => !submitted.includes(item)));
|
|
57
|
+
};
|
|
58
|
+
const close = () => {
|
|
59
|
+
suppressed.current = value;
|
|
60
|
+
setSession(undefined);
|
|
61
|
+
};
|
|
62
|
+
const detect = (text, start, end, edited = false) => {
|
|
63
|
+
if (disabled || !config || (!edited && suppressed.current !== undefined))
|
|
64
|
+
return;
|
|
65
|
+
if (edited)
|
|
66
|
+
suppressed.current = undefined;
|
|
67
|
+
const match = findCommandMatch(text, start, end, config.sources);
|
|
68
|
+
setSession((previous) => {
|
|
69
|
+
if (!match)
|
|
70
|
+
return undefined;
|
|
71
|
+
if ((previous === null || previous === void 0 ? void 0 : previous.origin) === 'input' &&
|
|
72
|
+
previous.sourceId === match.sourceId &&
|
|
73
|
+
previous.query === match.query &&
|
|
74
|
+
previous.start === match.start &&
|
|
75
|
+
previous.end === match.end)
|
|
76
|
+
return previous;
|
|
77
|
+
return Object.assign(Object.assign({}, match), { origin: 'input' });
|
|
78
|
+
});
|
|
79
|
+
};
|
|
80
|
+
useEffect(() => {
|
|
81
|
+
if (disabled)
|
|
82
|
+
setSession(undefined);
|
|
83
|
+
}, [disabled]);
|
|
84
|
+
useEffect(() => {
|
|
85
|
+
if (!session || !source || !open)
|
|
86
|
+
return undefined;
|
|
87
|
+
const controller = new AbortController();
|
|
88
|
+
setActiveIndex(0);
|
|
89
|
+
setSelectionError(false);
|
|
90
|
+
const load = async () => {
|
|
91
|
+
var _a;
|
|
92
|
+
try {
|
|
93
|
+
const matches = source.getItems
|
|
94
|
+
? await source.getItems(session.query, { signal: controller.signal })
|
|
95
|
+
: ((_a = source.items) !== null && _a !== void 0 ? _a : []).filter((item) => {
|
|
96
|
+
var _a;
|
|
97
|
+
return [item.title, item.description, ...((_a = item.keywords) !== null && _a !== void 0 ? _a : [])].some((text) => text === null || text === void 0 ? void 0 : text.toLocaleLowerCase().includes(session.query.toLocaleLowerCase()));
|
|
98
|
+
});
|
|
99
|
+
if (!controller.signal.aborted) {
|
|
100
|
+
setResult({ session, source, items: matches });
|
|
101
|
+
setActiveIndex(Math.max(0, matches.findIndex((item) => !item.disabled)));
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
catch (_b) {
|
|
105
|
+
if (!controller.signal.aborted)
|
|
106
|
+
setResult({ session, source, items: [], error: true });
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
load();
|
|
110
|
+
return () => controller.abort();
|
|
111
|
+
}, [session, source, open]);
|
|
112
|
+
const select = (item) => {
|
|
113
|
+
var _a;
|
|
114
|
+
if (!session || !source || item.disabled || disabled)
|
|
115
|
+
return;
|
|
116
|
+
const replacement = session.origin === 'button' && item.insertText === undefined
|
|
117
|
+
? { value, caret: session.start }
|
|
118
|
+
: replaceCommand(value, session, item.insertText);
|
|
119
|
+
if (maxLength !== undefined && replacement.value.length > maxLength) {
|
|
120
|
+
setSelectionError(true);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
try {
|
|
124
|
+
(_a = source.onSelect) === null || _a === void 0 ? void 0 : _a.call(source, item, { query: session.query, origin: session.origin });
|
|
125
|
+
}
|
|
126
|
+
catch (_b) {
|
|
127
|
+
setSelectionError(true);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (item.keepSelected !== false) {
|
|
131
|
+
const selection = Object.assign({ sourceId: source.id, id: item.id, title: item.title }, (item.data && { data: item.data }));
|
|
132
|
+
updateSelections([
|
|
133
|
+
...selections.filter((entry) => entry.sourceId !== source.id || entry.id !== item.id),
|
|
134
|
+
selection,
|
|
135
|
+
]);
|
|
136
|
+
}
|
|
137
|
+
onChange(replacement.value);
|
|
138
|
+
suppressed.current = replacement.value;
|
|
139
|
+
setSession(undefined);
|
|
140
|
+
requestAnimationFrame(() => {
|
|
141
|
+
const input = inputRef.current;
|
|
142
|
+
if (!(input === null || input === void 0 ? void 0 : input.isConnected))
|
|
143
|
+
return;
|
|
144
|
+
input.focus();
|
|
145
|
+
input.setSelectionRange(replacement.caret, replacement.caret);
|
|
146
|
+
});
|
|
147
|
+
};
|
|
148
|
+
const handleKeyDown = (event) => {
|
|
149
|
+
if (!open || event.nativeEvent.isComposing)
|
|
150
|
+
return false;
|
|
151
|
+
if (event.key === 'Escape' || event.key === 'Tab') {
|
|
152
|
+
if (event.key === 'Escape')
|
|
153
|
+
event.preventDefault();
|
|
154
|
+
close();
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
|
|
158
|
+
event.preventDefault();
|
|
159
|
+
const direction = event.key === 'ArrowDown' ? 1 : -1;
|
|
160
|
+
for (let step = 1; step <= items.length; step++) {
|
|
161
|
+
const index = (activeIndex + direction * step + items.length) % items.length;
|
|
162
|
+
if (!items[index].disabled) {
|
|
163
|
+
setActiveIndex(index);
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
if (event.key === 'Enter' && !event.shiftKey && !event.ctrlKey && !event.metaKey) {
|
|
170
|
+
event.preventDefault();
|
|
171
|
+
if (items[activeIndex])
|
|
172
|
+
select(items[activeIndex]);
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
175
|
+
return false;
|
|
176
|
+
};
|
|
177
|
+
return {
|
|
178
|
+
selections,
|
|
179
|
+
clearSubmitted,
|
|
180
|
+
captureSubmission: () => {
|
|
181
|
+
const submitted = [...latest.current.selections];
|
|
182
|
+
clearSubmitted(submitted);
|
|
183
|
+
let expectedRevision = selectionRevision.current;
|
|
184
|
+
return {
|
|
185
|
+
selections: submitted,
|
|
186
|
+
hasChanged: () => selectionRevision.current !== expectedRevision,
|
|
187
|
+
restore: () => {
|
|
188
|
+
updateSelections(submitted);
|
|
189
|
+
expectedRevision = selectionRevision.current;
|
|
190
|
+
},
|
|
191
|
+
clear: () => clearSubmitted(submitted),
|
|
192
|
+
};
|
|
193
|
+
},
|
|
194
|
+
open,
|
|
195
|
+
source,
|
|
196
|
+
session,
|
|
197
|
+
items,
|
|
198
|
+
loading,
|
|
199
|
+
error,
|
|
200
|
+
listId,
|
|
201
|
+
activeIndex,
|
|
202
|
+
select,
|
|
203
|
+
close,
|
|
204
|
+
handleKeyDown,
|
|
205
|
+
detect,
|
|
206
|
+
remove: (selection) => updateSelections(selections.filter((item) => item !== selection)),
|
|
207
|
+
openSource: (sourceId) => {
|
|
208
|
+
var _a, _b, _c, _d;
|
|
209
|
+
if (disabled)
|
|
210
|
+
return;
|
|
211
|
+
const start = (_b = (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.selectionStart) !== null && _b !== void 0 ? _b : value.length;
|
|
212
|
+
const end = (_d = (_c = inputRef.current) === null || _c === void 0 ? void 0 : _c.selectionEnd) !== null && _d !== void 0 ? _d : start;
|
|
213
|
+
setSession({ sourceId, query: '', start, end, origin: 'button' });
|
|
214
|
+
},
|
|
215
|
+
search: (query) => setSession((previous) => (previous ? Object.assign(Object.assign({}, previous), { query }) : previous)),
|
|
216
|
+
retry: () => setSession((previous) => (previous ? Object.assign({}, previous) : previous)),
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
//# sourceMappingURL=usePromptCommands.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"usePromptCommands.js","sourceRoot":"../../../../../src","sources":["components/organisms/PromptInput/usePromptCommands.ts"],"names":[],"mappings":"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","sourcesContent":["import {useEffect, useId, useRef, useState} from 'react';\nimport type {KeyboardEvent, RefObject} from 'react';\n\nimport type {\n PromptCommandItem,\n PromptCommandSource,\n PromptCommandsConfig,\n PromptSelection,\n} from '../../../types';\n\nimport {type CommandMatch, findCommandMatch, replaceCommand} from './commandUtils';\n\ntype Session = CommandMatch & {origin: 'input' | 'button'};\nconst EMPTY_SELECTIONS: readonly PromptSelection[] = [];\n\nexport function usePromptCommands({\n config,\n resetKey,\n value,\n onChange,\n inputRef,\n disabled,\n maxLength,\n}: {\n config?: PromptCommandsConfig;\n resetKey?: string | number;\n value: string;\n onChange: (value: string) => void;\n inputRef: RefObject<HTMLTextAreaElement>;\n disabled: boolean;\n maxLength?: number;\n}) {\n const [internalSelections, setInternalSelections] = useState<PromptSelection[]>([]);\n const selections = config ? (config.selections ?? internalSelections) : EMPTY_SELECTIONS;\n const [session, setSession] = useState<Session>();\n const [result, setResult] = useState<{\n session: Session;\n source: PromptCommandSource;\n items: readonly PromptCommandItem[];\n error?: boolean;\n }>();\n const [activeIndex, setActiveIndex] = useState(0);\n const [selectionError, setSelectionError] = useState(false);\n const suppressed = useRef<string>();\n const selectionRevision = useRef(0);\n const previousResetKey = useRef(resetKey);\n useEffect(() => {\n if (previousResetKey.current === resetKey) return;\n previousResetKey.current = resetKey;\n selectionRevision.current++;\n setInternalSelections([]);\n setSession(undefined);\n setResult(undefined);\n setSelectionError(false);\n suppressed.current = undefined;\n }, [resetKey]);\n const listId = useId();\n const source = config?.sources.find((item) => item.id === session?.sourceId);\n const open = Boolean(session && source && !disabled);\n const items =\n result && result.source === source && result.session === session ? result.items : [];\n const loading = open && (result?.session !== session || result?.source !== source);\n const error =\n selectionError ||\n (result && result.source === source && result.session === session && result.error);\n const mounted = useRef(true);\n useEffect(() => {\n mounted.current = true;\n return () => {\n mounted.current = false;\n };\n }, []);\n const latest = useRef({selections, config});\n if (\n latest.current.selections.length !== selections.length ||\n latest.current.selections.some((item, index) => item !== selections[index])\n ) {\n selectionRevision.current++;\n }\n latest.current = {selections, config};\n\n const updateSelections = (next: PromptSelection[]) => {\n if (!mounted.current) return;\n selectionRevision.current++;\n latest.current.selections = next;\n setInternalSelections(next);\n latest.current.config?.onSelectionsChange?.(next);\n };\n const clearSubmitted = (submitted: readonly PromptSelection[]) => {\n updateSelections(latest.current.selections.filter((item) => !submitted.includes(item)));\n };\n const close = () => {\n suppressed.current = value;\n setSession(undefined);\n };\n const detect = (text: string, start: number, end: number, edited = false) => {\n if (disabled || !config || (!edited && suppressed.current !== undefined)) return;\n if (edited) suppressed.current = undefined;\n const match = findCommandMatch(text, start, end, config.sources);\n setSession((previous) => {\n if (!match) return undefined;\n if (\n previous?.origin === 'input' &&\n previous.sourceId === match.sourceId &&\n previous.query === match.query &&\n previous.start === match.start &&\n previous.end === match.end\n )\n return previous;\n return {...match, origin: 'input'};\n });\n };\n useEffect(() => {\n if (disabled) setSession(undefined);\n }, [disabled]);\n useEffect(() => {\n if (!session || !source || !open) return undefined;\n const controller = new AbortController();\n setActiveIndex(0);\n setSelectionError(false);\n const load = async () => {\n try {\n const matches = source.getItems\n ? await source.getItems(session.query, {signal: controller.signal})\n : (source.items ?? []).filter((item) =>\n [item.title, item.description, ...(item.keywords ?? [])].some((text) =>\n text?.toLocaleLowerCase().includes(session.query.toLocaleLowerCase()),\n ),\n );\n if (!controller.signal.aborted) {\n setResult({session, source, items: matches});\n setActiveIndex(\n Math.max(\n 0,\n matches.findIndex((item) => !item.disabled),\n ),\n );\n }\n } catch {\n if (!controller.signal.aborted)\n setResult({session, source, items: [], error: true});\n }\n };\n load();\n return () => controller.abort();\n }, [session, source, open]);\n\n const select = (item: PromptCommandItem) => {\n if (!session || !source || item.disabled || disabled) return;\n const replacement =\n session.origin === 'button' && item.insertText === undefined\n ? {value, caret: session.start}\n : replaceCommand(value, session, item.insertText);\n if (maxLength !== undefined && replacement.value.length > maxLength) {\n setSelectionError(true);\n return;\n }\n try {\n source.onSelect?.(item, {query: session.query, origin: session.origin});\n } catch {\n setSelectionError(true);\n return;\n }\n if (item.keepSelected !== false) {\n const selection: PromptSelection = {\n sourceId: source.id,\n id: item.id,\n title: item.title,\n ...(item.data && {data: item.data}),\n };\n updateSelections([\n ...selections.filter(\n (entry) => entry.sourceId !== source.id || entry.id !== item.id,\n ),\n selection,\n ]);\n }\n onChange(replacement.value);\n suppressed.current = replacement.value;\n setSession(undefined);\n requestAnimationFrame(() => {\n const input = inputRef.current;\n if (!input?.isConnected) return;\n input.focus();\n input.setSelectionRange(replacement.caret, replacement.caret);\n });\n };\n const handleKeyDown = (event: KeyboardEvent<HTMLElement>) => {\n if (!open || event.nativeEvent.isComposing) return false;\n if (event.key === 'Escape' || event.key === 'Tab') {\n if (event.key === 'Escape') event.preventDefault();\n close();\n return true;\n }\n if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {\n event.preventDefault();\n const direction = event.key === 'ArrowDown' ? 1 : -1;\n for (let step = 1; step <= items.length; step++) {\n const index = (activeIndex + direction * step + items.length) % items.length;\n if (!items[index].disabled) {\n setActiveIndex(index);\n break;\n }\n }\n return true;\n }\n if (event.key === 'Enter' && !event.shiftKey && !event.ctrlKey && !event.metaKey) {\n event.preventDefault();\n if (items[activeIndex]) select(items[activeIndex]);\n return true;\n }\n return false;\n };\n return {\n selections,\n clearSubmitted,\n captureSubmission: () => {\n const submitted = [...latest.current.selections];\n clearSubmitted(submitted);\n let expectedRevision = selectionRevision.current;\n return {\n selections: submitted,\n hasChanged: () => selectionRevision.current !== expectedRevision,\n restore: () => {\n updateSelections(submitted);\n expectedRevision = selectionRevision.current;\n },\n clear: () => clearSubmitted(submitted),\n };\n },\n open,\n source,\n session,\n items,\n loading,\n error,\n listId,\n activeIndex,\n select,\n close,\n handleKeyDown,\n detect,\n remove: (selection: PromptSelection) =>\n updateSelections(selections.filter((item) => item !== selection)),\n openSource: (sourceId: string) => {\n if (disabled) return;\n const start = inputRef.current?.selectionStart ?? value.length;\n const end = inputRef.current?.selectionEnd ?? start;\n setSession({sourceId, query: '', start, end, origin: 'button'});\n },\n search: (query: string) =>\n setSession((previous) => (previous ? {...previous, query} : previous)),\n retry: () => setSession((previous) => (previous ? {...previous} : previous)),\n };\n}\n"]}
|
|
@@ -4,7 +4,13 @@ import { ChatStatus, TSubmitData } from "../../../types/index.js";
|
|
|
4
4
|
*/
|
|
5
5
|
export type UsePromptInputProps = {
|
|
6
6
|
/** Callback when message is sent */
|
|
7
|
-
onSend: (data: TSubmitData) => Promise<void>;
|
|
7
|
+
onSend: (data: TSubmitData) => Promise<void | false>;
|
|
8
|
+
resetKey?: string | number;
|
|
9
|
+
captureSubmission?: () => {
|
|
10
|
+
restore: () => void;
|
|
11
|
+
hasChanged: () => boolean;
|
|
12
|
+
clear: () => void;
|
|
13
|
+
};
|
|
8
14
|
/** Callback when sending is cancelled */
|
|
9
15
|
onCancel?: () => Promise<void>;
|
|
10
16
|
/** Initial value */
|
|
@@ -22,6 +28,7 @@ export type UsePromptInputProps = {
|
|
|
22
28
|
* Hook return type for PromptInput state management
|
|
23
29
|
*/
|
|
24
30
|
export type UsePromptInputReturn = {
|
|
31
|
+
sendError?: boolean;
|
|
25
32
|
/** Current input value */
|
|
26
33
|
value: string;
|
|
27
34
|
/** Can submit the form */
|
|
@@ -36,6 +43,8 @@ export type UsePromptInputReturn = {
|
|
|
36
43
|
handleKeyDown: (event: React.KeyboardEvent<HTMLTextAreaElement>) => void;
|
|
37
44
|
/** Handle submit */
|
|
38
45
|
handleSubmit: () => Promise<void>;
|
|
46
|
+
submit: (data?: TSubmitData) => Promise<void>;
|
|
47
|
+
clearFailedDraft: () => boolean;
|
|
39
48
|
/** Attachments */
|
|
40
49
|
attachments: File[];
|
|
41
50
|
/** Set attachments */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useCallback, useState } from 'react';
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
2
|
/**
|
|
3
3
|
* Custom hook for managing PromptInput state and behavior
|
|
4
4
|
*
|
|
@@ -6,14 +6,36 @@ import { useCallback, useState } from 'react';
|
|
|
6
6
|
* @returns Hook return value with state and handlers
|
|
7
7
|
*/
|
|
8
8
|
export function usePromptInput(props) {
|
|
9
|
-
const { onSend, onCancel, initialValue = '', maxLength, disabled = false, status = 'ready', onValueChange, } = props;
|
|
9
|
+
const { onSend, onCancel, initialValue = '', maxLength, disabled = false, status = 'ready', onValueChange, resetKey, captureSubmission, } = props;
|
|
10
10
|
const [value, setValue] = useState(initialValue);
|
|
11
|
-
const [
|
|
11
|
+
const [sending, setSending] = useState(false);
|
|
12
|
+
const [sendError, setSendError] = useState(false);
|
|
13
|
+
const mounted = useRef(true);
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
mounted.current = true;
|
|
16
|
+
return () => {
|
|
17
|
+
mounted.current = false;
|
|
18
|
+
};
|
|
19
|
+
}, []);
|
|
20
|
+
const revision = useRef(0);
|
|
21
|
+
const inFlight = useRef(false);
|
|
22
|
+
const [attachments, updateAttachments] = useState([]);
|
|
23
|
+
const setAttachments = useCallback((files) => {
|
|
24
|
+
revision.current++;
|
|
25
|
+
updateAttachments(files);
|
|
26
|
+
}, []);
|
|
27
|
+
const failedDraft = useRef(null);
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
revision.current++;
|
|
30
|
+
failedDraft.current = null;
|
|
31
|
+
}, [resetKey]);
|
|
12
32
|
const updateValue = useCallback((nextValue) => {
|
|
33
|
+
revision.current++;
|
|
34
|
+
setSendError(false);
|
|
13
35
|
setValue(nextValue);
|
|
14
36
|
onValueChange === null || onValueChange === void 0 ? void 0 : onValueChange(nextValue);
|
|
15
37
|
}, [onValueChange]);
|
|
16
|
-
const isSubmitted = status === 'submitted';
|
|
38
|
+
const isSubmitted = status === 'submitted' || sending;
|
|
17
39
|
const trimmedValue = value.trim();
|
|
18
40
|
const canSubmit = !disabled && !isSubmitted && trimmedValue.length > 0;
|
|
19
41
|
// Map ChatStatus to submit button state
|
|
@@ -48,6 +70,8 @@ export function usePromptInput(props) {
|
|
|
48
70
|
}
|
|
49
71
|
}
|
|
50
72
|
// Input is disabled when loading
|
|
73
|
+
if (sending && submitButtonState !== 'cancelable')
|
|
74
|
+
submitButtonState = 'loading';
|
|
51
75
|
const isInputDisabled = submitButtonState === 'loading';
|
|
52
76
|
const handleChange = useCallback((newValue) => {
|
|
53
77
|
if (maxLength && newValue.length > maxLength) {
|
|
@@ -55,20 +79,82 @@ export function usePromptInput(props) {
|
|
|
55
79
|
}
|
|
56
80
|
updateValue(newValue);
|
|
57
81
|
}, [maxLength, updateValue]);
|
|
82
|
+
const submit = useCallback(async (override) => {
|
|
83
|
+
var _a;
|
|
84
|
+
const content = (_a = override === null || override === void 0 ? void 0 : override.content.trim()) !== null && _a !== void 0 ? _a : trimmedValue;
|
|
85
|
+
if (disabled || isSubmitted || inFlight.current || !content)
|
|
86
|
+
return;
|
|
87
|
+
const draftValue = value;
|
|
88
|
+
const submitData = Object.assign(Object.assign(Object.assign({}, override), { content }), (attachments.length > 0 && { attachments }));
|
|
89
|
+
inFlight.current = true;
|
|
90
|
+
setSending(true);
|
|
91
|
+
setSendError(false);
|
|
92
|
+
failedDraft.current = null;
|
|
93
|
+
const selectionSnapshot = captureSubmission === null || captureSubmission === void 0 ? void 0 : captureSubmission();
|
|
94
|
+
updateValue('');
|
|
95
|
+
updateAttachments([]);
|
|
96
|
+
const clearedRevision = revision.current;
|
|
97
|
+
const restore = () => {
|
|
98
|
+
if (!mounted.current ||
|
|
99
|
+
revision.current !== clearedRevision ||
|
|
100
|
+
(selectionSnapshot === null || selectionSnapshot === void 0 ? void 0 : selectionSnapshot.hasChanged()))
|
|
101
|
+
return;
|
|
102
|
+
updateValue(draftValue);
|
|
103
|
+
updateAttachments(attachments);
|
|
104
|
+
selectionSnapshot === null || selectionSnapshot === void 0 ? void 0 : selectionSnapshot.restore();
|
|
105
|
+
const restoredRevision = revision.current;
|
|
106
|
+
failedDraft.current = () => {
|
|
107
|
+
if (!mounted.current ||
|
|
108
|
+
revision.current !== restoredRevision ||
|
|
109
|
+
(selectionSnapshot === null || selectionSnapshot === void 0 ? void 0 : selectionSnapshot.hasChanged()))
|
|
110
|
+
return false;
|
|
111
|
+
updateValue('');
|
|
112
|
+
updateAttachments([]);
|
|
113
|
+
selectionSnapshot === null || selectionSnapshot === void 0 ? void 0 : selectionSnapshot.clear();
|
|
114
|
+
return true;
|
|
115
|
+
};
|
|
116
|
+
return true;
|
|
117
|
+
};
|
|
118
|
+
try {
|
|
119
|
+
const result = await onSend(submitData);
|
|
120
|
+
if (result === false)
|
|
121
|
+
restore();
|
|
122
|
+
}
|
|
123
|
+
catch (_b) {
|
|
124
|
+
if (restore())
|
|
125
|
+
setSendError(true);
|
|
126
|
+
}
|
|
127
|
+
finally {
|
|
128
|
+
inFlight.current = false;
|
|
129
|
+
if (mounted.current)
|
|
130
|
+
setSending(false);
|
|
131
|
+
}
|
|
132
|
+
}, [
|
|
133
|
+
disabled,
|
|
134
|
+
isSubmitted,
|
|
135
|
+
trimmedValue,
|
|
136
|
+
value,
|
|
137
|
+
attachments,
|
|
138
|
+
onSend,
|
|
139
|
+
updateValue,
|
|
140
|
+
captureSubmission,
|
|
141
|
+
]);
|
|
142
|
+
const clearFailedDraft = useCallback(() => {
|
|
143
|
+
var _a;
|
|
144
|
+
const clear = failedDraft.current;
|
|
145
|
+
failedDraft.current = null;
|
|
146
|
+
return (_a = clear === null || clear === void 0 ? void 0 : clear()) !== null && _a !== void 0 ? _a : false;
|
|
147
|
+
}, []);
|
|
58
148
|
const handleSubmit = useCallback(async () => {
|
|
59
149
|
if (submitButtonState === 'cancelable' && onCancel) {
|
|
60
150
|
await onCancel();
|
|
61
151
|
return;
|
|
62
152
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
const submitData = Object.assign({ content: trimmedValue }, (attachments.length > 0 && { attachments }));
|
|
67
|
-
onSend(submitData);
|
|
68
|
-
updateValue('');
|
|
69
|
-
setAttachments([]);
|
|
70
|
-
}, [submitButtonState, canSubmit, trimmedValue, attachments, onSend, onCancel, updateValue]);
|
|
153
|
+
await submit();
|
|
154
|
+
}, [submitButtonState, onCancel, submit]);
|
|
71
155
|
const handleKeyDown = useCallback((event) => {
|
|
156
|
+
if (event.nativeEvent.isComposing || event.defaultPrevented)
|
|
157
|
+
return;
|
|
72
158
|
const isEnter = event.code === 'Enter' || event.code === 'NumpadEnter';
|
|
73
159
|
// Insert new line on Ctrl/Cmd + Enter
|
|
74
160
|
if (isEnter && (event.ctrlKey || event.metaKey)) {
|
|
@@ -89,12 +175,15 @@ export function usePromptInput(props) {
|
|
|
89
175
|
}, [canSubmit, handleSubmit, updateValue, value]);
|
|
90
176
|
return {
|
|
91
177
|
value,
|
|
178
|
+
sendError,
|
|
92
179
|
canSubmit,
|
|
93
180
|
submitButtonState,
|
|
94
181
|
isInputDisabled,
|
|
95
182
|
handleChange,
|
|
96
183
|
handleKeyDown,
|
|
97
184
|
handleSubmit,
|
|
185
|
+
submit,
|
|
186
|
+
clearFailedDraft,
|
|
98
187
|
attachments,
|
|
99
188
|
setAttachments,
|
|
100
189
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"usePromptInput.js","sourceRoot":"../../../../../src","sources":["components/organisms/PromptInput/usePromptInput.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,WAAW,EAAE,QAAQ,EAAC,MAAM,OAAO,CAAC;AAgD5C;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,KAA0B;IACrD,MAAM,EACF,MAAM,EACN,QAAQ,EACR,YAAY,GAAG,EAAE,EACjB,SAAS,EACT,QAAQ,GAAG,KAAK,EAChB,MAAM,GAAG,OAAO,EAChB,aAAa,GAChB,GAAG,KAAK,CAAC;IAEV,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAC,YAAY,CAAC,CAAC;IACjD,MAAM,CAAC,WAAW,EAAE,cAAc,CAAC,GAAG,QAAQ,CAAS,EAAE,CAAC,CAAC;IAE3D,MAAM,WAAW,GAAG,WAAW,CAC3B,CAAC,SAAiB,EAAE,EAAE;QAClB,QAAQ,CAAC,SAAS,CAAC,CAAC;QACpB,aAAa,aAAb,aAAa,uBAAb,aAAa,CAAG,SAAS,CAAC,CAAC;IAC/B,CAAC,EACD,CAAC,aAAa,CAAC,CAClB,CAAC;IACF,MAAM,WAAW,GAAG,MAAM,KAAK,WAAW,CAAC;IAE3C,MAAM,YAAY,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAClC,MAAM,SAAS,GAAG,CAAC,QAAQ,IAAI,CAAC,WAAW,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,CAAC;IAEvE,wCAAwC;IACxC,+CAA+C;IAC/C,+CAA+C;IAC/C,sDAAsD;IACtD,kFAAkF;IAClF,mDAAmD;IACnD,IAAI,iBAAiB,GAAsD,UAAU,CAAC;IAEtF,uDAAuD;IACvD,IAAI,QAAQ,EAAE,CAAC;QACX,iBAAiB,GAAG,UAAU,CAAC;IACnC,CAAC;SAAM,IAAI,CAAC,YAAY,IAAI,CAAC,MAAM,KAAK,OAAO,IAAI,MAAM,KAAK,OAAO,CAAC,EAAE,CAAC;QACrE,iBAAiB,GAAG,UAAU,CAAC;IACnC,CAAC;SAAM,CAAC;QACJ,QAAQ,MAAM,EAAE,CAAC;YACb,KAAK,OAAO,CAAC;YACb,KAAK,OAAO;gBACR,iBAAiB,GAAG,SAAS,CAAC;gBAC9B,MAAM;YACV,KAAK,WAAW,CAAC;YACjB,KAAK,mBAAmB;gBACpB,iBAAiB,GAAG,QAAQ,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,SAAS,CAAC;gBACxD,MAAM;YACV,KAAK,WAAW;gBACZ,iBAAiB,GAAG,SAAS,CAAC;gBAC9B,MAAM;YACV;gBACI,iBAAiB,GAAG,SAAS,CAAC;QACtC,CAAC;IACL,CAAC;IAED,iCAAiC;IACjC,MAAM,eAAe,GAAG,iBAAiB,KAAK,SAAS,CAAC;IAExD,MAAM,YAAY,GAAG,WAAW,CAC5B,CAAC,QAAgB,EAAE,EAAE;QACjB,IAAI,SAAS,IAAI,QAAQ,CAAC,MAAM,GAAG,SAAS,EAAE,CAAC;YAC3C,OAAO;QACX,CAAC;QACD,WAAW,CAAC,QAAQ,CAAC,CAAC;IAC1B,CAAC,EACD,CAAC,SAAS,EAAE,WAAW,CAAC,CAC3B,CAAC;IAEF,MAAM,YAAY,GAAG,WAAW,CAAC,KAAK,IAAI,EAAE;QACxC,IAAI,iBAAiB,KAAK,YAAY,IAAI,QAAQ,EAAE,CAAC;YACjD,MAAM,QAAQ,EAAE,CAAC;YACjB,OAAO;QACX,CAAC;QAED,IAAI,CAAC,SAAS,EAAE,CAAC;YACb,OAAO;QACX,CAAC;QAED,MAAM,UAAU,mBACZ,OAAO,EAAE,YAAY,IAClB,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,IAAI,EAAC,WAAW,EAAC,CAAC,CAC/C,CAAC;QAEF,MAAM,CAAC,UAAU,CAAC,CAAC;QACnB,WAAW,CAAC,EAAE,CAAC,CAAC;QAChB,cAAc,CAAC,EAAE,CAAC,CAAC;IACvB,CAAC,EAAE,CAAC,iBAAiB,EAAE,SAAS,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,WAAW,CAAC,CAAC,CAAC;IAE7F,MAAM,aAAa,GAAG,WAAW,CAC7B,CAAC,KAA+C,EAAE,EAAE;QAChD,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,KAAK,OAAO,IAAI,KAAK,CAAC,IAAI,KAAK,aAAa,CAAC;QAEvE,sCAAsC;QACtC,IAAI,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,IAAI,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;YAC9C,WAAW,CAAC,GAAG,KAAK,IAAI,CAAC,CAAC;YAC1B,KAAK,CAAC,cAAc,EAAE,CAAC;YACvB,OAAO;QACX,CAAC;QAED,qCAAqC;QACrC,IAAI,OAAO,IAAI,CAAC,KAAK,CAAC,QAAQ,IAAI,CAAC,SAAS,EAAE,CAAC;YAC3C,KAAK,CAAC,cAAc,EAAE,CAAC;YACvB,OAAO;QACX,CAAC;QAED,kCAAkC;QAClC,IAAI,OAAO,IAAI,CAAC,KAAK,CAAC,QAAQ,IAAI,SAAS,EAAE,CAAC;YAC1C,KAAK,CAAC,cAAc,EAAE,CAAC;YACvB,YAAY,EAAE,CAAC;QACnB,CAAC;IACL,CAAC,EACD,CAAC,SAAS,EAAE,YAAY,EAAE,WAAW,EAAE,KAAK,CAAC,CAChD,CAAC;IAEF,OAAO;QACH,KAAK;QACL,SAAS;QACT,iBAAiB;QACjB,eAAe;QACf,YAAY;QACZ,aAAa;QACb,YAAY;QACZ,WAAW;QACX,cAAc;KACjB,CAAC;AACN,CAAC","sourcesContent":["import {useCallback, useState} from 'react';\n\nimport {ChatStatus, TSubmitData} from '../../../types';\n\n/**\n * Hook props for managing PromptInput state\n */\nexport type UsePromptInputProps = {\n /** Callback when message is sent */\n onSend: (data: TSubmitData) => Promise<void>;\n /** Callback when sending is cancelled */\n onCancel?: () => Promise<void>;\n /** Initial value */\n initialValue?: string;\n /** Maximum length of input */\n maxLength?: number;\n /** Disabled state */\n disabled?: boolean;\n /** Chat status to determine input behavior */\n status?: ChatStatus;\n /** Called whenever the hook changes its internal value. */\n onValueChange?: (value: string) => void;\n};\n\n/**\n * Hook return type for PromptInput state management\n */\nexport type UsePromptInputReturn = {\n /** Current input value */\n value: string;\n /** Can submit the form */\n canSubmit: boolean;\n /** Submit button state */\n submitButtonState: 'enabled' | 'disabled' | 'loading' | 'cancelable';\n /** Is input disabled (when loading) */\n isInputDisabled: boolean;\n /** Handle input change */\n handleChange: (value: string) => void;\n /** Handle key down */\n handleKeyDown: (event: React.KeyboardEvent<HTMLTextAreaElement>) => void;\n /** Handle submit */\n handleSubmit: () => Promise<void>;\n /** Attachments */\n attachments: File[];\n /** Set attachments */\n setAttachments: (files: File[]) => void;\n};\n\n/**\n * Custom hook for managing PromptInput state and behavior\n *\n * @param props - Hook props\n * @returns Hook return value with state and handlers\n */\nexport function usePromptInput(props: UsePromptInputProps): UsePromptInputReturn {\n const {\n onSend,\n onCancel,\n initialValue = '',\n maxLength,\n disabled = false,\n status = 'ready',\n onValueChange,\n } = props;\n\n const [value, setValue] = useState(initialValue);\n const [attachments, setAttachments] = useState<File[]>([]);\n\n const updateValue = useCallback(\n (nextValue: string) => {\n setValue(nextValue);\n onValueChange?.(nextValue);\n },\n [onValueChange],\n );\n const isSubmitted = status === 'submitted';\n\n const trimmedValue = value.trim();\n const canSubmit = !disabled && !isSubmitted && trimmedValue.length > 0;\n\n // Map ChatStatus to submit button state\n // ChatStatus.ready → submitButtonState.enabled\n // ChatStatus.error → submitButtonState.enabled\n // ChatStatus.streaming → submitButtonState.cancelable\n // ChatStatus.streaming_loading → submitButtonState.cancelable (same as streaming)\n // ChatStatus.submitted → submitButtonState.loading\n let submitButtonState: 'enabled' | 'disabled' | 'loading' | 'cancelable' = 'disabled';\n\n // disabled by props or empty value and status is ready\n if (disabled) {\n submitButtonState = 'disabled';\n } else if (!trimmedValue && (status === 'ready' || status === 'error')) {\n submitButtonState = 'disabled';\n } else {\n switch (status) {\n case 'ready':\n case 'error':\n submitButtonState = 'enabled';\n break;\n case 'streaming':\n case 'streaming_loading':\n submitButtonState = onCancel ? 'cancelable' : 'enabled';\n break;\n case 'submitted':\n submitButtonState = 'loading';\n break;\n default:\n submitButtonState = 'enabled';\n }\n }\n\n // Input is disabled when loading\n const isInputDisabled = submitButtonState === 'loading';\n\n const handleChange = useCallback(\n (newValue: string) => {\n if (maxLength && newValue.length > maxLength) {\n return;\n }\n updateValue(newValue);\n },\n [maxLength, updateValue],\n );\n\n const handleSubmit = useCallback(async () => {\n if (submitButtonState === 'cancelable' && onCancel) {\n await onCancel();\n return;\n }\n\n if (!canSubmit) {\n return;\n }\n\n const submitData: TSubmitData = {\n content: trimmedValue,\n ...(attachments.length > 0 && {attachments}),\n };\n\n onSend(submitData);\n updateValue('');\n setAttachments([]);\n }, [submitButtonState, canSubmit, trimmedValue, attachments, onSend, onCancel, updateValue]);\n\n const handleKeyDown = useCallback(\n (event: React.KeyboardEvent<HTMLTextAreaElement>) => {\n const isEnter = event.code === 'Enter' || event.code === 'NumpadEnter';\n\n // Insert new line on Ctrl/Cmd + Enter\n if (isEnter && (event.ctrlKey || event.metaKey)) {\n updateValue(`${value}\\n`);\n event.preventDefault();\n return;\n }\n\n // Prevent submission if can't submit\n if (isEnter && !event.shiftKey && !canSubmit) {\n event.preventDefault();\n return;\n }\n\n // Submit on Enter (without Shift)\n if (isEnter && !event.shiftKey && canSubmit) {\n event.preventDefault();\n handleSubmit();\n }\n },\n [canSubmit, handleSubmit, updateValue, value],\n );\n\n return {\n value,\n canSubmit,\n submitButtonState,\n isInputDisabled,\n handleChange,\n handleKeyDown,\n handleSubmit,\n attachments,\n setAttachments,\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"usePromptInput.js","sourceRoot":"../../../../../src","sources":["components/organisms/PromptInput/usePromptInput.ts"],"names":[],"mappings":"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","sourcesContent":["import {useCallback, useEffect, useRef, useState} from 'react';\n\nimport {ChatStatus, TSubmitData} from '../../../types';\n\n/**\n * Hook props for managing PromptInput state\n */\nexport type UsePromptInputProps = {\n /** Callback when message is sent */\n onSend: (data: TSubmitData) => Promise<void | false>;\n resetKey?: string | number;\n captureSubmission?: () => {restore: () => void; hasChanged: () => boolean; clear: () => void};\n /** Callback when sending is cancelled */\n onCancel?: () => Promise<void>;\n /** Initial value */\n initialValue?: string;\n /** Maximum length of input */\n maxLength?: number;\n /** Disabled state */\n disabled?: boolean;\n /** Chat status to determine input behavior */\n status?: ChatStatus;\n /** Called whenever the hook changes its internal value. */\n onValueChange?: (value: string) => void;\n};\n\n/**\n * Hook return type for PromptInput state management\n */\nexport type UsePromptInputReturn = {\n sendError?: boolean;\n /** Current input value */\n value: string;\n /** Can submit the form */\n canSubmit: boolean;\n /** Submit button state */\n submitButtonState: 'enabled' | 'disabled' | 'loading' | 'cancelable';\n /** Is input disabled (when loading) */\n isInputDisabled: boolean;\n /** Handle input change */\n handleChange: (value: string) => void;\n /** Handle key down */\n handleKeyDown: (event: React.KeyboardEvent<HTMLTextAreaElement>) => void;\n /** Handle submit */\n handleSubmit: () => Promise<void>;\n submit: (data?: TSubmitData) => Promise<void>;\n clearFailedDraft: () => boolean;\n /** Attachments */\n attachments: File[];\n /** Set attachments */\n setAttachments: (files: File[]) => void;\n};\n\n/**\n * Custom hook for managing PromptInput state and behavior\n *\n * @param props - Hook props\n * @returns Hook return value with state and handlers\n */\nexport function usePromptInput(props: UsePromptInputProps): UsePromptInputReturn {\n const {\n onSend,\n onCancel,\n initialValue = '',\n maxLength,\n disabled = false,\n status = 'ready',\n onValueChange,\n resetKey,\n captureSubmission,\n } = props;\n\n const [value, setValue] = useState(initialValue);\n const [sending, setSending] = useState(false);\n const [sendError, setSendError] = useState(false);\n const mounted = useRef(true);\n useEffect(() => {\n mounted.current = true;\n return () => {\n mounted.current = false;\n };\n }, []);\n const revision = useRef(0);\n const inFlight = useRef(false);\n const [attachments, updateAttachments] = useState<File[]>([]);\n const setAttachments = useCallback((files: File[]) => {\n revision.current++;\n updateAttachments(files);\n }, []);\n const failedDraft = useRef<(() => boolean) | null>(null);\n useEffect(() => {\n revision.current++;\n failedDraft.current = null;\n }, [resetKey]);\n\n const updateValue = useCallback(\n (nextValue: string) => {\n revision.current++;\n setSendError(false);\n setValue(nextValue);\n onValueChange?.(nextValue);\n },\n [onValueChange],\n );\n const isSubmitted = status === 'submitted' || sending;\n\n const trimmedValue = value.trim();\n const canSubmit = !disabled && !isSubmitted && trimmedValue.length > 0;\n\n // Map ChatStatus to submit button state\n // ChatStatus.ready → submitButtonState.enabled\n // ChatStatus.error → submitButtonState.enabled\n // ChatStatus.streaming → submitButtonState.cancelable\n // ChatStatus.streaming_loading → submitButtonState.cancelable (same as streaming)\n // ChatStatus.submitted → submitButtonState.loading\n let submitButtonState: 'enabled' | 'disabled' | 'loading' | 'cancelable' = 'disabled';\n\n // disabled by props or empty value and status is ready\n if (disabled) {\n submitButtonState = 'disabled';\n } else if (!trimmedValue && (status === 'ready' || status === 'error')) {\n submitButtonState = 'disabled';\n } else {\n switch (status) {\n case 'ready':\n case 'error':\n submitButtonState = 'enabled';\n break;\n case 'streaming':\n case 'streaming_loading':\n submitButtonState = onCancel ? 'cancelable' : 'enabled';\n break;\n case 'submitted':\n submitButtonState = 'loading';\n break;\n default:\n submitButtonState = 'enabled';\n }\n }\n\n // Input is disabled when loading\n if (sending && submitButtonState !== 'cancelable') submitButtonState = 'loading';\n const isInputDisabled = submitButtonState === 'loading';\n\n const handleChange = useCallback(\n (newValue: string) => {\n if (maxLength && newValue.length > maxLength) {\n return;\n }\n updateValue(newValue);\n },\n [maxLength, updateValue],\n );\n\n const submit = useCallback(\n async (override?: TSubmitData) => {\n const content = override?.content.trim() ?? trimmedValue;\n if (disabled || isSubmitted || inFlight.current || !content) return;\n const draftValue = value;\n const submitData: TSubmitData = {\n ...override,\n content,\n ...(attachments.length > 0 && {attachments}),\n };\n inFlight.current = true;\n setSending(true);\n setSendError(false);\n failedDraft.current = null;\n const selectionSnapshot = captureSubmission?.();\n updateValue('');\n updateAttachments([]);\n const clearedRevision = revision.current;\n const restore = () => {\n if (\n !mounted.current ||\n revision.current !== clearedRevision ||\n selectionSnapshot?.hasChanged()\n )\n return;\n updateValue(draftValue);\n updateAttachments(attachments);\n selectionSnapshot?.restore();\n const restoredRevision = revision.current;\n failedDraft.current = () => {\n if (\n !mounted.current ||\n revision.current !== restoredRevision ||\n selectionSnapshot?.hasChanged()\n )\n return false;\n updateValue('');\n updateAttachments([]);\n selectionSnapshot?.clear();\n return true;\n };\n return true;\n };\n try {\n const result = await onSend(submitData);\n if (result === false) restore();\n } catch {\n if (restore()) setSendError(true);\n } finally {\n inFlight.current = false;\n if (mounted.current) setSending(false);\n }\n },\n [\n disabled,\n isSubmitted,\n trimmedValue,\n value,\n attachments,\n onSend,\n updateValue,\n captureSubmission,\n ],\n );\n\n const clearFailedDraft = useCallback(() => {\n const clear = failedDraft.current;\n failedDraft.current = null;\n return clear?.() ?? false;\n }, []);\n\n const handleSubmit = useCallback(async () => {\n if (submitButtonState === 'cancelable' && onCancel) {\n await onCancel();\n return;\n }\n await submit();\n }, [submitButtonState, onCancel, submit]);\n\n const handleKeyDown = useCallback(\n (event: React.KeyboardEvent<HTMLTextAreaElement>) => {\n if (event.nativeEvent.isComposing || event.defaultPrevented) return;\n const isEnter = event.code === 'Enter' || event.code === 'NumpadEnter';\n\n // Insert new line on Ctrl/Cmd + Enter\n if (isEnter && (event.ctrlKey || event.metaKey)) {\n updateValue(`${value}\\n`);\n event.preventDefault();\n return;\n }\n\n // Prevent submission if can't submit\n if (isEnter && !event.shiftKey && !canSubmit) {\n event.preventDefault();\n return;\n }\n\n // Submit on Enter (without Shift)\n if (isEnter && !event.shiftKey && canSubmit) {\n event.preventDefault();\n handleSubmit();\n }\n },\n [canSubmit, handleSubmit, updateValue, value],\n );\n\n return {\n value,\n sendError,\n canSubmit,\n submitButtonState,\n isInputDisabled,\n handleChange,\n handleKeyDown,\n handleSubmit,\n submit,\n clearFailedDraft,\n attachments,\n setAttachments,\n };\n}\n"]}
|