feat: generate chat suggestions for user

This commit is contained in:
Yidadaa 2023-07-09 17:51:42 +08:00
parent b55b01cb13
commit f75b238ebe
4 changed files with 190 additions and 83 deletions

View File

@ -475,3 +475,21 @@
bottom: 30px; bottom: 30px;
} }
} }
.chat-suggestions {
display: flex;
flex-direction: column;
.chat-suggestion {
display: inline;
white-space: nowrap;
border-radius: 20px;
font-size: 12px;
background-color: var(--white);
color: var(--black);
border: var(--border-in-light);
padding: 4px 10px;
animation: slide-in ease 0.3s;
margin-bottom: 5px;
}
}

View File

@ -606,6 +606,8 @@ export function Chat() {
} }
}; };
const [suggestions, setSuggestions] = useState<string[]>([]);
const doSubmit = (userInput: string) => { const doSubmit = (userInput: string) => {
if (userInput.trim() === "") return; if (userInput.trim() === "") return;
const matchCommand = chatCommands.match(userInput); const matchCommand = chatCommands.match(userInput);
@ -616,7 +618,13 @@ export function Chat() {
return; return;
} }
setIsLoading(true); setIsLoading(true);
chatStore.onUserInput(userInput).then(() => setIsLoading(false)); setSuggestions([]);
chatStore.onUserInput(userInput).then(() => {
setIsLoading(false);
chatStore
.getSuggestions()
.then((suggestions) => setSuggestions(suggestions));
});
localStorage.setItem(LAST_INPUT_KEY, userInput); localStorage.setItem(LAST_INPUT_KEY, userInput);
setUserInput(""); setUserInput("");
setPromptHints([]); setPromptHints([]);
@ -1061,6 +1069,25 @@ export function Chat() {
onSearch(""); onSearch("");
}} }}
/> />
{suggestions.length > 0 && (
<div className={styles["chat-suggestions"]}>
{suggestions.map((s, i) => {
return (
<div
key={i}
className={styles["chat-suggestion"] + " clickable"}
onClick={() => {
doSubmit(s);
}}
>
{s}
</div>
);
})}
</div>
)}
<div className={styles["chat-input-panel-inner"]}> <div className={styles["chat-input-panel-inner"]}>
<textarea <textarea
ref={inputRef} ref={inputRef}

View File

