mirror of
https://github.com/Yidadaa/ChatGPT-Next-Web.git
synced 2025-09-23 20:50:08 +08:00
Compare commits
12 Commits
v2.8.8
...
suggestion
Author | SHA1 | Date | |
---|---|---|---|
|
f75b238ebe | ||
|
b55b01cb13 | ||
|
90d8f3117f | ||
|
c7e976c8c5 | ||
|
6014b765f4 | ||
|
ca295588c4 | ||
|
3432d4df29 | ||
|
05fcb96181 | ||
|
6bb27f90a4 | ||
|
c10e8382a9 | ||
|
6653a31eb7 | ||
|
42561d04de |
@@ -84,7 +84,7 @@ One-Click to get well-designed cross-platform ChatGPT web UI.
|
|||||||
- [x] 预制角色:使用预制角色快速定制新对话 [#993](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/993)
|
- [x] 预制角色:使用预制角色快速定制新对话 [#993](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/993)
|
||||||
- [x] 分享为图片,分享到 ShareGPT 链接 [#1741](https://github.com/Yidadaa/ChatGPT-Next-Web/pull/1741)
|
- [x] 分享为图片,分享到 ShareGPT 链接 [#1741](https://github.com/Yidadaa/ChatGPT-Next-Web/pull/1741)
|
||||||
- [x] 使用 tauri 打包桌面应用
|
- [x] 使用 tauri 打包桌面应用
|
||||||
- [x] 支持自部署的大语言模型:开箱即用 [RWKV-Runner](https://github.com/josStorer/RWKV-Runner) ,服务端部署 [LocalAI 项目](https://github.com/go-skynet/LocalAI) llama / gpt4all / rwkv / vicuna / koala / gpt4all-j / cerebras / falcon / dolly 等等
|
- [x] 支持自部署的大语言模型:开箱即用 [RWKV-Runner](https://github.com/josStorer/RWKV-Runner) ,服务端部署 [LocalAI 项目](https://github.com/go-skynet/LocalAI) llama / gpt4all / rwkv / vicuna / koala / gpt4all-j / cerebras / falcon / dolly 等等,或者使用 [api-for-open-llm](https://github.com/xusenlinzy/api-for-open-llm)
|
||||||
- [ ] 插件机制,支持联网搜索、计算器、调用其他平台 api [#165](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/165)
|
- [ ] 插件机制,支持联网搜索、计算器、调用其他平台 api [#165](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/165)
|
||||||
|
|
||||||
## 最新动态
|
## 最新动态
|
||||||
@@ -263,6 +263,10 @@ bash <(curl -s https://raw.githubusercontent.com/Yidadaa/ChatGPT-Next-Web/main/s
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
## Translation
|
||||||
|
|
||||||
|
If you want to add a new translation, read this [document](./docs/translation.md).
|
||||||
|
|
||||||
## Donation
|
## Donation
|
||||||
|
|
||||||
[Buy Me a Coffee](https://www.buymeacoffee.com/yidadaa)
|
[Buy Me a Coffee](https://www.buymeacoffee.com/yidadaa)
|
||||||
@@ -294,6 +298,7 @@ bash <(curl -s https://raw.githubusercontent.com/Yidadaa/ChatGPT-Next-Web/main/s
|
|||||||
[@Sha1rholder](https://github.com/Sha1rholder)
|
[@Sha1rholder](https://github.com/Sha1rholder)
|
||||||
[@AnsonHyq](https://github.com/AnsonHyq)
|
[@AnsonHyq](https://github.com/AnsonHyq)
|
||||||
[@synwith](https://github.com/synwith)
|
[@synwith](https://github.com/synwith)
|
||||||
|
[@piksonGit](https://github.com/piksonGit)
|
||||||
|
|
||||||
### Contributor
|
### Contributor
|
||||||
|
|
||||||
|
@@ -254,13 +254,15 @@ export class ChatGPTApi implements LLMApi {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const resJson = (await res.json()) as OpenAIListModelResponse;
|
const resJson = (await res.json()) as OpenAIListModelResponse;
|
||||||
const chatModels = resJson.data.filter((m) => m.id.startsWith("gpt-"));
|
const chatModels = resJson.data?.filter((m) => m.id.startsWith("gpt-"));
|
||||||
console.log("[Models]", chatModels);
|
console.log("[Models]", chatModels);
|
||||||
|
|
||||||
return chatModels.map((m) => ({
|
return (
|
||||||
name: m.id,
|
chatModels?.map((m) => ({
|
||||||
available: true,
|
name: m.id,
|
||||||
}));
|
available: true,
|
||||||
|
})) || []
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export { OpenaiPath };
|
export { OpenaiPath };
|
||||||
|
@@ -240,24 +240,39 @@
|
|||||||
&:last-child {
|
&:last-child {
|
||||||
animation: slide-in ease 0.3s;
|
animation: slide-in ease 0.3s;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
|
||||||
.chat-message-actions {
|
|
||||||
opacity: 1;
|
|
||||||
transform: translateY(0px);
|
|
||||||
max-width: 100%;
|
|
||||||
height: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat-message-action-date {
|
|
||||||
opacity: 0.2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-message-user {
|
.chat-message-user {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row-reverse;
|
flex-direction: row-reverse;
|
||||||
|
|
||||||
|
.chat-message-header {
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-message-header {
|
||||||
|
margin-top: 20px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.chat-message-actions {
|
||||||
|
display: flex;
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-size: 12px;
|
||||||
|
align-items: flex-end;
|
||||||
|
justify-content: space-between;
|
||||||
|
transition: all ease 0.3s;
|
||||||
|
transform: scale(0.9) translateY(5px);
|
||||||
|
margin: 0 10px;
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
|
||||||
|
.chat-input-actions {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-message-container {
|
.chat-message-container {
|
||||||
@@ -270,6 +285,12 @@
|
|||||||
.chat-message-edit {
|
.chat-message-edit {
|
||||||
opacity: 0.9;
|
opacity: 0.9;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.chat-message-actions {
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: all;
|
||||||
|
transform: scale(1) translateY(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -278,7 +299,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.chat-message-avatar {
|
.chat-message-avatar {
|
||||||
margin-top: 20px;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
.chat-message-edit {
|
.chat-message-edit {
|
||||||
@@ -318,27 +338,6 @@
|
|||||||
border: var(--border-in-light);
|
border: var(--border-in-light);
|
||||||
position: relative;
|
position: relative;
|
||||||
transition: all ease 0.3s;
|
transition: all ease 0.3s;
|
||||||
|
|
||||||
.chat-message-actions {
|
|
||||||
display: flex;
|
|
||||||
box-sizing: border-box;
|
|
||||||
font-size: 12px;
|
|
||||||
align-items: flex-end;
|
|
||||||
justify-content: space-between;
|
|
||||||
transition: all ease 0.3s 0.15s;
|
|
||||||
transform: translateX(-5px) scale(0.9) translateY(30px);
|
|
||||||
opacity: 0;
|
|
||||||
height: 0;
|
|
||||||
max-width: 0;
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
z-index: 2;
|
|
||||||
|
|
||||||
.chat-input-actions {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: nowrap;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-message-action-date {
|
.chat-message-action-date {
|
||||||
@@ -476,3 +475,21 @@
|
|||||||
bottom: 30px;
|
bottom: 30px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.chat-suggestions {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.chat-suggestion {
|
||||||
|
display: inline;
|
||||||
|
white-space: nowrap;
|
||||||
|
border-radius: 20px;
|
||||||
|
font-size: 12px;
|
||||||
|
background-color: var(--white);
|
||||||
|
color: var(--black);
|
||||||
|
border: var(--border-in-light);
|
||||||
|
padding: 4px 10px;
|
||||||
|
animation: slide-in ease 0.3s;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -606,6 +606,8 @@ export function Chat() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const [suggestions, setSuggestions] = useState<string[]>([]);
|
||||||
|
|
||||||
const doSubmit = (userInput: string) => {
|
const doSubmit = (userInput: string) => {
|
||||||
if (userInput.trim() === "") return;
|
if (userInput.trim() === "") return;
|
||||||
const matchCommand = chatCommands.match(userInput);
|
const matchCommand = chatCommands.match(userInput);
|
||||||
@@ -616,7 +618,13 @@ export function Chat() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
chatStore.onUserInput(userInput).then(() => setIsLoading(false));
|
setSuggestions([]);
|
||||||
|
chatStore.onUserInput(userInput).then(() => {
|
||||||
|
setIsLoading(false);
|
||||||
|
chatStore
|
||||||
|
.getSuggestions()
|
||||||
|
.then((suggestions) => setSuggestions(suggestions));
|
||||||
|
});
|
||||||
localStorage.setItem(LAST_INPUT_KEY, userInput);
|
localStorage.setItem(LAST_INPUT_KEY, userInput);
|
||||||
setUserInput("");
|
setUserInput("");
|
||||||
setPromptHints([]);
|
setPromptHints([]);
|
||||||
@@ -708,49 +716,46 @@ export function Chat() {
|
|||||||
let lastUserMessageIndex: number | null = null;
|
let lastUserMessageIndex: number | null = null;
|
||||||
for (let i = 0; i < session.messages.length; i += 1) {
|
for (let i = 0; i < session.messages.length; i += 1) {
|
||||||
const message = session.messages[i];
|
const message = session.messages[i];
|
||||||
if (message.id === messageId) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (message.role === "user") {
|
if (message.role === "user") {
|
||||||
lastUserMessageIndex = i;
|
lastUserMessageIndex = i;
|
||||||
}
|
}
|
||||||
|
if (message.id === messageId) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return lastUserMessageIndex;
|
return lastUserMessageIndex;
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteMessage = (userIndex: number) => {
|
const deleteMessage = (msgId?: number) => {
|
||||||
chatStore.updateCurrentSession((session) =>
|
chatStore.updateCurrentSession(
|
||||||
session.messages.splice(userIndex, 2),
|
(session) =>
|
||||||
|
(session.messages = session.messages.filter((m) => m.id !== msgId)),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onDelete = (botMessageId: number) => {
|
const onDelete = (msgId: number) => {
|
||||||
const userIndex = findLastUserIndex(botMessageId);
|
deleteMessage(msgId);
|
||||||
if (userIndex === null) return;
|
|
||||||
deleteMessage(userIndex);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const onResend = (botMessageId: number) => {
|
const onResend = (message: ChatMessage) => {
|
||||||
// find last user input message and resend
|
let content = message.content;
|
||||||
const userIndex = findLastUserIndex(botMessageId);
|
|
||||||
if (userIndex === null) return;
|
if (message.role === "assistant" && message.id) {
|
||||||
|
const userIndex = findLastUserIndex(message.id);
|
||||||
|
if (userIndex) {
|
||||||
|
content = session.messages.at(userIndex)?.content ?? content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
const content = session.messages[userIndex].content;
|
|
||||||
deleteMessage(userIndex);
|
|
||||||
chatStore.onUserInput(content).then(() => setIsLoading(false));
|
chatStore.onUserInput(content).then(() => setIsLoading(false));
|
||||||
inputRef.current?.focus();
|
inputRef.current?.focus();
|
||||||
};
|
};
|
||||||
|
|
||||||
const onPinMessage = (botMessage: ChatMessage) => {
|
const onPinMessage = (message: ChatMessage) => {
|
||||||
if (!botMessage.id) return;
|
|
||||||
const userMessageIndex = findLastUserIndex(botMessage.id);
|
|
||||||
if (userMessageIndex === null) return;
|
|
||||||
|
|
||||||
const userMessage = session.messages[userMessageIndex];
|
|
||||||
chatStore.updateCurrentSession((session) =>
|
chatStore.updateCurrentSession((session) =>
|
||||||
session.mask.context.push(userMessage, botMessage),
|
session.mask.context.push(message),
|
||||||
);
|
);
|
||||||
|
|
||||||
showToast(Locale.Chat.Actions.PinToastContent, {
|
showToast(Locale.Chat.Actions.PinToastContent, {
|
||||||
@@ -923,11 +928,11 @@ export function Chat() {
|
|||||||
>
|
>
|
||||||
{messages.map((message, i) => {
|
{messages.map((message, i) => {
|
||||||
const isUser = message.role === "user";
|
const isUser = message.role === "user";
|
||||||
|
const isContext = i < context.length;
|
||||||
const showActions =
|
const showActions =
|
||||||
!isUser &&
|
|
||||||
i > 0 &&
|
i > 0 &&
|
||||||
!(message.preview || message.content.length === 0) &&
|
!(message.preview || message.content.length === 0) &&
|
||||||
i >= context.length; // do not show actions for context prompts
|
!isContext;
|
||||||
const showTyping = message.preview || message.streaming;
|
const showTyping = message.preview || message.streaming;
|
||||||
|
|
||||||
const shouldShowClearContextDivider = i === clearContextIndex - 1;
|
const shouldShowClearContextDivider = i === clearContextIndex - 1;
|
||||||
@@ -941,64 +946,38 @@ export function Chat() {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div className={styles["chat-message-container"]}>
|
<div className={styles["chat-message-container"]}>
|
||||||
<div className={styles["chat-message-avatar"]}>
|
<div className={styles["chat-message-header"]}>
|
||||||
<div className={styles["chat-message-edit"]}>
|
<div className={styles["chat-message-avatar"]}>
|
||||||
<IconButton
|
<div className={styles["chat-message-edit"]}>
|
||||||
icon={<EditIcon />}
|
<IconButton
|
||||||
onClick={async () => {
|
icon={<EditIcon />}
|
||||||
const newMessage = await showPrompt(
|
onClick={async () => {
|
||||||
Locale.Chat.Actions.Edit,
|
const newMessage = await showPrompt(
|
||||||
message.content,
|
Locale.Chat.Actions.Edit,
|
||||||
10,
|
message.content,
|
||||||
);
|
10,
|
||||||
chatStore.updateCurrentSession((session) => {
|
|
||||||
const m = session.messages.find(
|
|
||||||
(m) => m.id === message.id,
|
|
||||||
);
|
);
|
||||||
if (m) {
|
chatStore.updateCurrentSession((session) => {
|
||||||
m.content = newMessage;
|
const m = session.messages.find(
|
||||||
}
|
(m) => m.id === message.id,
|
||||||
});
|
);
|
||||||
}}
|
if (m) {
|
||||||
></IconButton>
|
m.content = newMessage;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
></IconButton>
|
||||||
|
</div>
|
||||||
|
{isUser ? (
|
||||||
|
<Avatar avatar={config.avatar} />
|
||||||
|
) : (
|
||||||
|
<MaskAvatar mask={session.mask} />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
{isUser ? (
|
|
||||||
<Avatar avatar={config.avatar} />
|
|
||||||
) : (
|
|
||||||
<MaskAvatar mask={session.mask} />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{showTyping && (
|
|
||||||
<div className={styles["chat-message-status"]}>
|
|
||||||
{Locale.Chat.Typing}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className={styles["chat-message-item"]}>
|
|
||||||
<Markdown
|
|
||||||
content={message.content}
|
|
||||||
loading={
|
|
||||||
(message.preview || message.content.length === 0) &&
|
|
||||||
!isUser
|
|
||||||
}
|
|
||||||
onContextMenu={(e) => onRightClick(e, message)}
|
|
||||||
onDoubleClickCapture={() => {
|
|
||||||
if (!isMobileScreen) return;
|
|
||||||
setUserInput(message.content);
|
|
||||||
}}
|
|
||||||
fontSize={fontSize}
|
|
||||||
parentRef={scrollRef}
|
|
||||||
defaultShow={i >= messages.length - 10}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{showActions && (
|
{showActions && (
|
||||||
<div className={styles["chat-message-actions"]}>
|
<div className={styles["chat-message-actions"]}>
|
||||||
<div
|
<div className={styles["chat-input-actions"]}>
|
||||||
className={styles["chat-input-actions"]}
|
|
||||||
style={{
|
|
||||||
marginTop: 10,
|
|
||||||
marginBottom: 0,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{message.streaming ? (
|
{message.streaming ? (
|
||||||
<ChatAction
|
<ChatAction
|
||||||
text={Locale.Chat.Actions.Stop}
|
text={Locale.Chat.Actions.Stop}
|
||||||
@@ -1010,7 +989,7 @@ export function Chat() {
|
|||||||
<ChatAction
|
<ChatAction
|
||||||
text={Locale.Chat.Actions.Retry}
|
text={Locale.Chat.Actions.Retry}
|
||||||
icon={<ResetIcon />}
|
icon={<ResetIcon />}
|
||||||
onClick={() => onResend(message.id ?? i)}
|
onClick={() => onResend(message)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ChatAction
|
<ChatAction
|
||||||
@@ -1035,12 +1014,34 @@ export function Chat() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
{showTyping && (
|
||||||
{showActions && (
|
<div className={styles["chat-message-status"]}>
|
||||||
<div className={styles["chat-message-action-date"]}>
|
{Locale.Chat.Typing}
|
||||||
{message.date.toLocaleString()}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
<div className={styles["chat-message-item"]}>
|
||||||
|
<Markdown
|
||||||
|
content={message.content}
|
||||||
|
loading={
|
||||||
|
(message.preview || message.content.length === 0) &&
|
||||||
|
!isUser
|
||||||
|
}
|
||||||
|
onContextMenu={(e) => onRightClick(e, message)}
|
||||||
|
onDoubleClickCapture={() => {
|
||||||
|
if (!isMobileScreen) return;
|
||||||
|
setUserInput(message.content);
|
||||||
|
}}
|
||||||
|
fontSize={fontSize}
|
||||||
|
parentRef={scrollRef}
|
||||||
|
defaultShow={i >= messages.length - 10}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles["chat-message-action-date"]}>
|
||||||
|
{isContext
|
||||||
|
? Locale.Chat.IsContext
|
||||||
|
: message.date.toLocaleString()}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{shouldShowClearContextDivider && <ClearContextDivider />}
|
{shouldShowClearContextDivider && <ClearContextDivider />}
|
||||||
@@ -1068,6 +1069,25 @@ export function Chat() {
|
|||||||
onSearch("");
|
onSearch("");
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{suggestions.length > 0 && (
|
||||||
|
<div className={styles["chat-suggestions"]}>
|
||||||
|
{suggestions.map((s, i) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={i}
|
||||||
|
className={styles["chat-suggestion"] + " clickable"}
|
||||||
|
onClick={() => {
|
||||||
|
doSubmit(s);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{s}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className={styles["chat-input-panel-inner"]}>
|
<div className={styles["chat-input-panel-inner"]}>
|
||||||
<textarea
|
<textarea
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
|
@@ -28,6 +28,7 @@ import { useAppConfig } from "../store/config";
|
|||||||
import { AuthPage } from "./auth";
|
import { AuthPage } from "./auth";
|
||||||
import { getClientConfig } from "../config/client";
|
import { getClientConfig } from "../config/client";
|
||||||
import { api } from "../client/api";
|
import { api } from "../client/api";
|
||||||
|
import { useChatStore } from "../store";
|
||||||
|
|
||||||
export function Loading(props: { noLogo?: boolean }) {
|
export function Loading(props: { noLogo?: boolean }) {
|
||||||
return (
|
return (
|
||||||
@@ -114,6 +115,7 @@ function Screen() {
|
|||||||
const isHome = location.pathname === Path.Home;
|
const isHome = location.pathname === Path.Home;
|
||||||
const isAuth = location.pathname === Path.Auth;
|
const isAuth = location.pathname === Path.Auth;
|
||||||
const isMobileScreen = useMobileScreen();
|
const isMobileScreen = useMobileScreen();
|
||||||
|
const chat = useChatStore();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadAsyncGoogleFont();
|
loadAsyncGoogleFont();
|
||||||
|
@@ -282,6 +282,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
z-index: 999;
|
||||||
|
|
||||||
&-content {
|
&-content {
|
||||||
.list {
|
.list {
|
||||||
|
@@ -26,7 +26,7 @@ const cn = {
|
|||||||
Stop: "停止",
|
Stop: "停止",
|
||||||
Retry: "重试",
|
Retry: "重试",
|
||||||
Pin: "固定",
|
Pin: "固定",
|
||||||
PinToastContent: "已将 2 条对话固定至预设提示词",
|
PinToastContent: "已将 1 条对话固定至预设提示词",
|
||||||
PinToastAction: "查看",
|
PinToastAction: "查看",
|
||||||
Delete: "删除",
|
Delete: "删除",
|
||||||
Edit: "编辑",
|
Edit: "编辑",
|
||||||
@@ -66,6 +66,7 @@ const cn = {
|
|||||||
Reset: "清除记忆",
|
Reset: "清除记忆",
|
||||||
SaveAs: "存为面具",
|
SaveAs: "存为面具",
|
||||||
},
|
},
|
||||||
|
IsContext: "预设提示词",
|
||||||
},
|
},
|
||||||
Export: {
|
Export: {
|
||||||
Title: "分享聊天记录",
|
Title: "分享聊天记录",
|
||||||
|
@@ -28,7 +28,7 @@ const en: LocaleType = {
|
|||||||
Stop: "Stop",
|
Stop: "Stop",
|
||||||
Retry: "Retry",
|
Retry: "Retry",
|
||||||
Pin: "Pin",
|
Pin: "Pin",
|
||||||
PinToastContent: "Pinned 2 messages to contextual prompts",
|
PinToastContent: "Pinned 1 messages to contextual prompts",
|
||||||
PinToastAction: "View",
|
PinToastAction: "View",
|
||||||
Delete: "Delete",
|
Delete: "Delete",
|
||||||
Edit: "Edit",
|
Edit: "Edit",
|
||||||
@@ -68,6 +68,7 @@ const en: LocaleType = {
|
|||||||
Reset: "Reset to Default",
|
Reset: "Reset to Default",
|
||||||
SaveAs: "Save as Mask",
|
SaveAs: "Save as Mask",
|
||||||
},
|
},
|
||||||
|
IsContext: "Contextual Prompt",
|
||||||
},
|
},
|
||||||
Export: {
|
Export: {
|
||||||
Title: "Export Messages",
|
Title: "Export Messages",
|
||||||
|
@@ -103,6 +103,7 @@ interface ChatStore {
|
|||||||
resetSession: () => void;
|
resetSession: () => void;
|
||||||
getMessagesWithMemory: () => ChatMessage[];
|
getMessagesWithMemory: () => ChatMessage[];
|
||||||
getMemoryPrompt: () => ChatMessage;
|
getMemoryPrompt: () => ChatMessage;
|
||||||
|
getSuggestions: () => Promise<string[]>;
|
||||||
|
|
||||||
clearAllData: () => void;
|
clearAllData: () => void;
|
||||||
}
|
}
|
||||||
@@ -278,96 +279,99 @@ export const useChatStore = create<ChatStore>()(
|
|||||||
get().summarizeSession();
|
get().summarizeSession();
|
||||||
},
|
},
|
||||||
|
|
||||||
async onUserInput(content) {
|
onUserInput(content) {
|
||||||
const session = get().currentSession();
|
return new Promise((resolve) => {
|
||||||
const modelConfig = session.mask.modelConfig;
|
const session = get().currentSession();
|
||||||
|
const modelConfig = session.mask.modelConfig;
|
||||||
|
|
||||||
const userContent = fillTemplateWith(content, modelConfig);
|
const userContent = fillTemplateWith(content, modelConfig);
|
||||||
console.log("[User Input] after template: ", userContent);
|
console.log("[User Input] after template: ", userContent);
|
||||||
|
|
||||||
const userMessage: ChatMessage = createMessage({
|
const userMessage: ChatMessage = createMessage({
|
||||||
role: "user",
|
role: "user",
|
||||||
content: userContent,
|
content: userContent,
|
||||||
});
|
});
|
||||||
|
|
||||||
const botMessage: ChatMessage = createMessage({
|
const botMessage: ChatMessage = createMessage({
|
||||||
role: "assistant",
|
role: "assistant",
|
||||||
streaming: true,
|
streaming: true,
|
||||||
id: userMessage.id! + 1,
|
id: userMessage.id! + 1,
|
||||||
model: modelConfig.model,
|
model: modelConfig.model,
|
||||||
});
|
});
|
||||||
|
|
||||||
// get recent messages
|
// get recent messages
|
||||||
const recentMessages = get().getMessagesWithMemory();
|
const recentMessages = get().getMessagesWithMemory();
|
||||||
const sendMessages = recentMessages.concat(userMessage);
|
const sendMessages = recentMessages.concat(userMessage);
|
||||||
const sessionIndex = get().currentSessionIndex;
|
const sessionIndex = get().currentSessionIndex;
|
||||||
const messageIndex = get().currentSession().messages.length + 1;
|
const messageIndex = get().currentSession().messages.length + 1;
|
||||||
|
|
||||||
// save user's and bot's message
|
// save user's and bot's message
|
||||||
get().updateCurrentSession((session) => {
|
get().updateCurrentSession((session) => {
|
||||||
const savedUserMessage = {
|
const savedUserMessage = {
|
||||||
...userMessage,
|
...userMessage,
|
||||||
content,
|
content,
|
||||||
};
|
};
|
||||||
session.messages = session.messages.concat([
|
session.messages = session.messages.concat([
|
||||||
savedUserMessage,
|
savedUserMessage,
|
||||||
botMessage,
|
botMessage,
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
// make request
|
// make request
|
||||||
api.llm.chat({
|
api.llm.chat({
|
||||||
messages: sendMessages,
|
messages: sendMessages,
|
||||||
config: { ...modelConfig, stream: true },
|
config: { ...modelConfig, stream: true },
|
||||||
onUpdate(message) {
|
onUpdate(message) {
|
||||||
botMessage.streaming = true;
|
botMessage.streaming = true;
|
||||||
if (message) {
|
if (message) {
|
||||||
botMessage.content = message;
|
botMessage.content = message;
|
||||||
}
|
}
|
||||||
get().updateCurrentSession((session) => {
|
get().updateCurrentSession((session) => {
|
||||||
session.messages = session.messages.concat();
|
session.messages = session.messages.concat();
|
||||||
});
|
|
||||||
},
|
|
||||||
onFinish(message) {
|
|
||||||
botMessage.streaming = false;
|
|
||||||
if (message) {
|
|
||||||
botMessage.content = message;
|
|
||||||
get().onNewMessage(botMessage);
|
|
||||||
}
|
|
||||||
ChatControllerPool.remove(
|
|
||||||
sessionIndex,
|
|
||||||
botMessage.id ?? messageIndex,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
onError(error) {
|
|
||||||
const isAborted = error.message.includes("aborted");
|
|
||||||
botMessage.content =
|
|
||||||
"\n\n" +
|
|
||||||
prettyObject({
|
|
||||||
error: true,
|
|
||||||
message: error.message,
|
|
||||||
});
|
});
|
||||||
botMessage.streaming = false;
|
},
|
||||||
userMessage.isError = !isAborted;
|
onFinish(message) {
|
||||||
botMessage.isError = !isAborted;
|
botMessage.streaming = false;
|
||||||
get().updateCurrentSession((session) => {
|
if (message) {
|
||||||
session.messages = session.messages.concat();
|
botMessage.content = message;
|
||||||
});
|
get().onNewMessage(botMessage);
|
||||||
ChatControllerPool.remove(
|
resolve();
|
||||||
sessionIndex,
|
}
|
||||||
botMessage.id ?? messageIndex,
|
ChatControllerPool.remove(
|
||||||
);
|
sessionIndex,
|
||||||
|
botMessage.id ?? messageIndex,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
onError(error) {
|
||||||
|
const isAborted = error.message.includes("aborted");
|
||||||
|
botMessage.content =
|
||||||
|
"\n\n" +
|
||||||
|
prettyObject({
|
||||||
|
error: true,
|
||||||
|
message: error.message,
|
||||||
|
});
|
||||||
|
botMessage.streaming = false;
|
||||||
|
userMessage.isError = !isAborted;
|
||||||
|
botMessage.isError = !isAborted;
|
||||||
|
get().updateCurrentSession((session) => {
|
||||||
|
session.messages = session.messages.concat();
|
||||||
|
});
|
||||||
|
ChatControllerPool.remove(
|
||||||
|
sessionIndex,
|
||||||
|
botMessage.id ?? messageIndex,
|
||||||
|
);
|
||||||
|
|
||||||
console.error("[Chat] failed ", error);
|
console.error("[Chat] failed ", error);
|
||||||
},
|
},
|
||||||
onController(controller) {
|
onController(controller) {
|
||||||
// collect controller for stop/retry
|
// collect controller for stop/retry
|
||||||
ChatControllerPool.addController(
|
ChatControllerPool.addController(
|
||||||
sessionIndex,
|
sessionIndex,
|
||||||
botMessage.id ?? messageIndex,
|
botMessage.id ?? messageIndex,
|
||||||
controller,
|
controller,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -594,6 +598,62 @@ export const useChatStore = create<ChatStore>()(
|
|||||||
localStorage.clear();
|
localStorage.clear();
|
||||||
location.reload();
|
location.reload();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getSuggestions() {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
// get last bot messages
|
||||||
|
const messages = get().currentSession().messages;
|
||||||
|
let lastBotMessage: ChatMessage | undefined = undefined;
|
||||||
|
|
||||||
|
for (let i = messages.length - 1; i >= 0; i -= 1) {
|
||||||
|
if (messages[i].role === "assistant") {
|
||||||
|
lastBotMessage = messages[i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const botMsg = lastBotMessage?.content;
|
||||||
|
|
||||||
|
if (!lastBotMessage || !botMsg) return resolve([]);
|
||||||
|
|
||||||
|
const prompt = `
|
||||||
|
here is bot's reponse:
|
||||||
|
'''
|
||||||
|
${botMsg}
|
||||||
|
'''
|
||||||
|
|
||||||
|
according to bot's reponse,
|
||||||
|
- according to the bot's message, generate three short user input suggestions
|
||||||
|
- detect the bot's language and response in detected language
|
||||||
|
- no other words, just response in pure json format:
|
||||||
|
{questions: string[]}";
|
||||||
|
`;
|
||||||
|
|
||||||
|
api.llm.chat({
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
|
role: "user",
|
||||||
|
content: prompt,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
config: {
|
||||||
|
model: "gpt-3.5-turbo",
|
||||||
|
},
|
||||||
|
onFinish(msg) {
|
||||||
|
try {
|
||||||
|
const msgJson = JSON.parse(msg) as {
|
||||||
|
questions: string[];
|
||||||
|
};
|
||||||
|
if (Array.isArray(msgJson.questions)) {
|
||||||
|
resolve(msgJson.questions);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
console.error("[Suggestions] failed to parse: ", msg);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
name: StoreKey.Chat,
|
name: StoreKey.Chat,
|
||||||
|
@@ -139,19 +139,20 @@ export const useAppConfig = create<ChatConfigStore>()(
|
|||||||
name: StoreKey.Config,
|
name: StoreKey.Config,
|
||||||
version: 3.4,
|
version: 3.4,
|
||||||
migrate(persistedState, version) {
|
migrate(persistedState, version) {
|
||||||
if (version === 3.4) return persistedState as any;
|
|
||||||
|
|
||||||
const state = persistedState as ChatConfig;
|
const state = persistedState as ChatConfig;
|
||||||
state.modelConfig.sendMemory = true;
|
|
||||||
state.modelConfig.historyMessageCount = 4;
|
|
||||||
state.modelConfig.compressMessageLengthThreshold = 1000;
|
|
||||||
state.modelConfig.frequency_penalty = 0;
|
|
||||||
state.modelConfig.top_p = 1;
|
|
||||||
state.modelConfig.template = DEFAULT_INPUT_TEMPLATE;
|
|
||||||
state.dontShowMaskSplashScreen = false;
|
|
||||||
state.hideBuiltinMasks = false;
|
|
||||||
|
|
||||||
return state;
|
if (version < 3.4) {
|
||||||
|
state.modelConfig.sendMemory = true;
|
||||||
|
state.modelConfig.historyMessageCount = 4;
|
||||||
|
state.modelConfig.compressMessageLengthThreshold = 1000;
|
||||||
|
state.modelConfig.frequency_penalty = 0;
|
||||||
|
state.modelConfig.top_p = 1;
|
||||||
|
state.modelConfig.template = DEFAULT_INPUT_TEMPLATE;
|
||||||
|
state.dontShowMaskSplashScreen = false;
|
||||||
|
state.hideBuiltinMasks = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return state as any;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
12
docs/translation.md
Normal file
12
docs/translation.md
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# How to add a new translation?
|
||||||
|
|
||||||
|
Assume that we are adding a new translation for `new`.
|
||||||
|
|
||||||
|
1. copy `app/locales/en.ts` to `app/locales/new.ts`;
|
||||||
|
2. edit `new.ts`, change `const en: LocaleType = ` to `const new: PartialLocaleType`, and `export default new;`;
|
||||||
|
3. edit `app/locales/index.ts`:
|
||||||
|
4. `import new from './new.ts'`;
|
||||||
|
5. add `new` to `ALL_LANGS`;
|
||||||
|
6. add `new: "new lang"` to `ALL_LANG_OPTIONS`;
|
||||||
|
7. translate the strings in `new.ts`;
|
||||||
|
8. submit a pull request, and the author will merge it.
|
@@ -24,6 +24,7 @@
|
|||||||
"fuse.js": "^6.6.2",
|
"fuse.js": "^6.6.2",
|
||||||
"html-to-image": "^1.11.11",
|
"html-to-image": "^1.11.11",
|
||||||
"mermaid": "^10.2.3",
|
"mermaid": "^10.2.3",
|
||||||
|
"nanoid": "^4.0.2",
|
||||||
"next": "^13.4.6",
|
"next": "^13.4.6",
|
||||||
"node-fetch": "^3.3.1",
|
"node-fetch": "^3.3.1",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
|
@@ -9,7 +9,7 @@
|
|||||||
},
|
},
|
||||||
"package": {
|
"package": {
|
||||||
"productName": "ChatGPT Next Web",
|
"productName": "ChatGPT Next Web",
|
||||||
"version": "2.8.7"
|
"version": "2.8.8"
|
||||||
},
|
},
|
||||||
"tauri": {
|
"tauri": {
|
||||||
"allowlist": {
|
"allowlist": {
|
||||||
|
@@ -4639,6 +4639,11 @@ nanoid@^3.3.4:
|
|||||||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c"
|
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c"
|
||||||
integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==
|
integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==
|
||||||
|
|
||||||
|
nanoid@^4.0.2:
|
||||||
|
version "4.0.2"
|
||||||
|
resolved "https://registry.npmmirror.com/nanoid/-/nanoid-4.0.2.tgz#140b3c5003959adbebf521c170f282c5e7f9fb9e"
|
||||||
|
integrity sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw==
|
||||||
|
|
||||||
natural-compare@^1.4.0:
|
natural-compare@^1.4.0:
|
||||||
version "1.4.0"
|
version "1.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
|
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
|
||||||
|
Reference in New Issue
Block a user