diff --git a/app/components/chat.module.scss b/app/components/chat.module.scss
index 6f443200..a3ab5606 100644
--- a/app/components/chat.module.scss
+++ b/app/components/chat.module.scss
@@ -95,11 +95,28 @@
}
.context-prompt {
+ .context-prompt-insert {
+ display: flex;
+ justify-content: center;
+ padding: 4px;
+ opacity: 0.2;
+ transition: all ease 0.3s;
+ background-color: rgba(0, 0, 0, 0);
+ cursor: pointer;
+ border-radius: 4px;
+ margin-top: 4px;
+ margin-bottom: 4px;
+
+ &:hover {
+ opacity: 1;
+ background-color: rgba(0, 0, 0, 0.05);
+ }
+ }
+
.context-prompt-row {
display: flex;
justify-content: center;
width: 100%;
- margin-bottom: 10px;
&:hover {
.context-drag {
diff --git a/app/components/chat.tsx b/app/components/chat.tsx
index db9f8448..7f54a7dd 100644
--- a/app/components/chat.tsx
+++ b/app/components/chat.tsx
@@ -25,6 +25,8 @@ import SettingsIcon from "../icons/chat-settings.svg";
import DeleteIcon from "../icons/clear.svg";
import PinIcon from "../icons/pin.svg";
import EditIcon from "../icons/rename.svg";
+import ConfirmIcon from "../icons/confirm.svg";
+import CancelIcon from "../icons/cancel.svg";
import LightIcon from "../icons/light.svg";
import DarkIcon from "../icons/dark.svg";
@@ -63,6 +65,7 @@ import { IconButton } from "./button";
import styles from "./chat.module.scss";
import {
+ List,
ListItem,
Modal,
Selector,
@@ -73,7 +76,7 @@ import {
import { useLocation, useNavigate } from "react-router-dom";
import { LAST_INPUT_KEY, Path, REQUEST_TIMEOUT_MS } from "../constant";
import { Avatar } from "./emoji";
-import { MaskAvatar, MaskConfig } from "./mask";
+import { ContextPrompts, MaskAvatar, MaskConfig } from "./mask";
import { useMaskStore } from "../store/mask";
import { ChatCommandPrefix, useChatCommand, useCommand } from "../command";
import { prettyObject } from "../utils/format";
@@ -520,6 +523,68 @@ export function ChatActions(props: {
);
}
+export function EditMessageModal(props: { onClose: () => void }) {
+ const chatStore = useChatStore();
+ const session = chatStore.currentSession();
+ const [messages, setMessages] = useState(session.messages.slice());
+
+ return (
+
+ }
+ key="cancel"
+ onClick={() => {
+ props.onClose();
+ }}
+ />,
+ }
+ key="ok"
+ onClick={() => {
+ chatStore.updateCurrentSession(
+ (session) => (session.messages = messages),
+ );
+ props.onClose();
+ }}
+ />,
+ ]}
+ >
+
+
+
+ chatStore.updateCurrentSession(
+ (session) => (session.topic = e.currentTarget.value),
+ )
+ }
+ >
+
+
+ {
+ const newMessages = messages.slice();
+ updater(newMessages);
+ setMessages(newMessages);
+ }}
+ />
+
+
+ );
+}
+
export function Chat() {
type RenderMessage = ChatMessage & { preview?: boolean };
@@ -710,22 +775,6 @@ export function Chat() {
}
};
- const findLastUserIndex = (messageId: string) => {
- // find last user input message
- let lastUserMessageIndex: number | null = null;
- for (let i = 0; i < session.messages.length; i += 1) {
- const message = session.messages[i];
- if (message.role === "user") {
- lastUserMessageIndex = i;
- }
- if (message.id === messageId) {
- break;
- }
- }
-
- return lastUserMessageIndex;
- };
-
const deleteMessage = (msgId?: string) => {
chatStore.updateCurrentSession(
(session) =>
@@ -859,16 +908,6 @@ export function Chat() {
const [showPromptModal, setShowPromptModal] = useState(false);
- const renameSession = () => {
- showPrompt(Locale.Chat.Rename, session.topic).then((newTopic) => {
- if (newTopic && newTopic !== session.topic) {
- chatStore.updateCurrentSession(
- (session) => (session.topic = newTopic!),
- );
- }
- });
- };
-
const clientConfig = useMemo(() => getClientConfig(), []);
const location = useLocation();
@@ -919,6 +958,9 @@ export function Chat() {
},
});
+ // edit / insert message modal
+ const [isEditingMessage, setIsEditingMessage] = useState(false);
+
return (
@@ -938,7 +980,7 @@ export function Chat() {
setIsEditingMessage(true)}
>
{!session.topic ? DEFAULT_TOPIC : session.topic}
@@ -952,7 +994,7 @@ export function Chat() {
}
bordered
- onClick={renameSession}
+ onClick={() => setIsEditingMessage(true)}
/>
)}
@@ -1170,6 +1212,14 @@ export function Chat() {
{showExport && (
setShowExport(false)} />
)}
+
+ {isEditingMessage && (
+ {
+ setIsEditingMessage(false);
+ }}
+ />
+ )}
);
}
diff --git a/app/components/mask.tsx b/app/components/mask.tsx
index b9072213..3d8ce3a2 100644
--- a/app/components/mask.tsx
+++ b/app/components/mask.tsx
@@ -215,67 +215,58 @@ function ContextPromptItem(props: {
const [focusingInput, setFocusingInput] = useState(false);
return (
-
- {(provided) => (
-
- {!focusingInput && (
- <>
-
-
-
-
- props.update({
- ...props.prompt,
- role: e.target.value as any,
- })
- }
- >
- {ROLES.map((r) => (
-
- {r}
-
- ))}
-
- >
- )}
-
setFocusingInput(true)}
- onBlur={() => {
- setFocusingInput(false);
- // If the selection is not removed when the user loses focus, some
- // extensions like "Translate" will always display a floating bar
- window?.getSelection()?.removeAllRanges();
- }}
- onInput={(e) =>
+
+ {!focusingInput && (
+ <>
+
+
+
+
props.update({
...props.prompt,
- content: e.currentTarget.value as any,
+ role: e.target.value as any,
})
}
- />
- {!focusingInput && (
- }
- className={chatStyle["context-delete-button"]}
- onClick={() => props.remove()}
- bordered
- />
- )}
-
+ >
+ {ROLES.map((r) => (
+
+ {r}
+
+ ))}
+
+ >
)}
-
+
setFocusingInput(true)}
+ onBlur={() => {
+ setFocusingInput(false);
+ // If the selection is not removed when the user loses focus, some
+ // extensions like "Translate" will always display a floating bar
+ window?.getSelection()?.removeAllRanges();
+ }}
+ onInput={(e) =>
+ props.update({
+ ...props.prompt,
+ content: e.currentTarget.value as any,
+ })
+ }
+ />
+ {!focusingInput && (
+
}
+ className={chatStyle["context-delete-button"]}
+ onClick={() => props.remove()}
+ bordered
+ />
+ )}
+
);
}
@@ -285,8 +276,8 @@ export function ContextPrompts(props: {
}) {
const context = props.context;
- const addContextPrompt = (prompt: ChatMessage) => {
- props.updateContext((context) => context.push(prompt));
+ const addContextPrompt = (prompt: ChatMessage, i: number) => {
+ props.updateContext((context) => context.splice(i, 0, prompt));
};
const removeContextPrompt = (i: number) => {
@@ -319,13 +310,41 @@ export function ContextPrompts(props: {
{(provided) => (
{context.map((c, i) => (
-
updateContextPrompt(i, prompt)}
- remove={() => removeContextPrompt(i)}
- />
+ >
+ {(provided) => (
+
+
updateContextPrompt(i, prompt)}
+ remove={() => removeContextPrompt(i)}
+ />
+ {
+ addContextPrompt(
+ createMessage({
+ role: "user",
+ content: "",
+ date: new Date().toLocaleString(),
+ }),
+ i + 1,
+ );
+ }}
+ >
+
+
+
+ )}
+
))}
{provided.placeholder}
@@ -333,23 +352,26 @@ export function ContextPrompts(props: {
-
- }
- text={Locale.Context.Add}
- bordered
- className={chatStyle["context-prompt-button"]}
- onClick={() =>
- addContextPrompt(
- createMessage({
- role: "user",
- content: "",
- date: "",
- }),
- )
- }
- />
-
+ {props.context.length === 0 && (
+
+ }
+ text={Locale.Context.Add}
+ bordered
+ className={chatStyle["context-prompt-button"]}
+ onClick={() =>
+ addContextPrompt(
+ createMessage({
+ role: "user",
+ content: "",
+ date: "",
+ }),
+ props.context.length,
+ )
+ }
+ />
+
+ )}
>
);
diff --git a/app/locales/cn.ts b/app/locales/cn.ts
index 54225e31..656cd5fe 100644
--- a/app/locales/cn.ts
+++ b/app/locales/cn.ts
@@ -18,6 +18,12 @@ const cn = {
},
Chat: {
SubTitle: (count: number) => `共 ${count} 条对话`,
+ EditMessage: {
+ Topic: {
+ Title: "聊天主题",
+ SubTitle: "更改当前聊天主题",
+ },
+ },
Actions: {
ChatList: "查看消息列表",
CompressedHistory: "查看压缩后的历史 Prompt",
diff --git a/app/locales/en.ts b/app/locales/en.ts
index ebc19f07..2d83de92 100644
--- a/app/locales/en.ts
+++ b/app/locales/en.ts
@@ -20,6 +20,12 @@ const en: LocaleType = {
},
Chat: {
SubTitle: (count: number) => `${count} messages`,
+ EditMessage: {
+ Topic: {
+ Title: "Topic",
+ SubTitle: "Change the current topic",
+ },
+ },
Actions: {
ChatList: "Go To Chat List",
CompressedHistory: "Compressed History Memory Prompt",