Skip to content

Commit

Permalink
Merge pull request ChatGPTNextWeb#2118 from Yidadaa/bugfix-0624
Browse files Browse the repository at this point in the history
feat: ChatGPTNextWeb#2013 ChatGPTNextWeb#628 switch model button & chat commands
  • Loading branch information
Yidadaa authored Jun 24, 2023
2 parents fb82806 + 8915af9 commit 7ee062e
Show file tree
Hide file tree
Showing 8 changed files with 130 additions and 16 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 };
}
63 changes: 56 additions & 7 deletions app/components/chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import DarkIcon from "../icons/dark.svg";
import AutoIcon from "../icons/auto.svg";
import BottomIcon from "../icons/bottom.svg";
import StopIcon from "../icons/pause.svg";
import RobotIcon from "../icons/robot.svg";

import {
ChatMessage,
Expand All @@ -38,6 +39,7 @@ import {
Theme,
useAppConfig,
DEFAULT_TOPIC,
ALL_MODELS,
} from "../store";

import {
Expand All @@ -64,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 @@ -206,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 @@ -385,6 +386,19 @@ export function ChatActions(props: {
const couldStop = ChatControllerPool.hasPending();
const stopAll = () => ChatControllerPool.stopAll();

// switch model
const currentModel = chatStore.currentSession().mask.modelConfig.model;
function nextModel() {
const models = ALL_MODELS.filter((m) => m.available).map((m) => m.name);
const modelIndex = models.indexOf(currentModel);
const nextIndex = (modelIndex + 1) % models.length;
const nextModel = models[nextIndex];
chatStore.updateCurrentSession((session) => {
session.mask.modelConfig.model = nextModel;
session.mask.syncGlobalConfig = false;
});
}

return (
<div className={chatStyle["chat-input-actions"]}>
{couldStop && (
Expand Down Expand Up @@ -453,6 +467,12 @@ export function ChatActions(props: {
});
}}
/>

<ChatAction
onClick={nextModel}
text={currentModel}
icon={<RobotIcon />}
/>
</div>
);
}
Expand Down Expand Up @@ -489,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 @@ -522,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 @@ -531,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 @@ -542,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 Expand Up @@ -605,6 +650,10 @@ export function Chat() {
const onRightClick = (e: any, message: ChatMessage) => {
// copy to clipboard
if (selectOrCopy(e.currentTarget, message.content)) {
if (userInput.length === 0) {
setUserInput(message.content);
}

e.preventDefault();
}
};
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
1 change: 1 addition & 0 deletions app/icons/robot.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions app/locales/ar.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { SubmitKey } from "../store/config";
import { LocaleType } from "./index";
import type { PartialLocaleType } from "./index";

const ar: LocaleType = {
const ar: PartialLocaleType = {
WIP: "قريبًا...",
Error: {
Unauthorized:
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
Loading

0 comments on commit 7ee062e

Please sign in to comment.