@ -28,6 +28,7 @@ import { useAppConfig } from "../store/config";
import { AuthPage } from "./auth"; import { AuthPage } from "./auth";
import { getClientConfig } from "../config/client"; import { getClientConfig } from "../config/client";
import { api } from "../client/api"; import { api } from "../client/api";
import { useChatStore } from "../store";
export function Loading(props: { noLogo?: boolean }) { export function Loading(props: { noLogo?: boolean }) {
return ( return (
@ -114,6 +115,7 @@ function Screen() {
const isHome = location.pathname === Path.Home; const isHome = location.pathname === Path.Home;
const isAuth = location.pathname === Path.Auth; const isAuth = location.pathname === Path.Auth;
const isMobileScreen = useMobileScreen(); const isMobileScreen = useMobileScreen();
const chat = useChatStore();
useEffect(() => { useEffect(() => {
loadAsyncGoogleFont(); loadAsyncGoogleFont();

View File

@ -103,6 +103,7 @@ interface ChatStore {
resetSession: () => void; resetSession: () => void;
getMessagesWithMemory: () => ChatMessage[]; getMessagesWithMemory: () => ChatMessage[];
getMemoryPrompt: () => ChatMessage; getMemoryPrompt: () => ChatMessage;
getSuggestions: () => Promise<string[]>;
clearAllData: () => void; clearAllData: () => void;
} }
@ -278,96 +279,99 @@ export const useChatStore = create<ChatStore>()(
get().summarizeSession(); get().summarizeSession();
}, },
async onUserInput(content) { onUserInput(content) {
const session = get().currentSession(); return new Promise((resolve) => {
const modelConfig = session.mask.modelConfig; const session = get().currentSession();
const modelConfig = session.mask.modelConfig;
const userContent = fillTemplateWith(content, modelConfig); const userContent = fillTemplateWith(content, modelConfig);
console.log("[User Input] after template: ", userContent); console.log("[User Input] after template: ", userContent);
const userMessage: ChatMessage = createMessage({ const userMessage: ChatMessage = createMessage({
role: "user", role: "user",
content: userContent, content: userContent,
}); });
const botMessage: ChatMessage = createMessage({ const botMessage: ChatMessage = createMessage({
role: "assistant", role: "assistant",
streaming: true, streaming: true,
id: userMessage.id! + 1, id: userMessage.id! + 1,
model: modelConfig.model, model: modelConfig.model,
}); });
// get recent messages // get recent messages
const recentMessages = get().getMessagesWithMemory(); const recentMessages = get().getMessagesWithMemory();
const sendMessages = recentMessages.concat(userMessage); const sendMessages = recentMessages.concat(userMessage);
const sessionIndex = get().currentSessionIndex; const sessionIndex = get().currentSessionIndex;
const messageIndex = get().currentSession().messages.length + 1; const messageIndex = get().currentSession().messages.length + 1;
// save user's and bot's message // save user's and bot's message
get().updateCurrentSession((session) => { get().updateCurrentSession((session) => {
const savedUserMessage = { const savedUserMessage = {
...userMessage, ...userMessage,
content, content,
}; };
session.messages = session.messages.concat([ session.messages = session.messages.concat([
savedUserMessage, savedUserMessage,
botMessage, botMessage,
]); ]);
}); });
// make request // make request
api.llm.chat({ api.llm.chat({
messages: sendMessages, messages: sendMessages,
config: { ...modelConfig, stream: true }, config: { ...modelConfig, stream: true },
onUpdate(message) { onUpdate(message) {
botMessage.streaming = true; botMessage.streaming = true;
if (message) { if (message) {
botMessage.content = message; botMessage.content = message;
} }
get().updateCurrentSession((session) => { get().updateCurrentSession((session) => {
session.messages = session.messages.concat(); session.messages = session.messages.concat();
});
},
onFinish(message) {
botMessage.streaming = false;
if (message) {
botMessage.content = message;
get().onNewMessage(botMessage);
}
ChatControllerPool.remove(
sessionIndex,
botMessage.id ?? messageIndex,
);
},
onError(error) {
const isAborted = error.message.includes("aborted");
botMessage.content =
"\n\n" +
prettyObject({
error: true,
message: error.message,
}); });
botMessage.streaming = false; },
userMessage.isError = !isAborted; onFinish(message) {
botMessage.isError = !isAborted; botMessage.streaming = false;
get().updateCurrentSession((session) => { if (message) {
session.messages = session.messages.concat(); botMessage.content = message;
}); get().onNewMessage(botMessage);
ChatControllerPool.remove( resolve();
sessionIndex, }
botMessage.id ?? messageIndex, ChatControllerPool.remove(
); sessionIndex,
botMessage.id ?? messageIndex,
);
},
onError(error) {
const isAborted = error.message.includes("aborted");
botMessage.content =
"\n\n" +
prettyObject({
error: true,
message: error.message,
});
botMessage.streaming = false;
userMessage.isError = !isAborted;
botMessage.isError = !isAborted;
get().updateCurrentSession((session) => {
session.messages = session.messages.concat();
});
ChatControllerPool.remove(
sessionIndex,
botMessage.id ?? messageIndex,
);
console.error("[Chat] failed ", error); console.error("[Chat] failed ", error);
}, },
onController(controller) { onController(controller) {
// collect controller for stop/retry // collect controller for stop/retry
ChatControllerPool.addController( ChatControllerPool.addController(
sessionIndex, sessionIndex,
botMessage.id ?? messageIndex, botMessage.id ?? messageIndex,
controller, controller,
); );
}, },
});
}); });
}, },
@ -594,6 +598,62 @@ export const useChatStore = create<ChatStore>()(
localStorage.clear(); localStorage.clear();
location.reload(); location.reload();
}, },
getSuggestions() {
return new Promise((resolve) => {
// get last bot messages
const messages = get().currentSession().messages;
let lastBotMessage: ChatMessage | undefined = undefined;
for (let i = messages.length - 1; i >= 0; i -= 1) {
if (messages[i].role === "assistant") {
lastBotMessage = messages[i];
break;
}
}
const botMsg = lastBotMessage?.content;
if (!lastBotMessage || !botMsg) return resolve([]);
const prompt = `
here is bot's reponse:
'''
${botMsg}
'''
according to bot's reponse,
- according to the bot's message, generate three short user input suggestions
- detect the bot's language and response in detected language
- no other words, just response in pure json format:
{questions: string[]}";
`;
api.llm.chat({
messages: [
{
role: "user",
content: prompt,
},
],
config: {
model: "gpt-3.5-turbo",
},
onFinish(msg) {
try {
const msgJson = JSON.parse(msg) as {
questions: string[];
};
if (Array.isArray(msgJson.questions)) {
resolve(msgJson.questions);
}
} catch {
console.error("[Suggestions] failed to parse: ", msg);
}
},
});
});
},
}), }),
{ {
name: StoreKey.Chat, name: StoreKey.Chat,