feat: add editable mode
This commit is contained in:
parent
4dbc984351
commit
cc255eea3c
|
@ -252,6 +252,11 @@
|
|||
right: 10px;
|
||||
pointer-events: all;
|
||||
}
|
||||
.chat-message-top-left-actions {
|
||||
opacity: 1;
|
||||
left:10px;
|
||||
pointer-events: all;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -284,6 +289,7 @@
|
|||
.chat-message-item {
|
||||
box-sizing: border-box;
|
||||
max-width: 100%;
|
||||
min-width: 70px;
|
||||
margin-top: 10px;
|
||||
border-radius: 10px;
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
|
@ -324,6 +330,33 @@
|
|||
}
|
||||
}
|
||||
|
||||
.chat-message-top-left-actions {
|
||||
font-size: 12px;
|
||||
position: absolute;
|
||||
top: -26px;
|
||||
transition: all ease 0.3s;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
|
||||
.chat-message-top-left-action {
|
||||
opacity: 0.5;
|
||||
color: var(--black);
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&:not(:first-child) {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.chat-message-user > .chat-message-container > .chat-message-item {
|
||||
background-color: var(--second);
|
||||
}
|
||||
|
@ -341,6 +374,21 @@
|
|||
color: #aaa;
|
||||
}
|
||||
|
||||
.chat-message-action-edit-button {
|
||||
width: 24px;
|
||||
height: 15px;
|
||||
opacity: 1;
|
||||
background: rgba(255, 255, 255, 1);
|
||||
border: 1px solid rgba(29.044500000000014, 147.21574999999993, 170.85, 1);
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
&:not(:first-child) {
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.chat-input-panel {
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
|
|
|
@ -20,6 +20,8 @@ import MenuIcon from "../icons/menu.svg";
|
|||
import CloseIcon from "../icons/close.svg";
|
||||
import CopyIcon from "../icons/copy.svg";
|
||||
import DownloadIcon from "../icons/download.svg";
|
||||
import OkIcon from "../icons/ok.svg";
|
||||
import ErrorIcon from "../icons/error.svg";
|
||||
|
||||
import { Message, SubmitKey, useChatStore, ChatSession } from "../store";
|
||||
import { showModal, showToast } from "./ui-lib";
|
||||
|
@ -190,6 +192,13 @@ export function Chat(props: {
|
|||
const fontSize = useChatStore((state) => state.config.fontSize);
|
||||
|
||||
const inputRef = useRef<HTMLTextAreaElement>(null);
|
||||
const MessageInputRefs = useRef<HTMLDivElement[]>([]);
|
||||
// avoid rendered more hooks error
|
||||
const setMessageInputRef = (element: HTMLDivElement | null, index: number) => {
|
||||
if (element) {
|
||||
MessageInputRefs.current[index] = element;
|
||||
}
|
||||
};
|
||||
const [userInput, setUserInput] = useState("");
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const { submitKey, shouldSubmit } = useSubmitHandler();
|
||||
|
@ -288,6 +297,18 @@ export function Chat(props: {
|
|||
}
|
||||
};
|
||||
|
||||
const confirmEdit = (index: number, content: string) => {
|
||||
chatStore.onConfirmEdit(index, content);
|
||||
}
|
||||
|
||||
const cancelEdit = (index: Message) => {
|
||||
chatStore.onCancelEdit(index);
|
||||
}
|
||||
|
||||
const onEdit = (message: Message) => {
|
||||
chatStore.onUserEdit(message);
|
||||
}
|
||||
|
||||
// for auto-scroll
|
||||
const latestMessageRef = useRef<HTMLDivElement>(null);
|
||||
const [autoScroll, setAutoScroll] = useState(true);
|
||||
|
@ -302,6 +323,7 @@ export function Chat(props: {
|
|||
content: "……",
|
||||
date: new Date().toLocaleString(),
|
||||
preview: true,
|
||||
isEditing: false
|
||||
},
|
||||
]
|
||||
: [],
|
||||
|
@ -314,6 +336,7 @@ export function Chat(props: {
|
|||
content: userInput,
|
||||
date: new Date().toLocaleString(),
|
||||
preview: true,
|
||||
isEditing: false
|
||||
},
|
||||
]
|
||||
: [],
|
||||
|
@ -400,7 +423,6 @@ export function Chat(props: {
|
|||
<div className={styles["chat-body"]}>
|
||||
{messages.map((message, i) => {
|
||||
const isUser = message.role === "user";
|
||||
|
||||
return (
|
||||
<div
|
||||
key={i}
|
||||
|
@ -445,6 +467,16 @@ export function Chat(props: {
|
|||
</div>
|
||||
</div>
|
||||
)}
|
||||
{isUser && !message.preview && !message.isEditing && (
|
||||
<div className={styles["chat-message-top-left-actions"]}>
|
||||
<div
|
||||
className={styles["chat-message-top-left-action"]}
|
||||
onClick={() => onEdit(message)}>,
|
||||
{Locale.Chat.Actions.Edit}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{(message.preview || message.content.length === 0) &&
|
||||
!isUser ? (
|
||||
<LoadingIcon />
|
||||
|
@ -455,7 +487,19 @@ export function Chat(props: {
|
|||
onContextMenu={(e) => onRightClick(e, message)}
|
||||
onDoubleClickCapture={() => setUserInput(message.content)}
|
||||
>
|
||||
<Markdown content={message.content} />
|
||||
{message.isEditing ? (
|
||||
<div
|
||||
key={i}
|
||||
ref={(element) => setMessageInputRef(element, i)}
|
||||
contentEditable={true}
|
||||
suppressContentEditableWarning={true}
|
||||
style={{ outline: "0px solid transparent"}}
|
||||
>
|
||||
{message.content}
|
||||
</div>
|
||||
) : (
|
||||
<Markdown content={message.content} />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
@ -466,6 +510,16 @@ export function Chat(props: {
|
|||
</div>
|
||||
</div>
|
||||
)}
|
||||
{isUser && message.isEditing && (
|
||||
<div className={styles["chat-message-actions"]}>
|
||||
<div className={styles["chat-message-action-edit-button"]} onClick={()=>{confirmEdit(i, MessageInputRefs.current[i].innerText!!)}}>
|
||||
<OkIcon />
|
||||
</div>
|
||||
<div className={styles["chat-message-action-edit-button"]} onClick={()=>{cancelEdit(message)}}>
|
||||
<ErrorIcon />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="10" height="10" viewBox="0 0 10 10" fill="none"><defs><rect id="path_0" x="0" y="0" width="10" height="10" /></defs><g opacity="1" transform="translate(0 0) rotate(0 5 5)"><mask id="bg-mask-0" fill="white"><use xlink:href="#path_0"></use></mask><g mask="url(#bg-mask-0)" ><path id="路径 1" style="stroke:#D43030; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0" transform="translate(1.25 1.25) rotate(0 3.75 3.75)" d="M6.46,7.5L7.5,6.46L4.79,3.75L7.5,1.04L6.46,0L3.75,2.71L1.04,0L0,1.04L2.71,3.75L0,6.46L1.04,7.5L3.75,4.79L6.46,7.5Z " /></g></g></svg>
|
After Width: | Height: | Size: 670 B |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="10" height="10" viewBox="0 0 10 10" fill="none"><defs><rect id="path_0" x="0" y="0" width="10" height="10" /></defs><g opacity="1" transform="translate(0 0) rotate(0 5 5)"><mask id="bg-mask-0" fill="white"><use xlink:href="#path_0"></use></mask><g mask="url(#bg-mask-0)" ><path id="路径 1" style="stroke:#43CF7C; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0" transform="translate(0.8333333333332575 1.875) rotate(0 4.166666666666667 3.125)" d="M3.13,6.25L8.33,1.04L7.29,0L3.13,4.17L1.04,2.08L0,3.13L3.13,6.25Z " /></g></g></svg>
|
After Width: | Height: | Size: 650 B |
|
@ -17,6 +17,7 @@ const cn = {
|
|||
Copy: "复制",
|
||||
Stop: "停止",
|
||||
Retry: "重试",
|
||||
Edit: "编辑"
|
||||
},
|
||||
Rename: "重命名对话",
|
||||
Typing: "正在输入…",
|
||||
|
|
|
@ -19,6 +19,7 @@ const en: LocaleType = {
|
|||
Copy: "Copy",
|
||||
Stop: "Stop",
|
||||
Retry: "Retry",
|
||||
Edit: "Edit",
|
||||
},
|
||||
Rename: "Rename Chat",
|
||||
Typing: "Typing…",
|
||||
|
|
|
@ -19,6 +19,7 @@ const es: LocaleType = {
|
|||
Copy: "Copiar",
|
||||
Stop: "Detener",
|
||||
Retry: "Reintentar",
|
||||
Edit: "Editar",
|
||||
},
|
||||
Rename: "Renombrar chat",
|
||||
Typing: "Escribiendo...",
|
||||
|
|
|
@ -18,6 +18,7 @@ const tw: LocaleType = {
|
|||
Copy: "複製",
|
||||
Stop: "停止",
|
||||
Retry: "重試",
|
||||
Edit: "編輯",
|
||||
},
|
||||
Rename: "重命名對話",
|
||||
Typing: "正在輸入…",
|
||||
|
|
|
@ -174,6 +174,7 @@ export async function requestWithPrompt(messages: Message[], prompt: string) {
|
|||
role: "user",
|
||||
content: prompt,
|
||||
date: new Date().toLocaleString(),
|
||||
isEditing: false
|
||||
},
|
||||
]);
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ import Locale from "../locales";
|
|||
export type Message = ChatCompletionResponseMessage & {
|
||||
date: string;
|
||||
streaming?: boolean;
|
||||
isEditing: boolean;
|
||||
};
|
||||
|
||||
export enum SubmitKey {
|
||||
|
@ -169,6 +170,7 @@ function createEmptySession(): ChatSession {
|
|||
role: "assistant",
|
||||
content: Locale.Store.BotHello,
|
||||
date: createDate,
|
||||
isEditing: false
|
||||
},
|
||||
],
|
||||
stat: {
|
||||
|
@ -190,6 +192,9 @@ interface ChatStore {
|
|||
newSession: () => void;
|
||||
currentSession: () => ChatSession;
|
||||
onNewMessage: (message: Message) => void;
|
||||
onUserEdit: (message: Message) => void;
|
||||
onConfirmEdit: (index: number, content: string) => void;
|
||||
onCancelEdit: (message: Message) => void;
|
||||
onUserInput: (content: string) => Promise<void>;
|
||||
summarizeSession: () => void;
|
||||
updateStat: (message: Message) => void;
|
||||
|
@ -297,11 +302,26 @@ export const useChatStore = create<ChatStore>()(
|
|||
get().summarizeSession();
|
||||
},
|
||||
|
||||
onUserEdit(message) {
|
||||
message.isEditing = true;
|
||||
set(() => ({}))
|
||||
},
|
||||
|
||||
onConfirmEdit(index, content) {
|
||||
const session = get().currentSession();
|
||||
session.messages = session.messages.slice(0, index)
|
||||
get().onUserInput(content)
|
||||
},
|
||||
onCancelEdit(message) {
|
||||
message.isEditing = false;
|
||||
set(() => ({}))
|
||||
},
|
||||
async onUserInput(content) {
|
||||
const userMessage: Message = {
|
||||
role: "user",
|
||||
content,
|
||||
date: new Date().toLocaleString(),
|
||||
isEditing: false
|
||||
};
|
||||
|
||||
const botMessage: Message = {
|
||||
|
@ -309,6 +329,7 @@ export const useChatStore = create<ChatStore>()(
|
|||
role: "assistant",
|
||||
date: new Date().toLocaleString(),
|
||||
streaming: true,
|
||||
isEditing: false
|
||||
};
|
||||
|
||||
// get recent messages
|
||||
|
@ -444,6 +465,7 @@ export const useChatStore = create<ChatStore>()(
|
|||
role: "system",
|
||||
content: Locale.Store.Prompt.Summarize,
|
||||
date: "",
|
||||
isEditing: false
|
||||
}),
|
||||
{
|
||||
filterBot: false,
|
||||
|
|
Loading…
Reference in New Issue