20 Commits

Author SHA1 Message Date
Yanyutin753
cad42d6b1b Update chat.tsx 2024-04-13 11:59:10 +08:00
Yanyutin753
8676585a11 Update chat.tsx 2024-04-13 11:25:53 +08:00
Yanyutin753
2da12d397e Update chat.tsx 2024-04-13 11:22:05 +08:00
Yanyutin753
24c8ca610c Update chat.tsx 2024-04-13 11:14:19 +08:00
Clivia
e7cc2a8c55 Update docker-compose.yml 2024-03-13 15:23:03 +08:00
Yanyutin753
344c532ca9 feat updateTypes 2024-03-11 22:56:06 +08:00
Yanyutin753
765e2eed6f Update model-config.tsx 2024-03-11 16:19:47 +08:00
Yanyutin753
d7690d843a feat support 2024-03-11 15:37:42 +08:00
Clivia
80fcf29353 Update Dockerfile 2024-03-11 14:52:19 +08:00
Clivia
5306ac1b14 修改coze开头的模型,使用url传image_url 2024-03-11 14:51:45 +08:00
Clivia
fbfc70e2bf feat 传url直接上传速度更快 2024-03-11 14:09:18 +08:00
Yanyutin753
a25a8ed812 feat moonshot in nextweb 2024-03-11 12:13:42 +08:00
Clivia
16bf0e5366 Update openai.ts 2024-03-11 00:28:02 +08:00
Clivia
0f9372717e feat update url 2024-03-11 00:18:24 +08:00
Clivia
e52eb5580b 优化上传链接 2024-03-10 23:47:21 +08:00
Clivia
3dddad317e feat moonshot-v1-vision 2024-03-10 23:39:35 +08:00
Clivia
da333f8f20 feat moonshot-vision 2024-03-10 23:10:22 +08:00
Yanyutin753
58d94818b4 feat base64 or url 2024-03-08 00:16:28 +08:00
Yanyutin753
7fd9653e7c feat send base64 or url 2024-03-07 23:33:32 +08:00
Clivia
79a863c636 feat "选择图片"改为"选择文件" 2024-03-03 15:09:02 +00:00
10 changed files with 161 additions and 86 deletions

View File

@@ -19,7 +19,7 @@ ENV OPENAI_API_KEY=""
ENV GOOGLE_API_KEY="" ENV GOOGLE_API_KEY=""
ENV CODE="" ENV CODE=""
ENV NEXT_PUBLIC_ENABLE_NODEJS_PLUGIN=1 ENV NEXT_PUBLIC_ENABLE_NODEJS_PLUGIN=1
ENV NEXT_PUBLIC_ENABLE_BASE64=0 ENV NEXT_PUBLIC_ENABLE_BASE64=1
WORKDIR /app WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules COPY --from=deps /app/node_modules ./node_modules
@@ -36,7 +36,7 @@ ENV PROXY_URL=""
ENV OPENAI_API_KEY="" ENV OPENAI_API_KEY=""
ENV GOOGLE_API_KEY="" ENV GOOGLE_API_KEY=""
ENV CODE="" ENV CODE=""
ENV NEXT_PUBLIC_ENABLE_BASE64=0 ENV NEXT_PUBLIC_ENABLE_BASE64=1
COPY --from=builder /app/public ./public COPY --from=builder /app/public ./public
COPY --from=builder /app/.next/standalone ./ COPY --from=builder /app/.next/standalone ./

View File

@@ -57,6 +57,7 @@ export interface RequestBody {
apiKey?: string; apiKey?: string;
maxIterations: number; maxIterations: number;
returnIntermediateSteps: boolean; returnIntermediateSteps: boolean;
updateTypes: boolean;
useTools: (undefined | string)[]; useTools: (undefined | string)[];
} }

View File

