|
|
|
@ -102,7 +102,7 @@ export function ChatList() {
|
|
|
|
|
state.currentSessionIndex,
|
|
|
|
|
state.selectSession,
|
|
|
|
|
state.removeSession,
|
|
|
|
|
]
|
|
|
|
|
],
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
@ -128,7 +128,7 @@ function useSubmitHandler() {
|
|
|
|
|
|
|
|
|
|
const shouldSubmit = (e: KeyboardEvent) => {
|
|
|
|
|
if (e.key !== "Enter") return false;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
(config.submitKey === SubmitKey.AltEnter && e.altKey) ||
|
|
|
|
|
(config.submitKey === SubmitKey.CtrlEnter && e.ctrlKey) ||
|
|
|
|
@ -170,7 +170,10 @@ export function PromptHints(props: {
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function Chat(props: { showSideBar?: () => void, sideBarShowing?: boolean }) {
|
|
|
|
|
export function Chat(props: {
|
|
|
|
|
showSideBar?: () => void;
|
|
|
|
|
sideBarShowing?: boolean;
|
|
|
|
|
}) {
|
|
|
|
|
type RenderMessage = Message & { preview?: boolean };
|
|
|
|
|
|
|
|
|
|
const chatStore = useChatStore();
|
|
|
|
@ -190,11 +193,10 @@ export function Chat(props: { showSideBar?: () => void, sideBarShowing?: boolean
|
|
|
|
|
const [promptHints, setPromptHints] = useState<Prompt[]>([]);
|
|
|
|
|
const onSearch = useDebouncedCallback(
|
|
|
|
|
(text: string) => {
|
|
|
|
|
if (chatStore.config.disablePromptHint) return;
|
|
|
|
|
setPromptHints(promptStore.search(text));
|
|
|
|
|
},
|
|
|
|
|
100,
|
|
|
|
|
{ leading: true, trailing: true }
|
|
|
|
|
{ leading: true, trailing: true },
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const onPromptSelect = (prompt: Prompt) => {
|
|
|
|
@ -203,20 +205,31 @@ export function Chat(props: { showSideBar?: () => void, sideBarShowing?: boolean
|
|
|
|
|
inputRef.current?.focus();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const scrollInput = () => {
|
|
|
|
|
const dom = inputRef.current;
|
|
|
|
|
if (!dom) return;
|
|
|
|
|
const paddingBottomNum: number = parseInt(
|
|
|
|
|
window.getComputedStyle(dom).paddingBottom,
|
|
|
|
|
10,
|
|
|
|
|
);
|
|
|
|
|
dom.scrollTop = dom.scrollHeight - dom.offsetHeight + paddingBottomNum;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// only search prompts when user input is short
|
|
|
|
|
const SEARCH_TEXT_LIMIT = 30;
|
|
|
|
|
const onInput = (text: string) => {
|
|
|
|
|
const textareaDom = inputRef.current
|
|
|
|
|
if (textareaDom) {
|
|
|
|
|
const paddingBottomNum: number = parseInt(window.getComputedStyle(textareaDom).paddingBottom, 10);
|
|
|
|
|
textareaDom.scrollTop = textareaDom.scrollHeight - textareaDom.offsetHeight + paddingBottomNum;
|
|
|
|
|
}
|
|
|
|
|
scrollInput();
|
|
|
|
|
setUserInput(text);
|
|
|
|
|
const n = text.trim().length;
|
|
|
|
|
if (n === 0 || n > SEARCH_TEXT_LIMIT) {
|
|
|
|
|
|
|
|
|
|
// clear search results
|
|
|
|
|
if (n === 0) {
|
|
|
|
|
setPromptHints([]);
|
|
|
|
|
} else {
|
|
|
|
|
onSearch(text);
|
|
|
|
|
} else if (!chatStore.config.disablePromptHint && n < SEARCH_TEXT_LIMIT) {
|
|
|
|
|
// check if need to trigger auto completion
|
|
|
|
|
if (text.startsWith("/") && text.length > 1) {
|
|
|
|
|
onSearch(text.slice(1));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
@ -285,7 +298,7 @@ export function Chat(props: { showSideBar?: () => void, sideBarShowing?: boolean
|
|
|
|
|
preview: true,
|
|
|
|
|
},
|
|
|
|
|
]
|
|
|
|
|
: []
|
|
|
|
|
: [],
|
|
|
|
|
)
|
|
|
|
|
.concat(
|
|
|
|
|
userInput.length > 0
|
|
|
|
@ -297,7 +310,7 @@ export function Chat(props: { showSideBar?: () => void, sideBarShowing?: boolean
|
|
|
|
|
preview: true,
|
|
|
|
|
},
|
|
|
|
|
]
|
|
|
|
|
: []
|
|
|
|
|
: [],
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// auto scroll
|
|
|
|
@ -380,32 +393,33 @@ export function Chat(props: { showSideBar?: () => void, sideBarShowing?: boolean
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
<div className={styles["chat-message-item"]}>
|
|
|
|
|
{(!isUser && !(message.preview || message.content.length === 0)) && (
|
|
|
|
|
<div className={styles["chat-message-top-actions"]}>
|
|
|
|
|
{message.streaming ? (
|
|
|
|
|
<div
|
|
|
|
|
className={styles["chat-message-top-action"]}
|
|
|
|
|
onClick={() => onUserStop(i)}
|
|
|
|
|
>
|
|
|
|
|
{Locale.Chat.Actions.Stop}
|
|
|
|
|
</div>
|
|
|
|
|
) : (
|
|
|
|
|
{!isUser &&
|
|
|
|
|
!(message.preview || message.content.length === 0) && (
|
|
|
|
|
<div className={styles["chat-message-top-actions"]}>
|
|
|
|
|
{message.streaming ? (
|
|
|
|
|
<div
|
|
|
|
|
className={styles["chat-message-top-action"]}
|
|
|
|
|
onClick={() => onUserStop(i)}
|
|
|
|
|
>
|
|
|
|
|
{Locale.Chat.Actions.Stop}
|
|
|
|
|
</div>
|
|
|
|
|
) : (
|
|
|
|
|
<div
|
|
|
|
|
className={styles["chat-message-top-action"]}
|
|
|
|
|
onClick={() => onResend(i)}
|
|
|
|
|
>
|
|
|
|
|
{Locale.Chat.Actions.Retry}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
<div
|
|
|
|
|
className={styles["chat-message-top-action"]}
|
|
|
|
|
onClick={() => onResend(i)}
|
|
|
|
|
onClick={() => copyToClipboard(message.content)}
|
|
|
|
|
>
|
|
|
|
|
{Locale.Chat.Actions.Retry}
|
|
|
|
|
{Locale.Chat.Actions.Copy}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
<div
|
|
|
|
|
className={styles["chat-message-top-action"]}
|
|
|
|
|
onClick={() => copyToClipboard(message.content)}
|
|
|
|
|
>
|
|
|
|
|
{Locale.Chat.Actions.Copy}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
)}
|
|
|
|
|
{(message.preview || message.content.length === 0) &&
|
|
|
|
|
!isUser ? (
|
|
|
|
|
<LoadingIcon />
|
|
|
|
@ -560,7 +574,7 @@ export function Home() {
|
|
|
|
|
state.newSession,
|
|
|
|
|
state.currentSessionIndex,
|
|
|
|
|
state.removeSession,
|
|
|
|
|
]
|
|
|
|
|
],
|
|
|
|
|
);
|
|
|
|
|
const loading = !useHasHydrated();
|
|
|
|
|
const [showSideBar, setShowSideBar] = useState(true);
|
|
|
|
@ -653,7 +667,11 @@ export function Home() {
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
) : (
|
|
|
|
|
<Chat key="chat" showSideBar={() => setShowSideBar(true)} sideBarShowing={showSideBar} />
|
|
|
|
|
<Chat
|
|
|
|
|
key="chat"
|
|
|
|
|
showSideBar={() => setShowSideBar(true)}
|
|
|
|
|
sideBarShowing={showSideBar}
|
|
|
|
|
/>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|