Skip to content

Commit

Permalink
feat: close ChatGPTNextWeb#628 add chat commands
Browse files Browse the repository at this point in the history
  • Loading branch information
Yidadaa committed Jun 24, 2023
1 parent e9b4523 commit 1f64345
Show file tree
Hide file tree
Showing 6 changed files with 102 additions and 14 deletions.
43 changes: 43 additions & 0 deletions app/command.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useSearchParams } from "react-router-dom";
import Locale from "./locales";

type Command = (param: string) => void;
interface Commands {
Expand Down Expand Up @@ -26,3 +27,45 @@ export function useCommand(commands: Commands = {}) {
setSearchParams(searchParams);
}
}

interface ChatCommands {
new?: Command;
newm?: Command;
next?: Command;
prev?: Command;
clear?: Command;
del?: Command;
}

export const ChatCommandPrefix = ":";

export function useChatCommand(commands: ChatCommands = {}) {
function extract(userInput: string) {
return (
userInput.startsWith(ChatCommandPrefix) ? userInput.slice(1) : userInput
) as keyof ChatCommands;
}

function search(userInput: string) {
const input = extract(userInput);
const desc = Locale.Chat.Commands;
return Object.keys(commands)
.filter((c) => c.startsWith(input))
.map((c) => ({
title: desc[c as keyof ChatCommands],
content: ChatCommandPrefix + c,
}));
}

function match(userInput: string) {
const command = extract(userInput);
const matched = typeof commands[command] === "function";

return {
matched,
invoke: () => matched && commands[command]!(userInput),
};
}

return { match, search };
}
38 changes: 31 additions & 7 deletions app/components/chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ import { LAST_INPUT_KEY, Path, REQUEST_TIMEOUT_MS } from "../constant";
import { Avatar } from "./emoji";
import { MaskAvatar, MaskConfig } from "./mask";
import { useMaskStore } from "../store/mask";
import { useCommand } from "../command";
import { ChatCommandPrefix, useChatCommand, useCommand } from "../command";
import { prettyObject } from "../utils/format";
import { ExportMessageModal } from "./exporter";
import { getClientConfig } from "../config/client";
Expand Down Expand Up @@ -208,8 +208,7 @@ export function PromptHints(props: {

useEffect(() => {
const onKeyDown = (e: KeyboardEvent) => {
if (noPrompts) return;
if (e.metaKey || e.altKey || e.ctrlKey) {
if (noPrompts || e.metaKey || e.altKey || e.ctrlKey) {
return;
}
// arrow up / down to select prompt
Expand Down Expand Up @@ -510,16 +509,19 @@ export function Chat() {
const [promptHints, setPromptHints] = useState<Prompt[]>([]);
const onSearch = useDebouncedCallback(
(text: string) => {
setPromptHints(promptStore.search(text));
const matchedPrompts = promptStore.search(text);
setPromptHints(matchedPrompts);
},
100,
{ leading: true, trailing: true },
);

const onPromptSelect = (prompt: Prompt) => {
setPromptHints([]);
inputRef.current?.focus();
setTimeout(() => setUserInput(prompt.content), 60);
setTimeout(() => {
setPromptHints([]);
setUserInput(prompt.content);
inputRef.current?.focus();
}, 30);
};

// auto grow input
Expand All @@ -543,6 +545,19 @@ export function Chat() {
// eslint-disable-next-line react-hooks/exhaustive-deps
useEffect(measure, [userInput]);

// chat commands shortcuts
const chatCommands = useChatCommand({
new: () => chatStore.newSession(),
newm: () => navigate(Path.NewChat),
prev: () => chatStore.nextSession(-1),
next: () => chatStore.nextSession(1),
clear: () =>
chatStore.updateCurrentSession(
(session) => (session.clearContextIndex = session.messages.length),
),
del: () => chatStore.deleteSession(chatStore.currentSessionIndex),
});

// only search prompts when user input is short
const SEARCH_TEXT_LIMIT = 30;
const onInput = (text: string) => {
Expand All @@ -552,6 +567,8 @@ export function Chat() {
// clear search results
if (n === 0) {
setPromptHints([]);
} else if (text.startsWith(ChatCommandPrefix)) {
setPromptHints(chatCommands.search(text));
} else if (!config.disablePromptHint && n < SEARCH_TEXT_LIMIT) {
// check if need to trigger auto completion
if (text.startsWith("/")) {
Expand All @@ -563,6 +580,13 @@ export function Chat() {

const doSubmit = (userInput: string) => {
if (userInput.trim() === "") return;
const matchCommand = chatCommands.match(userInput);
if (matchCommand.matched) {
setUserInput("");
setPromptHints([]);
matchCommand.invoke();
return;
}
setIsLoading(true);
chatStore.onUserInput(userInput).then(() => setIsLoading(false));
localStorage.setItem(LAST_INPUT_KEY, userInput);
Expand Down
7 changes: 2 additions & 5 deletions app/components/sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,10 @@ function useHotKey() {
useEffect(() => {
const onKeyDown = (e: KeyboardEvent) => {
if (e.metaKey || e.altKey || e.ctrlKey) {
const n = chatStore.sessions.length;
const limit = (x: number) => (x + n) % n;
const i = chatStore.currentSessionIndex;
if (e.key === "ArrowUp") {
chatStore.selectSession(limit(i - 1));
chatStore.nextSession(-1);
} else if (e.key === "ArrowDown") {
chatStore.selectSession(limit(i + 1));
chatStore.nextSession(1);
}
}
};
Expand Down
10 changes: 9 additions & 1 deletion app/locales/cn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@ const cn = {
Retry: "重试",
Delete: "删除",
},
Commands: {
new: "新建聊天",
newm: "从面具新建聊天",
next: "下一个聊天",
prev: "上一个聊天",
clear: "清除上下文",
del: "删除聊天",
},
InputActions: {
Stop: "停止响应",
ToBottom: "滚到最新",
Expand All @@ -47,7 +55,7 @@ const cn = {
if (submitKey === String(SubmitKey.Enter)) {
inputHints += ",Shift + Enter 换行";
}
return inputHints + ",/ 触发补全";
return inputHints + ",/ 触发补全,: 触发命令";
},
Send: "发送",
Config: {
Expand Down
10 changes: 9 additions & 1 deletion app/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,14 @@ const en: LocaleType = {
Retry: "Retry",
Delete: "Delete",
},
Commands: {
new: "Start a new chat",
newm: "Start a new chat with mask",
next: "Next Chat",
prev: "Previous Chat",
clear: "Clear Context",
del: "Delete Chat",
},
InputActions: {
Stop: "Stop",
ToBottom: "To Latest",
Expand All @@ -48,7 +56,7 @@ const en: LocaleType = {
if (submitKey === String(SubmitKey.Enter)) {
inputHints += ", Shift + Enter to wrap";
}
return inputHints + ", / to search prompts";
return inputHints + ", / to search prompts, : to use commands";
},
Send: "Send",
Config: {
Expand Down
8 changes: 8 additions & 0 deletions app/store/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ interface ChatStore {
newSession: (mask?: Mask) => void;
deleteSession: (index: number) => void;
currentSession: () => ChatSession;
nextSession: (delta: number) => void;
onNewMessage: (message: ChatMessage) => void;
onUserInput: (content: string) => Promise<void>;
summarizeSession: () => void;
Expand Down Expand Up @@ -200,6 +201,13 @@ export const useChatStore = create<ChatStore>()(
}));
},

nextSession(delta) {
const n = get().sessions.length;
const limit = (x: number) => (x + n) % n;
const i = get().currentSessionIndex;
get().selectSession(limit(i + delta));
},

deleteSession(index) {
const deletingLastSession = get().sessions.length === 1;
const deletedSession = get().sessions.at(index);
Expand Down

0 comments on commit 1f64345

Please sign in to comment.