mirror of
https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web.git
synced 2025-09-30 23:26:39 +08:00
feat: dialog title edit
This commit is contained in:
parent
fe858621f2
commit
c99d0d6f10
@ -169,13 +169,38 @@
|
|||||||
transition: all ease 0.3s;
|
transition: all ease 0.3s;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
.chat-item-edit {
|
||||||
|
position: absolute;
|
||||||
|
top: 11px;
|
||||||
|
right: -25px;
|
||||||
|
transition: all ease 0.3s;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.chat-item-edit-input[type=text] {
|
||||||
|
font-size: 14px;
|
||||||
|
color: black;
|
||||||
|
font-weight: bolder;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border: 1px rgb(29,147,171) solid;
|
||||||
|
outline: none;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
.chat-item:hover > .chat-item-delete {
|
.chat-item:hover > .chat-item-delete {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
right: 10px;
|
right: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-item:hover > .chat-item-delete:hover {
|
.chat-item:hover > .chat-item-edit {
|
||||||
|
opacity: 0.5;
|
||||||
|
right: 35px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-item:hover > .chat-item-delete,
|
||||||
|
.chat-item:hover > .chat-item-edit {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@ import ExportIcon from "../icons/export.svg";
|
|||||||
import BotIcon from "../icons/bot.svg";
|
import BotIcon from "../icons/bot.svg";
|
||||||
import AddIcon from "../icons/add.svg";
|
import AddIcon from "../icons/add.svg";
|
||||||
import DeleteIcon from "../icons/delete.svg";
|
import DeleteIcon from "../icons/delete.svg";
|
||||||
|
import EditIcon from "../icons/edit-title.svg";
|
||||||
import LoadingIcon from "../icons/three-dots.svg";
|
import LoadingIcon from "../icons/three-dots.svg";
|
||||||
import MenuIcon from "../icons/menu.svg";
|
import MenuIcon from "../icons/menu.svg";
|
||||||
import CloseIcon from "../icons/close.svg";
|
import CloseIcon from "../icons/close.svg";
|
||||||
@ -69,11 +70,16 @@ export function Avatar(props: { role: Message["role"] }) {
|
|||||||
export function ChatItem(props: {
|
export function ChatItem(props: {
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
onDelete?: () => void;
|
onDelete?: () => void;
|
||||||
|
onEdit?: (title: string) => void;
|
||||||
title: string;
|
title: string;
|
||||||
count: number;
|
count: number;
|
||||||
time: string;
|
time: string;
|
||||||
selected: boolean;
|
selected: boolean;
|
||||||
}) {
|
}) {
|
||||||
|
const [dialog, setDialog] = useState({
|
||||||
|
isEdit: false,
|
||||||
|
title: props.title,
|
||||||
|
});
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`${styles["chat-item"]} ${
|
className={`${styles["chat-item"]} ${
|
||||||
@ -81,7 +87,30 @@ export function ChatItem(props: {
|
|||||||
}`}
|
}`}
|
||||||
onClick={props.onClick}
|
onClick={props.onClick}
|
||||||
>
|
>
|
||||||
<div className={styles["chat-item-title"]}>{props.title}</div>
|
<div className={styles["chat-item-title"]}>
|
||||||
|
{dialog.isEdit ? (
|
||||||
|
<input
|
||||||
|
autoFocus
|
||||||
|
type="text"
|
||||||
|
className={styles["chat-item-edit-input"]}
|
||||||
|
value={dialog.title}
|
||||||
|
onChange={(data) => {
|
||||||
|
setDialog({
|
||||||
|
...dialog,
|
||||||
|
title: data.target.value,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
onBlur={editCompleted}
|
||||||
|
onKeyDown={(event) => {
|
||||||
|
if (event.key === "Enter") {
|
||||||
|
editCompleted();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div>{dialog.title}</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
<div className={styles["chat-item-info"]}>
|
<div className={styles["chat-item-info"]}>
|
||||||
<div className={styles["chat-item-count"]}>
|
<div className={styles["chat-item-count"]}>
|
||||||
{Locale.ChatItem.ChatItemCount(props.count)}
|
{Locale.ChatItem.ChatItemCount(props.count)}
|
||||||
@ -91,19 +120,41 @@ export function ChatItem(props: {
|
|||||||
<div className={styles["chat-item-delete"]} onClick={props.onDelete}>
|
<div className={styles["chat-item-delete"]} onClick={props.onDelete}>
|
||||||
<DeleteIcon />
|
<DeleteIcon />
|
||||||
</div>
|
</div>
|
||||||
|
<div className={styles["chat-item-edit"]} onClick={editStart}>
|
||||||
|
<EditIcon />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
function editStart() {
|
||||||
|
setDialog({
|
||||||
|
...dialog,
|
||||||
|
isEdit: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function editCompleted() {
|
||||||
|
setDialog({
|
||||||
|
...dialog,
|
||||||
|
isEdit: false,
|
||||||
|
});
|
||||||
|
props.onEdit && props.onEdit(dialog.title);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ChatList() {
|
export function ChatList() {
|
||||||
const [sessions, selectedIndex, selectSession, removeSession] = useChatStore(
|
const [
|
||||||
(state) => [
|
sessions,
|
||||||
|
selectedIndex,
|
||||||
|
selectSession,
|
||||||
|
removeSession,
|
||||||
|
editDialogTitle,
|
||||||
|
] = useChatStore((state) => [
|
||||||
state.sessions,
|
state.sessions,
|
||||||
state.currentSessionIndex,
|
state.currentSessionIndex,
|
||||||
state.selectSession,
|
state.selectSession,
|
||||||
state.removeSession,
|
state.removeSession,
|
||||||
]
|
state.editDialogTitle,
|
||||||
);
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles["chat-list"]}>
|
<div className={styles["chat-list"]}>
|
||||||
@ -116,6 +167,7 @@ export function ChatList() {
|
|||||||
selected={i === selectedIndex}
|
selected={i === selectedIndex}
|
||||||
onClick={() => selectSession(i)}
|
onClick={() => selectSession(i)}
|
||||||
onDelete={() => removeSession(i)}
|
onDelete={() => removeSession(i)}
|
||||||
|
onEdit={(title: string) => editDialogTitle(i, title)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@ -196,7 +248,7 @@ export function Chat(props: {
|
|||||||
setPromptHints(promptStore.search(text));
|
setPromptHints(promptStore.search(text));
|
||||||
},
|
},
|
||||||
100,
|
100,
|
||||||
{ leading: true, trailing: true }
|
{ leading: true, trailing: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
const onPromptSelect = (prompt: Prompt) => {
|
const onPromptSelect = (prompt: Prompt) => {
|
||||||
@ -210,7 +262,7 @@ export function Chat(props: {
|
|||||||
if (!dom) return;
|
if (!dom) return;
|
||||||
const paddingBottomNum: number = parseInt(
|
const paddingBottomNum: number = parseInt(
|
||||||
window.getComputedStyle(dom).paddingBottom,
|
window.getComputedStyle(dom).paddingBottom,
|
||||||
10
|
10,
|
||||||
);
|
);
|
||||||
dom.scrollTop = dom.scrollHeight - dom.offsetHeight + paddingBottomNum;
|
dom.scrollTop = dom.scrollHeight - dom.offsetHeight + paddingBottomNum;
|
||||||
};
|
};
|
||||||
@ -300,7 +352,7 @@ export function Chat(props: {
|
|||||||
preview: true,
|
preview: true,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
: []
|
: [],
|
||||||
)
|
)
|
||||||
.concat(
|
.concat(
|
||||||
userInput.length > 0
|
userInput.length > 0
|
||||||
@ -312,7 +364,7 @@ export function Chat(props: {
|
|||||||
preview: true,
|
preview: true,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
: []
|
: [],
|
||||||
);
|
);
|
||||||
|
|
||||||
// auto scroll
|
// auto scroll
|
||||||
@ -340,7 +392,7 @@ export function Chat(props: {
|
|||||||
const newTopic = prompt(Locale.Chat.Rename, session.topic);
|
const newTopic = prompt(Locale.Chat.Rename, session.topic);
|
||||||
if (newTopic && newTopic !== session.topic) {
|
if (newTopic && newTopic !== session.topic) {
|
||||||
chatStore.updateCurrentSession(
|
chatStore.updateCurrentSession(
|
||||||
(session) => (session.topic = newTopic!)
|
(session) => (session.topic = newTopic!),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
@ -586,7 +638,7 @@ export function Home() {
|
|||||||
state.newSession,
|
state.newSession,
|
||||||
state.currentSessionIndex,
|
state.currentSessionIndex,
|
||||||
state.removeSession,
|
state.removeSession,
|
||||||
]
|
],
|
||||||
);
|
);
|
||||||
const loading = !useHasHydrated();
|
const loading = !useHasHydrated();
|
||||||
const [showSideBar, setShowSideBar] = useState(true);
|
const [showSideBar, setShowSideBar] = useState(true);
|
||||||
|
1
app/icons/edit-title.svg
Normal file
1
app/icons/edit-title.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" style="fill: rgba(77, 175, 214, 1);transform: ;msFilter:;"><path d="M19.045 7.401c.378-.378.586-.88.586-1.414s-.208-1.036-.586-1.414l-1.586-1.586c-.378-.378-.88-.586-1.414-.586s-1.036.208-1.413.585L4 13.585V18h4.413L19.045 7.401zm-3-3 1.587 1.585-1.59 1.584-1.586-1.585 1.589-1.584zM6 16v-1.585l7.04-7.018 1.586 1.586L7.587 16H6zm-2 4h16v2H4z"></path></svg>
|
After Width: | Height: | Size: 440 B |
@ -186,6 +186,7 @@ interface ChatStore {
|
|||||||
removeSession: (index: number) => void;
|
removeSession: (index: number) => void;
|
||||||
selectSession: (index: number) => void;
|
selectSession: (index: number) => void;
|
||||||
newSession: () => void;
|
newSession: () => void;
|
||||||
|
editDialogTitle: (index: number, title: string) => void;
|
||||||
currentSession: () => ChatSession;
|
currentSession: () => ChatSession;
|
||||||
onNewMessage: (message: Message) => void;
|
onNewMessage: (message: Message) => void;
|
||||||
onUserInput: (content: string) => Promise<void>;
|
onUserInput: (content: string) => Promise<void>;
|
||||||
@ -266,6 +267,17 @@ export const useChatStore = create<ChatStore>()(
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
editDialogTitle(index: number, title: string) {
|
||||||
|
set((state) => {
|
||||||
|
const sessions = state.sessions;
|
||||||
|
sessions[index].topic = title;
|
||||||
|
|
||||||
|
return {
|
||||||
|
sessions,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
newSession() {
|
newSession() {
|
||||||
set((state) => ({
|
set((state) => ({
|
||||||
currentSessionIndex: 0,
|
currentSessionIndex: 0,
|
||||||
|
Loading…
Reference in New Issue
Block a user