mirror of
				https://github.com/Yidadaa/ChatGPT-Next-Web.git
				synced 2025-11-04 16:57:27 +08:00 
			
		
		
		
	Merge remote-tracking branch 'upstream/main'
This commit is contained in:
		@@ -110,6 +110,16 @@ export class ChatGPTApi implements LLMApi {
 | 
				
			|||||||
      // 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.
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // add max_tokens to vision model
 | 
				
			||||||
 | 
					    if (visionModel) {
 | 
				
			||||||
 | 
					      Object.defineProperty(requestPayload, "max_tokens", {
 | 
				
			||||||
 | 
					        enumerable: true,
 | 
				
			||||||
 | 
					        configurable: true,
 | 
				
			||||||
 | 
					        writable: true,
 | 
				
			||||||
 | 
					        value: modelConfig.max_tokens,
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    console.log("[Request] openai payload: ", requestPayload);
 | 
					    console.log("[Request] openai payload: ", requestPayload);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const shouldStream = !!options.config.stream;
 | 
					    const shouldStream = !!options.config.stream;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1104,6 +1104,47 @@ function _Chat() {
 | 
				
			|||||||
    };
 | 
					    };
 | 
				
			||||||
    // eslint-disable-next-line react-hooks/exhaustive-deps
 | 
					    // eslint-disable-next-line react-hooks/exhaustive-deps
 | 
				
			||||||
  }, []);
 | 
					  }, []);
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  const handlePaste = useCallback(
 | 
				
			||||||
 | 
					    async (event: React.ClipboardEvent<HTMLTextAreaElement>) => {
 | 
				
			||||||
 | 
					      const currentModel = chatStore.currentSession().mask.modelConfig.model;
 | 
				
			||||||
 | 
					      if(!isVisionModel(currentModel)){return;}
 | 
				
			||||||
 | 
					      const items = (event.clipboardData || window.clipboardData).items;
 | 
				
			||||||
 | 
					      for (const item of items) {
 | 
				
			||||||
 | 
					        if (item.kind === "file" && item.type.startsWith("image/")) {
 | 
				
			||||||
 | 
					          event.preventDefault();
 | 
				
			||||||
 | 
					          const file = item.getAsFile();
 | 
				
			||||||
 | 
					          if (file) {
 | 
				
			||||||
 | 
					            const images: string[] = [];
 | 
				
			||||||
 | 
					            images.push(...attachImages);
 | 
				
			||||||
 | 
					            images.push(
 | 
				
			||||||
 | 
					              ...(await new Promise<string[]>((res, rej) => {
 | 
				
			||||||
 | 
					                setUploading(true);
 | 
				
			||||||
 | 
					                const imagesData: string[] = [];
 | 
				
			||||||
 | 
					                compressImage(file, 256 * 1024)
 | 
				
			||||||
 | 
					                  .then((dataUrl) => {
 | 
				
			||||||
 | 
					                    imagesData.push(dataUrl);
 | 
				
			||||||
 | 
					                    setUploading(false);
 | 
				
			||||||
 | 
					                    res(imagesData);
 | 
				
			||||||
 | 
					                  })
 | 
				
			||||||
 | 
					                  .catch((e) => {
 | 
				
			||||||
 | 
					                    setUploading(false);
 | 
				
			||||||
 | 
					                    rej(e);
 | 
				
			||||||
 | 
					                  });
 | 
				
			||||||
 | 
					              })),
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					            const imagesLength = images.length;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (imagesLength > 3) {
 | 
				
			||||||
 | 
					              images.splice(3, imagesLength - 3);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            setAttachImages(images);
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    [attachImages, chatStore],
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async function uploadImage() {
 | 
					  async function uploadImage() {
 | 
				
			||||||
    const images: string[] = [];
 | 
					    const images: string[] = [];
 | 
				
			||||||
@@ -1453,6 +1494,7 @@ function _Chat() {
 | 
				
			|||||||
            onKeyDown={onInputKeyDown}
 | 
					            onKeyDown={onInputKeyDown}
 | 
				
			||||||
            onFocus={scrollToBottom}
 | 
					            onFocus={scrollToBottom}
 | 
				
			||||||
            onClick={scrollToBottom}
 | 
					            onClick={scrollToBottom}
 | 
				
			||||||
 | 
					            onPaste={handlePaste}
 | 
				
			||||||
            rows={inputRows}
 | 
					            rows={inputRows}
 | 
				
			||||||
            autoFocus={autoFocus}
 | 
					            autoFocus={autoFocus}
 | 
				
			||||||
            style={{
 | 
					            style={{
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -30,6 +30,9 @@ declare global {
 | 
				
			|||||||
      // google only
 | 
					      // google only
 | 
				
			||||||
      GOOGLE_API_KEY?: string;
 | 
					      GOOGLE_API_KEY?: string;
 | 
				
			||||||
      GOOGLE_URL?: string;
 | 
					      GOOGLE_URL?: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // google tag manager
 | 
				
			||||||
 | 
					      GTM_ID?: string;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,8 +7,8 @@ const tw = {
 | 
				
			|||||||
  WIP: "該功能仍在開發中……",
 | 
					  WIP: "該功能仍在開發中……",
 | 
				
			||||||
  Error: {
 | 
					  Error: {
 | 
				
			||||||
    Unauthorized: isApp
 | 
					    Unauthorized: isApp
 | 
				
			||||||
      ? "檢測到無效 API Key,請前往[設定](/#/settings)頁檢查 API Key 是否配置正確。"
 | 
					      ? "檢測到無效 API Key,請前往[設定](/#/settings)頁檢查 API Key 是否設定正確。"
 | 
				
			||||||
      : "訪問密碼不正確或為空,請前往[登錄](/#/auth)頁輸入正確的訪問密碼,或者在[設定](/#/settings)頁填入你自己的 OpenAI API Key。",
 | 
					      : "訪問密碼不正確或為空,請前往[登入](/#/auth)頁輸入正確的訪問密碼,或者在[設定](/#/settings)頁填入你自己的 OpenAI API Key。",
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Auth: {
 | 
					  Auth: {
 | 
				
			||||||
@@ -17,7 +17,7 @@ const tw = {
 | 
				
			|||||||
    SubTips: "或者輸入你的 OpenAI 或 Google API 密鑰",
 | 
					    SubTips: "或者輸入你的 OpenAI 或 Google API 密鑰",
 | 
				
			||||||
    Input: "在此處填寫訪問碼",
 | 
					    Input: "在此處填寫訪問碼",
 | 
				
			||||||
    Confirm: "確認",
 | 
					    Confirm: "確認",
 | 
				
			||||||
    Later: "稍後再說",
 | 
					    Later: "稍候再說",
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  ChatItem: {
 | 
					  ChatItem: {
 | 
				
			||||||
    ChatItemCount: (count: number) => `${count} 則對話`,
 | 
					    ChatItemCount: (count: number) => `${count} 則對話`,
 | 
				
			||||||
@@ -53,8 +53,8 @@ const tw = {
 | 
				
			|||||||
      del: "刪除聊天",
 | 
					      del: "刪除聊天",
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    InputActions: {
 | 
					    InputActions: {
 | 
				
			||||||
      Stop: "停止響應",
 | 
					      Stop: "停止回應",
 | 
				
			||||||
      ToBottom: "滾到最新",
 | 
					      ToBottom: "移至最新",
 | 
				
			||||||
      Theme: {
 | 
					      Theme: {
 | 
				
			||||||
        auto: "自動主題",
 | 
					        auto: "自動主題",
 | 
				
			||||||
        light: "亮色模式",
 | 
					        light: "亮色模式",
 | 
				
			||||||
@@ -107,7 +107,7 @@ const tw = {
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  Select: {
 | 
					  Select: {
 | 
				
			||||||
    Search: "搜索消息",
 | 
					    Search: "查詢消息",
 | 
				
			||||||
    All: "選取全部",
 | 
					    All: "選取全部",
 | 
				
			||||||
    Latest: "最近幾條",
 | 
					    Latest: "最近幾條",
 | 
				
			||||||
    Clear: "清除選中",
 | 
					    Clear: "清除選中",
 | 
				
			||||||
@@ -133,15 +133,15 @@ const tw = {
 | 
				
			|||||||
    Danger: {
 | 
					    Danger: {
 | 
				
			||||||
      Reset: {
 | 
					      Reset: {
 | 
				
			||||||
        Title: "重置所有設定",
 | 
					        Title: "重置所有設定",
 | 
				
			||||||
        SubTitle: "重置所有設定項回默認值",
 | 
					        SubTitle: "重置所有設定項回預設值",
 | 
				
			||||||
        Action: "立即重置",
 | 
					        Action: "立即重置",
 | 
				
			||||||
        Confirm: "確認重置所有設定?",
 | 
					        Confirm: "確認重置所有設定?",
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      Clear: {
 | 
					      Clear: {
 | 
				
			||||||
        Title: "清除所有數據",
 | 
					        Title: "清除所有資料",
 | 
				
			||||||
        SubTitle: "清除所有聊天、設定數據",
 | 
					        SubTitle: "清除所有聊天、設定資料",
 | 
				
			||||||
        Action: "立即清除",
 | 
					        Action: "立即清除",
 | 
				
			||||||
        Confirm: "確認清除所有聊天、設定數據?",
 | 
					        Confirm: "確認清除所有聊天、設定資料?",
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    Lang: {
 | 
					    Lang: {
 | 
				
			||||||
@@ -182,14 +182,14 @@ const tw = {
 | 
				
			|||||||
      SubTitle: "根據對話內容生成合適的標題",
 | 
					      SubTitle: "根據對話內容生成合適的標題",
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    Sync: {
 | 
					    Sync: {
 | 
				
			||||||
      CloudState: "雲端數據",
 | 
					      CloudState: "雲端資料",
 | 
				
			||||||
      NotSyncYet: "還沒有進行過同步",
 | 
					      NotSyncYet: "還沒有進行過同步",
 | 
				
			||||||
      Success: "同步成功",
 | 
					      Success: "同步成功",
 | 
				
			||||||
      Fail: "同步失敗",
 | 
					      Fail: "同步失敗",
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      Config: {
 | 
					      Config: {
 | 
				
			||||||
        Modal: {
 | 
					        Modal: {
 | 
				
			||||||
          Title: "配置雲端同步",
 | 
					          Title: "設定雲端同步",
 | 
				
			||||||
          Check: "檢查可用性",
 | 
					          Check: "檢查可用性",
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        SyncType: {
 | 
					        SyncType: {
 | 
				
			||||||
@@ -218,7 +218,7 @@ const tw = {
 | 
				
			|||||||
        },
 | 
					        },
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      LocalState: "本地數據",
 | 
					      LocalState: "本地資料",
 | 
				
			||||||
      Overview: (overview: any) => {
 | 
					      Overview: (overview: any) => {
 | 
				
			||||||
        return `${overview.chat} 次對話,${overview.message} 條消息,${overview.prompt} 條提示詞,${overview.mask} 個面具`;
 | 
					        return `${overview.chat} 次對話,${overview.message} 條消息,${overview.prompt} 條提示詞,${overview.mask} 個面具`;
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
@@ -385,7 +385,7 @@ const tw = {
 | 
				
			|||||||
    Edit: "前置上下文和歷史記憶",
 | 
					    Edit: "前置上下文和歷史記憶",
 | 
				
			||||||
    Add: "新增一條",
 | 
					    Add: "新增一條",
 | 
				
			||||||
    Clear: "上下文已清除",
 | 
					    Clear: "上下文已清除",
 | 
				
			||||||
    Revert: "恢覆上下文",
 | 
					    Revert: "恢復上下文",
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  Plugin: { Name: "外掛" },
 | 
					  Plugin: { Name: "外掛" },
 | 
				
			||||||
  FineTuned: { Sysmessage: "你是一個助手" },
 | 
					  FineTuned: { Sysmessage: "你是一個助手" },
 | 
				
			||||||
@@ -440,8 +440,8 @@ const tw = {
 | 
				
			|||||||
    More: "搜尋更多",
 | 
					    More: "搜尋更多",
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  URLCommand: {
 | 
					  URLCommand: {
 | 
				
			||||||
    Code: "檢測到鏈接中已經包含訪問碼,是否自動填入?",
 | 
					    Code: "檢測到連結中已經包含訪問碼,是否自動填入?",
 | 
				
			||||||
    Settings: "檢測到鏈接中包含了預制設置,是否自動填入?",
 | 
					    Settings: "檢測到連結中包含了預設設定,是否自動填入?",
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  UI: {
 | 
					  UI: {
 | 
				
			||||||
    Confirm: "確認",
 | 
					    Confirm: "確認",
 | 
				
			||||||
@@ -452,7 +452,7 @@ const tw = {
 | 
				
			|||||||
    Export: "導出",
 | 
					    Export: "導出",
 | 
				
			||||||
    Import: "導入",
 | 
					    Import: "導入",
 | 
				
			||||||
    Sync: "同步",
 | 
					    Sync: "同步",
 | 
				
			||||||
    Config: "配置",
 | 
					    Config: "設定",
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  Exporter: {
 | 
					  Exporter: {
 | 
				
			||||||
    Description: {
 | 
					    Description: {
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										11
									
								
								app/utils.ts
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								app/utils.ts
									
									
									
									
									
								
							@@ -9,8 +9,9 @@ export function trimTopic(topic: string) {
 | 
				
			|||||||
  // This will remove the specified punctuation from the end of the string
 | 
					  // This will remove the specified punctuation from the end of the string
 | 
				
			||||||
  // and also trim quotes from both the start and end if they exist.
 | 
					  // and also trim quotes from both the start and end if they exist.
 | 
				
			||||||
  return topic
 | 
					  return topic
 | 
				
			||||||
    .replace(/^["“”]+|["“”]+$/g, "")
 | 
					    // fix for gemini
 | 
				
			||||||
    .replace(/[,。!?”“"、,.!?]*$/, "");
 | 
					    .replace(/^["“”*]+|["“”*]+$/g, "")
 | 
				
			||||||
 | 
					    .replace(/[,。!?”“"、,.!?*]*$/, "");
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export async function copyToClipboard(text: string) {
 | 
					export async function copyToClipboard(text: string) {
 | 
				
			||||||
@@ -292,8 +293,8 @@ export function getMessageImages(message: RequestMessage): string[] {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
export function isVisionModel(model: string) {
 | 
					export function isVisionModel(model: string) {
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    model.startsWith("gpt-4-vision") ||
 | 
					    // model.startsWith("gpt-4-vision") ||
 | 
				
			||||||
    model.startsWith("gemini-pro-vision") ||
 | 
					    // model.startsWith("gemini-pro-vision") ||
 | 
				
			||||||
    !DEFAULT_MODELS.find((m) => m.name == model)
 | 
					    model.includes("vision")
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user