@@ -28,6 +28,7 @@ export interface LLMConfig {
stream?: boolean; stream?: boolean;
presence_penalty?: number; presence_penalty?: number;
frequency_penalty?: number; frequency_penalty?: number;
updateTypes?: boolean;
} }
export interface LLMAgentConfig { export interface LLMAgentConfig {

View File

@@ -79,6 +79,7 @@ export class ChatGPTApi implements LLMApi {
async chat(options: ChatOptions) { async chat(options: ChatOptions) {
const messages: any[] = []; const messages: any[] = [];
const accessStore = useAccessStore.getState();
const getImageBase64Data = async (url: string) => { const getImageBase64Data = async (url: string) => {
const response = await axios.get(url, { responseType: "arraybuffer" }); const response = await axios.get(url, { responseType: "arraybuffer" });
@@ -104,7 +105,7 @@ export class ChatGPTApi implements LLMApi {
}); });
if (v.image_url) { if (v.image_url) {
let image_url_data = ""; let image_url_data = "";
if (process.env.NEXT_PUBLIC_ENABLE_BASE64) { if (options.config.updateTypes && !options.config.model.includes("coze")) {
var base64Data = await getImageBase64Data(v.image_url); var base64Data = await getImageBase64Data(v.image_url);
let mimeType: string | null; let mimeType: string | null;
try { try {
@@ -128,8 +129,8 @@ export class ChatGPTApi implements LLMApi {
else { else {
const match = v.image_url.match(/\.(\w+)$/); const match = v.image_url.match(/\.(\w+)$/);
if (match && match[1]) { if (match && match[1]) {
const fileExtension = match[1].toLowerCase(); const fileExtension = match[1].toLowerCase();
v.image_url = v.image_url.replace(/\.\w+$/, '.' + fileExtension); v.image_url = v.image_url.replace(/\.\w+$/, '.' + fileExtension);
} }
var port = window.location.port ? ':' + window.location.port : ''; var port = window.location.port ? ':' + window.location.port : '';
var url = window.location.protocol + "//" + window.location.hostname + port; var url = window.location.protocol + "//" + window.location.hostname + port;
@@ -175,8 +176,16 @@ export class ChatGPTApi implements LLMApi {
// max_tokens: Math.max(modelConfig.max_tokens, 1024), // max_tokens: Math.max(modelConfig.max_tokens, 1024),
// Please do not ask me why not send max_tokens, no reason, this param is just shit, I dont want to explain anymore. // Please do not ask me why not send max_tokens, no reason, this param is just shit, I dont want to explain anymore.
}; };
// 用于隐藏传参变量
console.log("[Request] openai payload: ", requestPayload); const moonshotPayload = {
messages,
stream: options.config.stream,
model: modelConfig.model,
use_search:
modelConfig.model.includes("vision")
? false
: true,
}
const shouldStream = !!options.config.stream; const shouldStream = !!options.config.stream;
const controller = new AbortController(); const controller = new AbortController();
@@ -190,6 +199,13 @@ export class ChatGPTApi implements LLMApi {
signal: controller.signal, signal: controller.signal,
headers: getHeaders(), headers: getHeaders(),
}; };
if (modelConfig.model.includes("moonshot")) {
console.log("[Request] moonshot payload: ", moonshotPayload);
chatPayload.body = JSON.stringify(moonshotPayload)
}
else {
console.log("[Request] openai payload: ", requestPayload);
}
// make a fetch request // make a fetch request
const requestTimeoutId = setTimeout( const requestTimeoutId = setTimeout(
@@ -347,6 +363,7 @@ export class ChatGPTApi implements LLMApi {
baseUrl: baseUrl, baseUrl: baseUrl,
maxIterations: options.agentConfig.maxIterations, maxIterations: options.agentConfig.maxIterations,
returnIntermediateSteps: options.agentConfig.returnIntermediateSteps, returnIntermediateSteps: options.agentConfig.returnIntermediateSteps,
updateTypes: modelConfig.updateTypes,
useTools: options.agentConfig.useTools, useTools: options.agentConfig.useTools,
}; };

View File

@@ -373,17 +373,17 @@ function ChatAction(props: {
style={ style={
props.icon && !props.loding props.icon && !props.loding
? ({ ? ({
"--icon-width": `${width.icon}px`, "--icon-width": `${width.icon}px`,
"--full-width": `${width.full}px`, "--full-width": `${width.full}px`,
...props.style, ...props.style,
} as React.CSSProperties) } as React.CSSProperties)
: props.loding : props.loding
? ({ ? ({
"--icon-width": `30px`, "--icon-width": `30px`,
"--full-width": `30px`, "--full-width": `30px`,
...props.style, ...props.style,
} as React.CSSProperties) } as React.CSSProperties)
: props.style : props.style
} }
> >
{props.icon ? ( {props.icon ? (
@@ -476,22 +476,38 @@ export function ChatActions(props: {
} }
const onImageSelected = async (e: any) => { const onImageSelected = async (e: any) => {
const file = e.target.files[0]; const files = e.target.files;
if (!file) return; if (!files.length) return;
const api = new ClientApi(); const api = new ClientApi();
setUploadLoading(true);
const uploadFile = await api.file // Here we create a Promise for each file upload,
.upload(file) // then use Promise.all to wait for all of them to complete.
.catch((e) => { const uploadPromises = Array.from(files).map(async (file) => {
console.error("[Upload]", e); setUploadLoading(true);
showToast(prettyObject(e)); const uploadFile = await api.file
}) .upload(file)
.finally(() => setUploadLoading(false)); .catch((e) => {
props.imageSelected({ console.error("[Upload]", e);
fileName: uploadFile.fileName, showToast(prettyObject(e));
fileUrl: uploadFile.filePath, // return null if upload fails
return null;
})
.finally(() => setUploadLoading(false));
if (uploadFile) {
// use a callback or event to inform about the new file
props.imageSelected({
fileName: uploadFile.fileName,
fileUrl: uploadFile.filePath,
});
}
// Clear file input on each iteration.
e.target.value = null;
}); });
e.target.value = null;
// Wait for all uploads to finish.
await Promise.all(uploadPromises);
}; };
// switch model // switch model
@@ -518,7 +534,7 @@ export function ChatActions(props: {
const items = event.clipboardData?.items || []; const items = event.clipboardData?.items || [];
const api = new ClientApi(); const api = new ClientApi();
for (let i = 0; i < items.length; i++) { for (let i = 0; i < items.length; i++) {
if (items[i].type.indexOf("image") === -1) continue; // if (items[i].type.indexOf("image") === -1) continue;
const file = items[i].getAsFile(); const file = items[i].getAsFile();
if (file !== null) { if (file !== null) {
setUploadLoading(true); setUploadLoading(true);
@@ -623,12 +639,13 @@ export function ChatActions(props: {
{(currentModel.includes("vision") || currentModel.includes("gizmo")) && ( {(currentModel.includes("vision") || currentModel.includes("gizmo")) && (
<ChatAction <ChatAction
onClick={selectImage} onClick={selectImage}
text="选择图片" text="选择文件"
loding={uploadLoading} loding={uploadLoading}
icon={<UploadIcon />} icon={<UploadIcon />}
innerNode={ innerNode={
<input <input
type="file" type="file"
multiple
accept="*/*" accept="*/*"
id="chat-image-file-select-upload" id="chat-image-file-select-upload"
style={{ display: "none" }} style={{ display: "none" }}
@@ -826,7 +843,7 @@ function _Chat() {
} }
}; };
const doSubmit = (userInput: string, userImage?: any) => { const doSubmit = (userInput: string, userImages?: any[]) => {
if (userInput.trim() === "") return; if (userInput.trim() === "") return;
const matchCommand = chatCommands.match(userInput); const matchCommand = chatCommands.match(userInput);
if (matchCommand.matched) { if (matchCommand.matched) {
@@ -836,11 +853,16 @@ function _Chat() {
return; return;
} }
setIsLoading(true); setIsLoading(true);
const userImageUrls = userImages?.map(image => image.fileUrl) || [];
chatStore chatStore
.onUserInput(userInput, userImage?.fileUrl) .onUserInput(userInput, ...userImageUrls)
.then(() => setIsLoading(false)); .then(() => setIsLoading(false));
localStorage.setItem(LAST_INPUT_KEY, userInput); localStorage.setItem(LAST_INPUT_KEY, userInput);
localStorage.setItem(LAST_INPUT_IMAGE_KEY, userImage); localStorage.setItem(LAST_INPUT_IMAGE_KEY, JSON.stringify(userImages));
setUserInput(""); setUserInput("");
setPromptHints([]); setPromptHints([]);
setUserImage(null); setUserImage(null);
@@ -908,7 +930,13 @@ 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)); if(localStorage.getItem(LAST_INPUT_IMAGE_KEY) !== null){
let retrievedUserImages = JSON.parse(localStorage.getItem(LAST_INPUT_IMAGE_KEY)!);
setUserImage(retrievedUserImages);
}
else{
setUserImage(null);
}
e.preventDefault(); e.preventDefault();
return; return;
} }
@@ -1031,28 +1059,28 @@ function _Chat() {
.concat( .concat(
isLoading isLoading
? [ ? [
{ {
...createMessage({ ...createMessage({
role: "assistant", role: "assistant",
content: "……", content: "……",
}), }),
preview: true, preview: true,
}, },
] ]
: [], : [],
) )
.concat( .concat(
userInput.length > 0 && config.sendPreviewBubble userInput.length > 0 && config.sendPreviewBubble
? [ ? [
{ {
...createMessage({ ...createMessage({
role: "user", role: "user",
content: userInput, content: userInput,
image_url: userImage?.fileUrl, image_url: userImage?.fileUrl,
}), }),
preview: true, preview: true,
}, },
] ]
: [], : [],
); );
}, [ }, [
@@ -1149,7 +1177,7 @@ function _Chat() {
if (payload.key || payload.url) { if (payload.key || payload.url) {
showConfirm( showConfirm(
Locale.URLCommand.Settings + Locale.URLCommand.Settings +
`\n${JSON.stringify(payload, null, 4)}`, `\n${JSON.stringify(payload, null, 4)}`,
).then((res) => { ).then((res) => {
if (!res) return; if (!res) return;
if (payload.key) { if (payload.key) {
@@ -1457,7 +1485,7 @@ function _Chat() {
onSearch(""); onSearch("");
}} }}
imageSelected={(img: any) => { imageSelected={(img: any) => {
setUserImage(img); setUserImage([...userImage, img]); // Add new image to the array
}} }}
/> />
<div className={styles["chat-input-panel-inner"]}> <div className={styles["chat-input-panel-inner"]}>
@@ -1477,8 +1505,8 @@ function _Chat() {
minHeight: textareaMinHeight, minHeight: textareaMinHeight,
}} }}
/> />
{userImage && ( {userImages.map((userImage, index) => (
<div className={styles["chat-input-image"]}> <div key={index} className={styles["chat-input-image"]}>
<div <div
style={{ position: "relative", width: "48px", height: "48px" }} style={{ position: "relative", width: "48px", height: "48px" }}
> >
@@ -1494,21 +1522,21 @@ function _Chat() {
</div> </div>
<button <button
className={styles["chat-input-image-close"]} className={styles["chat-input-image-close"]}
id="chat-input-image-close" id={`chat-input-image-close-${index}`}
onClick={() => { onClick={() => {
setUserImage(null); setUserImage(userImage.filter((_: any, i: any) => i !== index)); // Remove image
}} }}
> >
<CloseIcon /> <CloseIcon />
</button> </button>
</div> </div>
)} ))}
<IconButton <IconButton
icon={<SendWhiteIcon />} icon={<SendWhiteIcon />}
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, userImages)} // Pass the new array
/> />
</div> </div>
</div> </div>
@@ -1525,6 +1553,7 @@ function _Chat() {
/> />
)} )}
</div> </div>
); );
} }

View File

@@ -19,9 +19,9 @@ export function ModelConfigList(props: {
onChange={(e) => { onChange={(e) => {
props.updateConfig( props.updateConfig(
(config) => (config) =>
(config.model = ModalConfigValidator.model( (config.model = ModalConfigValidator.model(
e.currentTarget.value, e.currentTarget.value,
)), )),
); );
}} }}
> >
@@ -46,9 +46,9 @@ export function ModelConfigList(props: {
onChange={(e) => { onChange={(e) => {
props.updateConfig( props.updateConfig(
(config) => (config) =>
(config.temperature = ModalConfigValidator.temperature( (config.temperature = ModalConfigValidator.temperature(
e.currentTarget.valueAsNumber, e.currentTarget.valueAsNumber,
)), )),
); );
}} }}
></InputRange> ></InputRange>
@@ -65,9 +65,9 @@ export function ModelConfigList(props: {
onChange={(e) => { onChange={(e) => {
props.updateConfig( props.updateConfig(
(config) => (config) =>
(config.top_p = ModalConfigValidator.top_p( (config.top_p = ModalConfigValidator.top_p(
e.currentTarget.valueAsNumber, e.currentTarget.valueAsNumber,
)), )),
); );
}} }}
></InputRange> ></InputRange>
@@ -84,9 +84,9 @@ export function ModelConfigList(props: {
onChange={(e) => onChange={(e) =>
props.updateConfig( props.updateConfig(
(config) => (config) =>
(config.max_tokens = ModalConfigValidator.max_tokens( (config.max_tokens = ModalConfigValidator.max_tokens(
e.currentTarget.valueAsNumber, e.currentTarget.valueAsNumber,
)), )),
) )
} }
></input> ></input>
@@ -106,10 +106,10 @@ export function ModelConfigList(props: {
onChange={(e) => { onChange={(e) => {
props.updateConfig( props.updateConfig(
(config) => (config) =>
(config.presence_penalty = (config.presence_penalty =
ModalConfigValidator.presence_penalty( ModalConfigValidator.presence_penalty(
e.currentTarget.valueAsNumber, e.currentTarget.valueAsNumber,
)), )),
); );
}} }}
></InputRange> ></InputRange>
@@ -127,10 +127,10 @@ export function ModelConfigList(props: {
onChange={(e) => { onChange={(e) => {
props.updateConfig( props.updateConfig(
(config) => (config) =>
(config.frequency_penalty = (config.frequency_penalty =
ModalConfigValidator.frequency_penalty( ModalConfigValidator.frequency_penalty(
e.currentTarget.valueAsNumber, e.currentTarget.valueAsNumber,
)), )),
); );
}} }}
></InputRange> ></InputRange>
@@ -146,8 +146,8 @@ export function ModelConfigList(props: {
onChange={(e) => onChange={(e) =>
props.updateConfig( props.updateConfig(
(config) => (config) =>
(config.enableInjectSystemPrompts = (config.enableInjectSystemPrompts =
e.currentTarget.checked), e.currentTarget.checked),
) )
} }
></input> ></input>
@@ -199,8 +199,8 @@ export function ModelConfigList(props: {
onChange={(e) => onChange={(e) =>
props.updateConfig( props.updateConfig(
(config) => (config) =>
(config.compressMessageLengthThreshold = (config.compressMessageLengthThreshold =
e.currentTarget.valueAsNumber), e.currentTarget.valueAsNumber),
) )
} }
></input> ></input>
@@ -216,6 +216,25 @@ export function ModelConfigList(props: {
} }
></input> ></input>
</ListItem> </ListItem>
<ListItem
title={Locale.Settings.UpdateType.Title}
subTitle={Locale.Settings.UpdateType.SubTitle}
>
<input
type="checkbox"
checked={
props.modelConfig.updateTypes
}
onChange={(e) =>
props.updateConfig(
(config) =>
(config.updateTypes =
e.currentTarget.checked),
)
}
></input>
</ListItem>
</> </>
); );
} }

View File

@@ -359,6 +359,10 @@ const cn = {
Title: "频率惩罚度 (frequency_penalty)", Title: "频率惩罚度 (frequency_penalty)",
SubTitle: "值越大,越有可能降低重复字词", SubTitle: "值越大,越有可能降低重复字词",
}, },
UpdateType: {
Title: "上传类型",
SubTitle: "是否上传Base64格式消息",
},
Plugin: { Plugin: {
Enable: { Enable: {
Title: "启用插件", Title: "启用插件",

View File

@@ -365,6 +365,10 @@ const en: LocaleType = {
SubTitle: SubTitle:
"A larger value decreasing the likelihood to repeat the same line", "A larger value decreasing the likelihood to repeat the same line",
}, },
UpdateType: {
Title: "Upload type",
SubTitle: "Upload Base64 format message",
},
Plugin: { Plugin: {
Enable: { Enable: {
Title: "Enable Plugin", Title: "Enable Plugin",

View File

@@ -56,6 +56,7 @@ export const DEFAULT_CONFIG = {
historyMessageCount: 4, historyMessageCount: 4,
compressMessageLengthThreshold: 1000, compressMessageLengthThreshold: 1000,
enableInjectSystemPrompts: true, enableInjectSystemPrompts: true,
updateTypes: true,
template: DEFAULT_INPUT_TEMPLATE, template: DEFAULT_INPUT_TEMPLATE,
}, },
@@ -169,7 +170,6 @@ export const useAppConfig = createPersistStore(
if (version < 3.8) { if (version < 3.8) {
state.lastUpdate = Date.now(); state.lastUpdate = Date.now();
} }
return state as any; return state as any;
}, },
}, },

View File

@@ -3,7 +3,7 @@ services:
chatgpt-next-web: chatgpt-next-web:
profiles: [ "no-proxy" ] profiles: [ "no-proxy" ]
container_name: chatgpt-next-web container_name: chatgpt-next-web
image: gosuto/chatgpt-next-web-langchain image: yangclivia/chatgpt-next-web-langchain
volumes: volumes:
- next_chat_upload:/app/uploads - next_chat_upload:/app/uploads
ports: ports:
@@ -23,7 +23,7 @@ services:
chatgpt-next-web-proxy: chatgpt-next-web-proxy:
profiles: [ "proxy" ] profiles: [ "proxy" ]
container_name: chatgpt-next-web-proxy container_name: chatgpt-next-web-proxy
image: gosuto/chatgpt-next-web-langchain image: yangclivia/chatgpt-next-web-langchain
volumes: volumes:
- next_chat_upload:/app/uploads - next_chat_upload:/app/uploads
ports: ports:
@@ -42,4 +42,4 @@ services:
- OPENAI_SB=$OPENAI_SB - OPENAI_SB=$OPENAI_SB
volumes: volumes:
next_chat_upload: next_chat_upload: