Merge remote-tracking branch 'upstream/main'

This commit is contained in:
Hk-Gosuto 2024-03-08 17:35:59 +08:00
parent 8fbcc653a0
commit f1c9c96082
7 changed files with 312 additions and 387 deletions

View File

@ -7,7 +7,7 @@
[![Web][Web-image]][web-url] [![Web][Web-image]][web-url]
[网页版](https://n3xt.chat) / [反馈](https://github.com/Hk-Gosuto/ChatGPT-Next-Web-LangChain/issues) [网页版](https://n3xt.chat) / [反馈](https://github.com/Hk-Gosuto/ChatGPT-Next-Web-LangChain/issues) / [Discord](https://discord.gg/zTwDFtSC)
[web-url]: https://n3xt.chat/ [web-url]: https://n3xt.chat/
[download-url]: https://github.com/Hk-Gosuto/ChatGPT-Next-Web-LangChain/releases [download-url]: https://github.com/Hk-Gosuto/ChatGPT-Next-Web-LangChain/releases
@ -41,7 +41,7 @@
- 支持 GPT-4V(视觉) 模型 - 支持 GPT-4V(视觉) 模型
- ~~需要配置对象存储服务,请参考 [对象存储服务配置指南](./docs/s3-oss.md) 配置~~ - ~~需要配置对象存储服务,请参考 [对象存储服务配置指南](./docs/s3-oss.md) 配置~~
- 已同步上游仓库视觉模型调用方式(压缩图片),不过这里还是会有撑爆 localstorage 的风险https://github.com/Hk-Gosuto/ChatGPT-Next-Web-LangChain/issues/77#issuecomment-1846410078续会兼容两种形式的图片本地存储,如果配置了对象存储会优先使用对象存储 - 已同步上游仓库视觉模型调用方式(压缩图片),这里还是会有撑爆 LocalStorage 的风险https://github.com/Hk-Gosuto/ChatGPT-Next-Web-LangChain/issues/77#issuecomment-1846410078面如果出现类似问题会再适配对象存储来存储图像
- 基于 [LangChain](https://github.com/hwchase17/langchainjs) 实现的插件功能,目前支持以下插件,未来会添加更多 - 基于 [LangChain](https://github.com/hwchase17/langchainjs) 实现的插件功能,目前支持以下插件,未来会添加更多
- 搜索(优先级:`GoogleCustomSearch > SerpAPI > BingSerpAPI > ChooseSearchEngine > DuckDuckGo` - 搜索(优先级:`GoogleCustomSearch > SerpAPI > BingSerpAPI > ChooseSearchEngine > DuckDuckGo`
@ -138,8 +138,6 @@
最新版本中已经移除上面两个模型。 最新版本中已经移除上面两个模型。
- [ ] ~~支持添加自定义插件~~
- [ ] 支持其他类型文件上传 https://github.com/Hk-Gosuto/ChatGPT-Next-Web-LangChain/issues/77 - [ ] 支持其他类型文件上传 https://github.com/Hk-Gosuto/ChatGPT-Next-Web-LangChain/issues/77
- [ ] 支持 Azure Storage https://github.com/Hk-Gosuto/ChatGPT-Next-Web-LangChain/issues/217 - [ ] 支持 Azure Storage https://github.com/Hk-Gosuto/ChatGPT-Next-Web-LangChain/issues/217
@ -301,12 +299,6 @@ docker run -d -p 3000:3000 \
<img src="https://contrib.rocks/image?repo=Hk-Gosuto/ChatGPT-Next-Web-LangChain" /> <img src="https://contrib.rocks/image?repo=Hk-Gosuto/ChatGPT-Next-Web-LangChain" />
</a> </a>
## 截图
![Settings](./docs/images/settings.png)
![More](./docs/images/more.png)
## 捐赠 ## 捐赠
[请项目原作者喝杯咖啡](https://www.buymeacoffee.com/yidadaa) [请项目原作者喝杯咖啡](https://www.buymeacoffee.com/yidadaa)

View File

@ -308,7 +308,7 @@ export class ChatGPTApi implements LLMApi {
async toolAgentChat(options: AgentChatOptions) { async toolAgentChat(options: AgentChatOptions) {
const messages = options.messages.map((v) => ({ const messages = options.messages.map((v) => ({
role: v.role, role: v.role,
content: v.content, content: getMessageTextContent(v),
})); }));
const modelConfig = { const modelConfig = {

View File

@ -648,39 +648,6 @@
bottom: 32px; bottom: 32px;
} }
.chat-input-image {
background-color: var(--primary);
color: white;
position: absolute;
right: 28px;
bottom: 78px;
display: flex;
align-items: flex-start;
border-radius: 4px;
overflow: hidden;
border: 1px solid rgba(0, 0, 0, 0.1);
}
.chat-input-image-close {
background-color: white;
fill: black;
border: none;
align-items: center;
justify-content: center;
display: flex;
margin: 0px;
padding: 0px;
width: 22px;
height: 48px;
}
.chat-input-image-close:hover {
background-color: #f3f3f3;
}
@media only screen and (max-width: 600px) { @media only screen and (max-width: 600px) {
.chat-input { .chat-input {
font-size: 16px; font-size: 16px;
@ -689,4 +656,4 @@
.chat-input-send { .chat-input-send {
bottom: 30px; bottom: 30px;
} }
} }

View File

@ -90,7 +90,6 @@ import {
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { import {
CHAT_PAGE_SIZE, CHAT_PAGE_SIZE,
LAST_INPUT_IMAGE_KEY,
LAST_INPUT_KEY, LAST_INPUT_KEY,
ModelProvider, ModelProvider,
Path, Path,
@ -105,7 +104,6 @@ import { prettyObject } from "../utils/format";
import { ExportMessageModal } from "./exporter"; import { ExportMessageModal } from "./exporter";
import { getClientConfig } from "../config/client"; import { getClientConfig } from "../config/client";
import { useAllModels } from "../utils/hooks"; import { useAllModels } from "../utils/hooks";
import Image from "next/image";
import { ClientApi } from "../client/api"; import { ClientApi } from "../client/api";
import { createTTSPlayer } from "../utils/audio"; import { createTTSPlayer } from "../utils/audio";
import { MultimodalContent } from "../client/api"; import { MultimodalContent } from "../client/api";
@ -455,7 +453,6 @@ export function ChatActions(props: {
showPromptModal: () => void; showPromptModal: () => void;
scrollToBottom: () => void; scrollToBottom: () => void;
showPromptHints: () => void; showPromptHints: () => void;
imageSelected: (img: any) => void;
hitBottom: boolean; hitBottom: boolean;
uploading: boolean; uploading: boolean;
}) { }) {
@ -717,7 +714,6 @@ function _Chat() {
const inputRef = useRef<HTMLTextAreaElement>(null); const inputRef = useRef<HTMLTextAreaElement>(null);
const [userInput, setUserInput] = useState(""); const [userInput, setUserInput] = useState("");
const [userImage, setUserImage] = useState<any>();
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const { submitKey, shouldSubmit } = useSubmitHandler(); const { submitKey, shouldSubmit } = useSubmitHandler();
const scrollRef = useRef<HTMLDivElement>(null); const scrollRef = useRef<HTMLDivElement>(null);
@ -803,7 +799,7 @@ function _Chat() {
} }
}; };
const doSubmit = (userInput: string, userImage?: any) => { const doSubmit = (userInput: string) => {
if (userInput.trim() === "") return; if (userInput.trim() === "") return;
const matchCommand = chatCommands.match(userInput); const matchCommand = chatCommands.match(userInput);
if (matchCommand.matched) { if (matchCommand.matched) {
@ -818,10 +814,8 @@ function _Chat() {
.then(() => setIsLoading(false)); .then(() => setIsLoading(false));
setAttachImages([]); setAttachImages([]);
localStorage.setItem(LAST_INPUT_KEY, userInput); localStorage.setItem(LAST_INPUT_KEY, userInput);
localStorage.setItem(LAST_INPUT_IMAGE_KEY, userImage);
setUserInput(""); setUserInput("");
setPromptHints([]); setPromptHints([]);
setUserImage(null);
if (!isMobileScreen) inputRef.current?.focus(); if (!isMobileScreen) inputRef.current?.focus();
setAutoScroll(true); setAutoScroll(true);
}; };
@ -886,12 +880,11 @@ function _Chat() {
!(e.metaKey || e.altKey || e.ctrlKey) !(e.metaKey || e.altKey || e.ctrlKey)
) { ) {
setUserInput(localStorage.getItem(LAST_INPUT_KEY) ?? ""); setUserInput(localStorage.getItem(LAST_INPUT_KEY) ?? "");
setUserImage(localStorage.getItem(LAST_INPUT_IMAGE_KEY));
e.preventDefault(); e.preventDefault();
return; return;
} }
if (shouldSubmit(e) && promptHints.length === 0) { if (shouldSubmit(e) && promptHints.length === 0) {
doSubmit(userInput, userImage); doSubmit(userInput);
e.preventDefault(); e.preventDefault();
} }
}; };
@ -1069,7 +1062,6 @@ function _Chat() {
isLoading, isLoading,
session.messages, session.messages,
userInput, userInput,
userImage?.fileUrl,
]); ]);
const [msgRenderIndex, _setMsgRenderIndex] = useState( const [msgRenderIndex, _setMsgRenderIndex] = useState(
@ -1283,8 +1275,6 @@ function _Chat() {
setAttachImages(images); setAttachImages(images);
} }
const textareaMinHeight = userImage ? 121 : 68;
return ( return (
<div className={styles.chat} key={session.id}> <div className={styles.chat} key={session.id}>
<div className="window-header" data-tauri-drag-region> <div className="window-header" data-tauri-drag-region>
@ -1574,19 +1564,6 @@ function _Chat() {
</div> </div>
)} )}
</div> </div>
{!isUser && message.model?.includes("vision") && (
<div
className={[
styles["chat-message-actions"],
styles["column-flex"],
].join(" ")}
>
<div
style={{ marginTop: "6px" }}
className={styles["chat-input-actions"]}
></div>
</div>
)}
<div className={styles["chat-message-action-date"]}> <div className={styles["chat-message-action-date"]}>
{isContext {isContext
? Locale.Chat.IsContext ? Locale.Chat.IsContext
@ -1622,9 +1599,6 @@ function _Chat() {
setUserInput("/"); setUserInput("/");
onSearch(""); onSearch("");
}} }}
imageSelected={(img: any) => {
setUserImage(img);
}}
/> />
<label <label
className={`${styles["chat-input-panel-inner"]} ${ className={`${styles["chat-input-panel-inner"]} ${
@ -1649,7 +1623,6 @@ function _Chat() {
autoFocus={autoFocus} autoFocus={autoFocus}
style={{ style={{
fontSize: config.fontSize, fontSize: config.fontSize,
minHeight: textareaMinHeight,
}} }}
/> />
{attachImages.length != 0 && ( {attachImages.length != 0 && (
@ -1680,7 +1653,7 @@ function _Chat() {
text={Locale.Chat.Send} text={Locale.Chat.Send}
className={styles["chat-input-send"]} className={styles["chat-input-send"]}
type="primary" type="primary"
onClick={() => doSubmit(userInput, userImage)} onClick={() => doSubmit(userInput)}
/> />
</label> </label>
</div> </div>

View File

@ -116,41 +116,38 @@ function escapeDollarNumber(text: string) {
return escapedText; return escapedText;
} }
function _MarkDownContent(props: { content: string; imageBase64?: string }) { function _MarkDownContent(props: { content: string }) {
const escapedContent = useMemo( const escapedContent = useMemo(
() => escapeDollarNumber(props.content), () => escapeDollarNumber(props.content),
[props.content], [props.content],
); );
return ( return (
<div style={{ fontSize: "inherit" }}> <ReactMarkdown
{props.imageBase64 && <img src={props.imageBase64} alt="" />} remarkPlugins={[RemarkMath, RemarkGfm, RemarkBreaks]}
<ReactMarkdown rehypePlugins={[
remarkPlugins={[RemarkMath, RemarkGfm, RemarkBreaks]} RehypeKatex,
rehypePlugins={[ [
RehypeKatex, RehypeHighlight,
[ {
RehypeHighlight, detect: false,
{ ignoreMissing: true,
detect: false,
ignoreMissing: true,
},
],
]}
components={{
pre: PreCode,
p: (pProps) => <p {...pProps} dir="auto" />,
a: (aProps) => {
const href = aProps.href || "";
const isInternal = /^\/#/i.test(href);
const target = isInternal ? "_self" : aProps.target ?? "_blank";
return <a {...aProps} target={target} />;
}, },
}} ],
> ]}
{escapedContent} components={{
</ReactMarkdown> pre: PreCode,
</div> p: (pProps) => <p {...pProps} dir="auto" />,
a: (aProps) => {
const href = aProps.href || "";
const isInternal = /^\/#/i.test(href);
const target = isInternal ? "_self" : aProps.target ?? "_blank";
return <a {...aProps} target={target} />;
},
}}
>
{escapedContent}
</ReactMarkdown>
); );
} }
@ -163,10 +160,10 @@ export function Markdown(
fontSize?: number; fontSize?: number;
parentRef?: RefObject<HTMLDivElement>; parentRef?: RefObject<HTMLDivElement>;
defaultShow?: boolean; defaultShow?: boolean;
imageBase64?: string;
} & React.DOMAttributes<HTMLDivElement>, } & React.DOMAttributes<HTMLDivElement>,
) { ) {
const mdRef = useRef<HTMLDivElement>(null); const mdRef = useRef<HTMLDivElement>(null);
return ( return (
<div <div
className="markdown-body" className="markdown-body"
@ -181,10 +178,7 @@ export function Markdown(
{props.loading ? ( {props.loading ? (
<LoadingIcon /> <LoadingIcon />
) : ( ) : (
<MarkdownContent <MarkdownContent content={props.content} />
content={props.content}
imageBase64={props.imageBase64}
/>
)} )}
</div> </div>
); );

View File

@ -58,7 +58,6 @@ export const NARROW_SIDEBAR_WIDTH = 100;
export const ACCESS_CODE_PREFIX = "nk-"; export const ACCESS_CODE_PREFIX = "nk-";
export const LAST_INPUT_KEY = "last-input"; export const LAST_INPUT_KEY = "last-input";
export const LAST_INPUT_IMAGE_KEY = "last-input-image";
export const UNFINISHED_INPUT = (id: string) => "unfinished-input-" + id; export const UNFINISHED_INPUT = (id: string) => "unfinished-input-" + id;
export const STORAGE_KEY = "chatgpt-next-web"; export const STORAGE_KEY = "chatgpt-next-web";

File diff suppressed because it is too large Load Diff