mirror of
				https://github.com/Yidadaa/ChatGPT-Next-Web.git
				synced 2025-11-01 06:19:35 +08:00 
			
		
		
		
	Merge branch 'main' into chatGPT
This commit is contained in:
		| @@ -245,13 +245,17 @@ To control custom models, use `+` to add a custom model, use `-` to hide a model | |||||||
|  |  | ||||||
| User `-all` to disable all default models, `+all` to enable all default models. | User `-all` to disable all default models, `+all` to enable all default models. | ||||||
|  |  | ||||||
| ### `WHITE_WEBDEV_ENDPOINTS` (可选) | ### `WHITE_WEBDEV_ENDPOINTS` (optional) | ||||||
|  |  | ||||||
| You can use this option if you want to increase the number of webdav service addresses you are allowed to access, as required by the format: | You can use this option if you want to increase the number of webdav service addresses you are allowed to access, as required by the format: | ||||||
| - Each address must be a complete endpoint  | - Each address must be a complete endpoint  | ||||||
| > `https://xxxx/yyy` | > `https://xxxx/yyy` | ||||||
| - Multiple addresses are connected by ', ' | - Multiple addresses are connected by ', ' | ||||||
|  |  | ||||||
|  | ### `DEFAULT_INPUT_TEMPLATE` (optional) | ||||||
|  |  | ||||||
|  | Customize the default template used to initialize the User Input Preprocessing configuration item in Settings. | ||||||
|  |  | ||||||
| ## Requirements | ## Requirements | ||||||
|  |  | ||||||
| NodeJS >= 18, Docker >= 20 | NodeJS >= 18, Docker >= 20 | ||||||
|   | |||||||
| @@ -156,6 +156,9 @@ anthropic claude Api Url. | |||||||
|  |  | ||||||
| 用来控制模型列表,使用 `+` 增加一个模型,使用 `-` 来隐藏一个模型,使用 `模型名=展示名` 来自定义模型的展示名,用英文逗号隔开。 | 用来控制模型列表,使用 `+` 增加一个模型,使用 `-` 来隐藏一个模型,使用 `模型名=展示名` 来自定义模型的展示名,用英文逗号隔开。 | ||||||
|  |  | ||||||
|  | ### `DEFAULT_INPUT_TEMPLATE` (可选) | ||||||
|  | 自定义默认的 template,用于初始化『设置』中的『用户输入预处理』配置项 | ||||||
|  |  | ||||||
| ## 开发 | ## 开发 | ||||||
|  |  | ||||||
| 点击下方按钮,开始二次开发: | 点击下方按钮,开始二次开发: | ||||||
|   | |||||||
| @@ -59,9 +59,10 @@ import { | |||||||
|   getMessageTextContent, |   getMessageTextContent, | ||||||
|   getMessageImages, |   getMessageImages, | ||||||
|   isVisionModel, |   isVisionModel, | ||||||
|   compressImage, |  | ||||||
| } from "../utils"; | } from "../utils"; | ||||||
|  |  | ||||||
|  | import { compressImage } from "@/app/utils/chat"; | ||||||
|  |  | ||||||
| import dynamic from "next/dynamic"; | import dynamic from "next/dynamic"; | ||||||
|  |  | ||||||
| import { ChatControllerPool } from "../client/controller"; | import { ChatControllerPool } from "../client/controller"; | ||||||
|   | |||||||
| @@ -1,4 +1,5 @@ | |||||||
| import tauriConfig from "../../src-tauri/tauri.conf.json"; | import tauriConfig from "../../src-tauri/tauri.conf.json"; | ||||||
|  | import { DEFAULT_INPUT_TEMPLATE } from "../constant"; | ||||||
|  |  | ||||||
| export const getBuildConfig = () => { | export const getBuildConfig = () => { | ||||||
|   if (typeof process === "undefined") { |   if (typeof process === "undefined") { | ||||||
| @@ -38,6 +39,7 @@ export const getBuildConfig = () => { | |||||||
|     ...commitInfo, |     ...commitInfo, | ||||||
|     buildMode, |     buildMode, | ||||||
|     isApp, |     isApp, | ||||||
|  |     template: process.env.DEFAULT_INPUT_TEMPLATE ?? DEFAULT_INPUT_TEMPLATE, | ||||||
|   }; |   }; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -34,6 +34,9 @@ declare global { | |||||||
|  |  | ||||||
|       // google tag manager |       // google tag manager | ||||||
|       GTM_ID?: string; |       GTM_ID?: string; | ||||||
|  |  | ||||||
|  |       // custom template for preprocessing user input | ||||||
|  |       DEFAULT_INPUT_TEMPLATE?: string; | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -428,14 +428,13 @@ export const useChatStore = createPersistStore( | |||||||
|       getMemoryPrompt() { |       getMemoryPrompt() { | ||||||
|         const session = get().currentSession(); |         const session = get().currentSession(); | ||||||
|  |  | ||||||
|         return { |         if (session.memoryPrompt.length) { | ||||||
|           role: "system", |           return { | ||||||
|           content: |             role: "system", | ||||||
|             session.memoryPrompt.length > 0 |             content: Locale.Store.Prompt.History(session.memoryPrompt), | ||||||
|               ? Locale.Store.Prompt.History(session.memoryPrompt) |             date: "", | ||||||
|               : "", |           } as ChatMessage; | ||||||
|           date: "", |         } | ||||||
|         } as ChatMessage; |  | ||||||
|       }, |       }, | ||||||
|  |  | ||||||
|       getMessagesWithMemory() { |       getMessagesWithMemory() { | ||||||
| @@ -471,16 +470,15 @@ export const useChatStore = createPersistStore( | |||||||
|             systemPrompts.at(0)?.content ?? "empty", |             systemPrompts.at(0)?.content ?? "empty", | ||||||
|           ); |           ); | ||||||
|         } |         } | ||||||
|  |         const memoryPrompt = get().getMemoryPrompt(); | ||||||
|         // long term memory |         // long term memory | ||||||
|         const shouldSendLongTermMemory = |         const shouldSendLongTermMemory = | ||||||
|           modelConfig.sendMemory && |           modelConfig.sendMemory && | ||||||
|           session.memoryPrompt && |           session.memoryPrompt && | ||||||
|           session.memoryPrompt.length > 0 && |           session.memoryPrompt.length > 0 && | ||||||
|           session.lastSummarizeIndex > clearContextIndex; |           session.lastSummarizeIndex > clearContextIndex; | ||||||
|         const longTermMemoryPrompts = shouldSendLongTermMemory |         const longTermMemoryPrompts = | ||||||
|           ? [get().getMemoryPrompt()] |           shouldSendLongTermMemory && memoryPrompt ? [memoryPrompt] : []; | ||||||
|           : []; |  | ||||||
|         const longTermMemoryStartIndex = session.lastSummarizeIndex; |         const longTermMemoryStartIndex = session.lastSummarizeIndex; | ||||||
|  |  | ||||||
|         // short term memory |         // short term memory | ||||||
| @@ -605,9 +603,11 @@ export const useChatStore = createPersistStore( | |||||||
|             Math.max(0, n - modelConfig.historyMessageCount), |             Math.max(0, n - modelConfig.historyMessageCount), | ||||||
|           ); |           ); | ||||||
|         } |         } | ||||||
|  |         const memoryPrompt = get().getMemoryPrompt(); | ||||||
|         // add memory prompt |         if (memoryPrompt) { | ||||||
|         toBeSummarizedMsgs.unshift(get().getMemoryPrompt()); |           // add memory prompt | ||||||
|  |           toBeSummarizedMsgs.unshift(memoryPrompt); | ||||||
|  |         } | ||||||
|  |  | ||||||
|         const lastSummarizeIndex = session.messages.length; |         const lastSummarizeIndex = session.messages.length; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,5 +1,4 @@ | |||||||
| import { LLMModel } from "../client/api"; | import { LLMModel } from "../client/api"; | ||||||
| import { isMacOS } from "../utils"; |  | ||||||
| import { getClientConfig } from "../config/client"; | import { getClientConfig } from "../config/client"; | ||||||
| import { | import { | ||||||
|   DEFAULT_INPUT_TEMPLATE, |   DEFAULT_INPUT_TEMPLATE, | ||||||
| @@ -25,6 +24,8 @@ export enum Theme { | |||||||
|   Light = "light", |   Light = "light", | ||||||
| } | } | ||||||
|  |  | ||||||
|  | const config = getClientConfig(); | ||||||
|  |  | ||||||
| export const DEFAULT_CONFIG = { | export const DEFAULT_CONFIG = { | ||||||
|   lastUpdate: Date.now(), // timestamp, to merge state |   lastUpdate: Date.now(), // timestamp, to merge state | ||||||
|  |  | ||||||
| @@ -56,7 +57,7 @@ export const DEFAULT_CONFIG = { | |||||||
|     historyMessageCount: 4, |     historyMessageCount: 4, | ||||||
|     compressMessageLengthThreshold: 1000, |     compressMessageLengthThreshold: 1000, | ||||||
|     enableInjectSystemPrompts: true, |     enableInjectSystemPrompts: true, | ||||||
|     template: DEFAULT_INPUT_TEMPLATE, |     template: config?.template ?? DEFAULT_INPUT_TEMPLATE, | ||||||
|   }, |   }, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| @@ -132,7 +133,7 @@ export const useAppConfig = createPersistStore( | |||||||
|   }), |   }), | ||||||
|   { |   { | ||||||
|     name: StoreKey.Config, |     name: StoreKey.Config, | ||||||
|     version: 3.8, |     version: 3.9, | ||||||
|     migrate(persistedState, version) { |     migrate(persistedState, version) { | ||||||
|       const state = persistedState as ChatConfig; |       const state = persistedState as ChatConfig; | ||||||
|  |  | ||||||
| @@ -163,6 +164,13 @@ export const useAppConfig = createPersistStore( | |||||||
|         state.lastUpdate = Date.now(); |         state.lastUpdate = Date.now(); | ||||||
|       } |       } | ||||||
|  |  | ||||||
|  |       if (version < 3.9) { | ||||||
|  |         state.modelConfig.template = | ||||||
|  |           state.modelConfig.template !== DEFAULT_INPUT_TEMPLATE | ||||||
|  |             ? state.modelConfig.template | ||||||
|  |             : config?.template ?? DEFAULT_INPUT_TEMPLATE; | ||||||
|  |       } | ||||||
|  |  | ||||||
|       return state as any; |       return state as any; | ||||||
|     }, |     }, | ||||||
|   }, |   }, | ||||||
|   | |||||||
							
								
								
									
										42
									
								
								app/utils.ts
									
									
									
									
									
								
							
							
						
						
									
										42
									
								
								app/utils.ts
									
									
									
									
									
								
							| @@ -83,48 +83,6 @@ export async function downloadAs(text: string, filename: string) { | |||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| export function compressImage(file: File, maxSize: number): Promise<string> { |  | ||||||
|   return new Promise((resolve, reject) => { |  | ||||||
|     const reader = new FileReader(); |  | ||||||
|     reader.onload = (readerEvent: any) => { |  | ||||||
|       const image = new Image(); |  | ||||||
|       image.onload = () => { |  | ||||||
|         let canvas = document.createElement("canvas"); |  | ||||||
|         let ctx = canvas.getContext("2d"); |  | ||||||
|         let width = image.width; |  | ||||||
|         let height = image.height; |  | ||||||
|         let quality = 0.9; |  | ||||||
|         let dataUrl; |  | ||||||
|  |  | ||||||
|         do { |  | ||||||
|           canvas.width = width; |  | ||||||
|           canvas.height = height; |  | ||||||
|           ctx?.clearRect(0, 0, canvas.width, canvas.height); |  | ||||||
|           ctx?.drawImage(image, 0, 0, width, height); |  | ||||||
|           dataUrl = canvas.toDataURL("image/jpeg", quality); |  | ||||||
|  |  | ||||||
|           if (dataUrl.length < maxSize) break; |  | ||||||
|  |  | ||||||
|           if (quality > 0.5) { |  | ||||||
|             // Prioritize quality reduction |  | ||||||
|             quality -= 0.1; |  | ||||||
|           } else { |  | ||||||
|             // Then reduce the size |  | ||||||
|             width *= 0.9; |  | ||||||
|             height *= 0.9; |  | ||||||
|           } |  | ||||||
|         } while (dataUrl.length > maxSize); |  | ||||||
|  |  | ||||||
|         resolve(dataUrl); |  | ||||||
|       }; |  | ||||||
|       image.onerror = reject; |  | ||||||
|       image.src = readerEvent.target.result; |  | ||||||
|     }; |  | ||||||
|     reader.onerror = reject; |  | ||||||
|     reader.readAsDataURL(file); |  | ||||||
|   }); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| export function readFromFile() { | export function readFromFile() { | ||||||
|   return new Promise<string>((res, rej) => { |   return new Promise<string>((res, rej) => { | ||||||
|     const fileInput = document.createElement("input"); |     const fileInput = document.createElement("input"); | ||||||
|   | |||||||
							
								
								
									
										54
									
								
								app/utils/chat.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								app/utils/chat.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,54 @@ | |||||||
|  | import heic2any from "heic2any"; | ||||||
|  |  | ||||||
|  | export function compressImage(file: File, maxSize: number): Promise<string> { | ||||||
|  |   return new Promise((resolve, reject) => { | ||||||
|  |     const reader = new FileReader(); | ||||||
|  |     reader.onload = (readerEvent: any) => { | ||||||
|  |       const image = new Image(); | ||||||
|  |       image.onload = () => { | ||||||
|  |         let canvas = document.createElement("canvas"); | ||||||
|  |         let ctx = canvas.getContext("2d"); | ||||||
|  |         let width = image.width; | ||||||
|  |         let height = image.height; | ||||||
|  |         let quality = 0.9; | ||||||
|  |         let dataUrl; | ||||||
|  |  | ||||||
|  |         do { | ||||||
|  |           canvas.width = width; | ||||||
|  |           canvas.height = height; | ||||||
|  |           ctx?.clearRect(0, 0, canvas.width, canvas.height); | ||||||
|  |           ctx?.drawImage(image, 0, 0, width, height); | ||||||
|  |           dataUrl = canvas.toDataURL("image/jpeg", quality); | ||||||
|  |  | ||||||
|  |           if (dataUrl.length < maxSize) break; | ||||||
|  |  | ||||||
|  |           if (quality > 0.5) { | ||||||
|  |             // Prioritize quality reduction | ||||||
|  |             quality -= 0.1; | ||||||
|  |           } else { | ||||||
|  |             // Then reduce the size | ||||||
|  |             width *= 0.9; | ||||||
|  |             height *= 0.9; | ||||||
|  |           } | ||||||
|  |         } while (dataUrl.length > maxSize); | ||||||
|  |  | ||||||
|  |         resolve(dataUrl); | ||||||
|  |       }; | ||||||
|  |       image.onerror = reject; | ||||||
|  |       image.src = readerEvent.target.result; | ||||||
|  |     }; | ||||||
|  |     reader.onerror = reject; | ||||||
|  |  | ||||||
|  |     if (file.type.includes("heic")) { | ||||||
|  |       heic2any({ blob: file, toType: "image/jpeg" }) | ||||||
|  |         .then((blob) => { | ||||||
|  |           reader.readAsDataURL(blob as Blob); | ||||||
|  |         }) | ||||||
|  |         .catch((e) => { | ||||||
|  |           reject(e); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     reader.readAsDataURL(file); | ||||||
|  |   }); | ||||||
|  | } | ||||||
| @@ -93,14 +93,17 @@ export function createUpstashClient(store: SyncStore) { | |||||||
|       } |       } | ||||||
|  |  | ||||||
|       let url; |       let url; | ||||||
|       if (proxyUrl.length > 0 || proxyUrl === "/") { |       const pathPrefix = "/api/upstash/"; | ||||||
|         let u = new URL(proxyUrl + "/api/upstash/" + path); |  | ||||||
|  |       try { | ||||||
|  |         let u = new URL(proxyUrl + pathPrefix + path); | ||||||
|         // add query params |         // add query params | ||||||
|         u.searchParams.append("endpoint", config.endpoint); |         u.searchParams.append("endpoint", config.endpoint); | ||||||
|         url = u.toString(); |         url = u.toString(); | ||||||
|       } else { |       } catch (e) { | ||||||
|         url = "/api/upstash/" + path + "?endpoint=" + config.endpoint; |         url = pathPrefix + path + "?endpoint=" + config.endpoint; | ||||||
|       } |       } | ||||||
|  |  | ||||||
|       return url; |       return url; | ||||||
|     }, |     }, | ||||||
|   }; |   }; | ||||||
|   | |||||||
| @@ -24,6 +24,7 @@ | |||||||
|     "@vercel/speed-insights": "^1.0.2", |     "@vercel/speed-insights": "^1.0.2", | ||||||
|     "emoji-picker-react": "^4.9.2", |     "emoji-picker-react": "^4.9.2", | ||||||
|     "fuse.js": "^7.0.0", |     "fuse.js": "^7.0.0", | ||||||
|  |     "heic2any": "^0.0.4", | ||||||
|     "html-to-image": "^1.11.11", |     "html-to-image": "^1.11.11", | ||||||
|     "mermaid": "^10.6.1", |     "mermaid": "^10.6.1", | ||||||
|     "nanoid": "^5.0.3", |     "nanoid": "^5.0.3", | ||||||
|   | |||||||
| @@ -3669,6 +3669,11 @@ heap@^0.2.6: | |||||||
|   resolved "https://registry.npmmirror.com/heap/-/heap-0.2.7.tgz#1e6adf711d3f27ce35a81fe3b7bd576c2260a8fc" |   resolved "https://registry.npmmirror.com/heap/-/heap-0.2.7.tgz#1e6adf711d3f27ce35a81fe3b7bd576c2260a8fc" | ||||||
|   integrity sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg== |   integrity sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg== | ||||||
|  |  | ||||||
|  | heic2any@^0.0.4: | ||||||
|  |   version "0.0.4" | ||||||
|  |   resolved "https://registry.npmmirror.com/heic2any/-/heic2any-0.0.4.tgz#eddb8e6fec53c8583a6e18b65069bb5e8d19028a" | ||||||
|  |   integrity sha512-3lLnZiDELfabVH87htnRolZ2iehX9zwpRyGNz22GKXIu0fznlblf0/ftppXKNqS26dqFSeqfIBhAmAj/uSp0cA== | ||||||
|  |  | ||||||
| highlight.js@~11.7.0: | highlight.js@~11.7.0: | ||||||
|   version "11.7.0" |   version "11.7.0" | ||||||
|   resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-11.7.0.tgz#3ff0165bc843f8c9bce1fd89e2fda9143d24b11e" |   resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-11.7.0.tgz#3ff0165bc843f8c9bce1fd89e2fda9143d24b11e" | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user