mirror of
				https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web.git
				synced 2025-11-01 14:53:43 +08:00 
			
		
		
		
	Compare commits
	
		
			34 Commits
		
	
	
		
			v2.16.0
			...
			50d191482b
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 50d191482b | ||
|  | 3809375694 | ||
|  | 1b0de25986 | ||
|  | 865c45dd29 | ||
|  | 1f5d8e6d9c | ||
|  | c9ef6d58ed | ||
|  | 2d7229d2b8 | ||
|  | 11b37c15bd | ||
|  | 1d0038f17d | ||
|  | 619fa519c0 | ||
|  | 48469bd8ca | ||
|  | 5a5e887f2b | ||
|  | b6f5d75656 | ||
|  | 0d41a17ef6 | ||
|  | f7cde17919 | ||
|  | 570cbb34b6 | ||
|  | 7aa9ae0a3e | ||
|  | 2d4180f5be | ||
|  | 9f0182b55e | ||
|  | ad6666eeaf | ||
|  | a2c4e468a0 | ||
|  | 2167076652 | ||
|  | e123076250 | ||
|  | ebcb4db245 | ||
|  | 0a25a1a8cb | ||
|  | f3154b20a5 | ||
|  | b709ee3983 | ||
|  | f5f3ce94f6 | ||
|  | 2b5f600308 | ||
|  | 812c1c770c | ||
|  | 54e3753d93 | ||
|  | b966107117 | ||
|  | 90827fc593 | ||
|  | 008e339b6d | 
							
								
								
									
										18
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								README.md
									
									
									
									
									
								
							| @@ -7,7 +7,7 @@ | ||||
|  | ||||
|  | ||||
|  | ||||
| <h1 align="center">NextChat (ChatGPT Next Web)</h1> | ||||
| <h1 align="center">NextChat</h1> | ||||
|  | ||||
| English / [简体中文](./README_CN.md) | ||||
|  | ||||
| @@ -22,8 +22,7 @@ English / [简体中文](./README_CN.md) | ||||
| [![MacOS][MacOS-image]][download-url] | ||||
| [![Linux][Linux-image]][download-url] | ||||
|  | ||||
| [NextChatAI](https://nextchat.dev/chat?utm_source=readme) / [Web App Demo](https://app.nextchat.dev) / [Desktop App](https://github.com/Yidadaa/ChatGPT-Next-Web/releases)  | ||||
| [NextChatAI](https://nextchat.club?utm_source=readme) / [Web App Demo](https://app.nextchat.dev) / [Desktop App](https://github.com/Yidadaa/ChatGPT-Next-Web/releases) / [Discord](https://discord.gg/YCkeafCafC) / [Enterprise Edition](#enterprise-edition) / [Twitter](https://twitter.com/NextChatDev) | ||||
| [NextChatAI](https://nextchat.club?utm_source=readme) / [iOS APP](https://apps.apple.com/us/app/nextchat-ai/id6743085599) / [Web App Demo](https://app.nextchat.dev) / [Desktop App](https://github.com/Yidadaa/ChatGPT-Next-Web/releases) / [Enterprise Edition](#enterprise-edition)  | ||||
|  | ||||
|  | ||||
| [saas-url]: https://nextchat.club?utm_source=readme | ||||
| @@ -41,13 +40,14 @@ English / [简体中文](./README_CN.md) | ||||
|  | ||||
| </div> | ||||
|  | ||||
| ## 🥳 Cheer for DeepSeek, China's AI star! | ||||
|  > Purpose-Built UI for DeepSeek Reasoner Model | ||||
| ## 🥳 Cheer for NextChat iOS Version Online! | ||||
| > [👉 Click Here to Install Now](https://apps.apple.com/us/app/nextchat-ai/id6743085599) | ||||
|  | ||||
| > [❤️ Source Code Coming Soon](https://github.com/ChatGPTNextWeb/NextChat-iOS) | ||||
|  | ||||
|  | ||||
|  | ||||
|   | ||||
| <img src="https://github.com/user-attachments/assets/f3952210-3af1-4dc0-9b81-40eaa4847d9a"/> | ||||
|  | ||||
|  | ||||
|  | ||||
| ## 🫣 NextChat Support MCP  !  | ||||
| > Before build, please set env ENABLE_MCP=true | ||||
|  | ||||
|   | ||||
| @@ -40,6 +40,11 @@ export interface MultimodalContent { | ||||
|   }; | ||||
| } | ||||
|  | ||||
| export interface MultimodalContentForAlibaba { | ||||
|   text?: string; | ||||
|   image?: string; | ||||
| } | ||||
|  | ||||
| export interface RequestMessage { | ||||
|   role: MessageRole; | ||||
|   content: string | MultimodalContent[]; | ||||
|   | ||||
| @@ -7,7 +7,10 @@ import { | ||||
|   ChatMessageTool, | ||||
|   usePluginStore, | ||||
| } from "@/app/store"; | ||||
| import { streamWithThink } from "@/app/utils/chat"; | ||||
| import { | ||||
|   preProcessImageContentForAlibabaDashScope, | ||||
|   streamWithThink, | ||||
| } from "@/app/utils/chat"; | ||||
| import { | ||||
|   ChatOptions, | ||||
|   getHeaders, | ||||
| @@ -15,12 +18,14 @@ import { | ||||
|   LLMModel, | ||||
|   SpeechOptions, | ||||
|   MultimodalContent, | ||||
|   MultimodalContentForAlibaba, | ||||
| } from "../api"; | ||||
| import { getClientConfig } from "@/app/config/client"; | ||||
| import { | ||||
|   getMessageTextContent, | ||||
|   getMessageTextContentWithoutThinking, | ||||
|   getTimeoutMSByModel, | ||||
|   isVisionModel, | ||||
| } from "@/app/utils"; | ||||
| import { fetch } from "@/app/utils/stream"; | ||||
|  | ||||
| @@ -89,14 +94,6 @@ export class QwenApi implements LLMApi { | ||||
|   } | ||||
|  | ||||
|   async chat(options: ChatOptions) { | ||||
|     const messages = options.messages.map((v) => ({ | ||||
|       role: v.role, | ||||
|       content: | ||||
|         v.role === "assistant" | ||||
|           ? getMessageTextContentWithoutThinking(v) | ||||
|           : getMessageTextContent(v), | ||||
|     })); | ||||
|  | ||||
|     const modelConfig = { | ||||
|       ...useAppConfig.getState().modelConfig, | ||||
|       ...useChatStore.getState().currentSession().mask.modelConfig, | ||||
| @@ -105,6 +102,21 @@ export class QwenApi implements LLMApi { | ||||
|       }, | ||||
|     }; | ||||
|  | ||||
|     const visionModel = isVisionModel(options.config.model); | ||||
|  | ||||
|     const messages: ChatOptions["messages"] = []; | ||||
|     for (const v of options.messages) { | ||||
|       const content = ( | ||||
|         visionModel | ||||
|           ? await preProcessImageContentForAlibabaDashScope(v.content) | ||||
|           : v.role === "assistant" | ||||
|           ? getMessageTextContentWithoutThinking(v) | ||||
|           : getMessageTextContent(v) | ||||
|       ) as any; | ||||
|  | ||||
|       messages.push({ role: v.role, content }); | ||||
|     } | ||||
|  | ||||
|     const shouldStream = !!options.config.stream; | ||||
|     const requestPayload: RequestPayload = { | ||||
|       model: modelConfig.model, | ||||
| @@ -129,7 +141,7 @@ export class QwenApi implements LLMApi { | ||||
|         "X-DashScope-SSE": shouldStream ? "enable" : "disable", | ||||
|       }; | ||||
|  | ||||
|       const chatPath = this.path(Alibaba.ChatPath); | ||||
|       const chatPath = this.path(Alibaba.ChatPath(modelConfig.model)); | ||||
|       const chatPayload = { | ||||
|         method: "POST", | ||||
|         body: JSON.stringify(requestPayload), | ||||
| @@ -162,7 +174,7 @@ export class QwenApi implements LLMApi { | ||||
|             const json = JSON.parse(text); | ||||
|             const choices = json.output.choices as Array<{ | ||||
|               message: { | ||||
|                 content: string | null; | ||||
|                 content: string | null | MultimodalContentForAlibaba[]; | ||||
|                 tool_calls: ChatMessageTool[]; | ||||
|                 reasoning_content: string | null; | ||||
|               }; | ||||
| @@ -212,7 +224,9 @@ export class QwenApi implements LLMApi { | ||||
|             } else if (content && content.length > 0) { | ||||
|               return { | ||||
|                 isThinking: false, | ||||
|                 content: content, | ||||
|                 content: Array.isArray(content) | ||||
|                   ? content.map((item) => item.text).join(",") | ||||
|                   : content, | ||||
|               }; | ||||
|             } | ||||
|  | ||||
|   | ||||
| @@ -75,6 +75,25 @@ export class DeepSeekApi implements LLMApi { | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     // 检测并修复消息顺序,确保除system外的第一个消息是user | ||||
|     const filteredMessages: ChatOptions["messages"] = []; | ||||
|     let hasFoundFirstUser = false; | ||||
|  | ||||
|     for (const msg of messages) { | ||||
|       if (msg.role === "system") { | ||||
|         // Keep all system messages | ||||
|         filteredMessages.push(msg); | ||||
|       } else if (msg.role === "user") { | ||||
|         // User message directly added | ||||
|         filteredMessages.push(msg); | ||||
|         hasFoundFirstUser = true; | ||||
|       } else if (hasFoundFirstUser) { | ||||
|         // After finding the first user message, all subsequent non-system messages are retained. | ||||
|         filteredMessages.push(msg); | ||||
|       } | ||||
|       // If hasFoundFirstUser is false and it is not a system message, it will be skipped. | ||||
|     } | ||||
|  | ||||
|     const modelConfig = { | ||||
|       ...useAppConfig.getState().modelConfig, | ||||
|       ...useChatStore.getState().currentSession().mask.modelConfig, | ||||
| @@ -85,7 +104,7 @@ export class DeepSeekApi implements LLMApi { | ||||
|     }; | ||||
|  | ||||
|     const requestPayload: RequestPayload = { | ||||
|       messages, | ||||
|       messages: filteredMessages, | ||||
|       stream: options.config.stream, | ||||
|       model: modelConfig.model, | ||||
|       temperature: modelConfig.temperature, | ||||
|   | ||||
| @@ -198,7 +198,8 @@ export class ChatGPTApi implements LLMApi { | ||||
|     const isDalle3 = _isDalle3(options.config.model); | ||||
|     const isO1OrO3 = | ||||
|       options.config.model.startsWith("o1") || | ||||
|       options.config.model.startsWith("o3"); | ||||
|       options.config.model.startsWith("o3") || | ||||
|       options.config.model.startsWith("o4-mini"); | ||||
|     if (isDalle3) { | ||||
|       const prompt = getMessageTextContent( | ||||
|         options.messages.slice(-1)?.pop() as any, | ||||
| @@ -243,7 +244,7 @@ export class ChatGPTApi implements LLMApi { | ||||
|       } | ||||
|  | ||||
|       // add max_tokens to vision model | ||||
|       if (visionModel) { | ||||
|       if (visionModel && !isO1OrO3) { | ||||
|         requestPayload["max_tokens"] = Math.max(modelConfig.max_tokens, 4000); | ||||
|       } | ||||
|     } | ||||
|   | ||||
| @@ -66,11 +66,11 @@ export function Avatar(props: { model?: ModelType; avatar?: string }) { | ||||
|       LlmIcon = BotIconGemma; | ||||
|     } else if (modelName.startsWith("claude")) { | ||||
|       LlmIcon = BotIconClaude; | ||||
|     } else if (modelName.toLowerCase().includes("llama")) { | ||||
|     } else if (modelName.includes("llama")) { | ||||
|       LlmIcon = BotIconMeta; | ||||
|     } else if (modelName.startsWith("mixtral")) { | ||||
|     } else if (modelName.startsWith("mixtral") || modelName.startsWith("codestral")) { | ||||
|       LlmIcon = BotIconMistral; | ||||
|     } else if (modelName.toLowerCase().includes("deepseek")) { | ||||
|     } else if (modelName.includes("deepseek")) { | ||||
|       LlmIcon = BotIconDeepseek; | ||||
|     } else if (modelName.startsWith("moonshot")) { | ||||
|       LlmIcon = BotIconMoonshot; | ||||
| @@ -85,7 +85,7 @@ export function Avatar(props: { model?: ModelType; avatar?: string }) { | ||||
|     } else if (modelName.startsWith("doubao") || modelName.startsWith("ep-")) { | ||||
|       LlmIcon = BotIconDoubao; | ||||
|     } else if ( | ||||
|       modelName.toLowerCase().includes("glm") || | ||||
|       modelName.includes("glm") || | ||||
|       modelName.startsWith("cogview-") || | ||||
|       modelName.startsWith("cogvideox-") | ||||
|     ) { | ||||
|   | ||||
| @@ -221,7 +221,12 @@ export const ByteDance = { | ||||
|  | ||||
| export const Alibaba = { | ||||
|   ExampleEndpoint: ALIBABA_BASE_URL, | ||||
|   ChatPath: "v1/services/aigc/text-generation/generation", | ||||
|   ChatPath: (modelName: string) => { | ||||
|     if (modelName.includes("vl") || modelName.includes("omni")) { | ||||
|       return "v1/services/aigc/multimodal-generation/generation"; | ||||
|     } | ||||
|     return `v1/services/aigc/text-generation/generation`; | ||||
|   }, | ||||
| }; | ||||
|  | ||||
| export const Tencent = { | ||||
| @@ -412,6 +417,14 @@ export const KnowledgeCutOffDate: Record<string, string> = { | ||||
|   "gpt-4-turbo": "2023-12", | ||||
|   "gpt-4-turbo-2024-04-09": "2023-12", | ||||
|   "gpt-4-turbo-preview": "2023-12", | ||||
|   "gpt-4.1": "2024-06", | ||||
|   "gpt-4.1-2025-04-14": "2024-06", | ||||
|   "gpt-4.1-mini": "2024-06", | ||||
|   "gpt-4.1-mini-2025-04-14": "2024-06", | ||||
|   "gpt-4.1-nano": "2024-06", | ||||
|   "gpt-4.1-nano-2025-04-14": "2024-06", | ||||
|   "gpt-4.5-preview": "2023-10", | ||||
|   "gpt-4.5-preview-2025-02-27": "2023-10", | ||||
|   "gpt-4o": "2023-10", | ||||
|   "gpt-4o-2024-05-13": "2023-10", | ||||
|   "gpt-4o-2024-08-06": "2023-10", | ||||
| @@ -453,6 +466,7 @@ export const DEFAULT_TTS_VOICES = [ | ||||
| export const VISION_MODEL_REGEXES = [ | ||||
|   /vision/, | ||||
|   /gpt-4o/, | ||||
|   /gpt-4\.1/, | ||||
|   /claude-3/, | ||||
|   /gemini-1\.5/, | ||||
|   /gemini-exp/, | ||||
| @@ -464,6 +478,8 @@ export const VISION_MODEL_REGEXES = [ | ||||
|   /^dall-e-3$/, // Matches exactly "dall-e-3" | ||||
|   /glm-4v/, | ||||
|   /vl/i, | ||||
|   /o3/, | ||||
|   /o4-mini/, | ||||
| ]; | ||||
|  | ||||
| export const EXCLUDE_VISION_MODEL_REGEXES = [/claude-3-5-haiku-20241022/]; | ||||
| @@ -480,6 +496,14 @@ const openaiModels = [ | ||||
|   "gpt-4-32k-0613", | ||||
|   "gpt-4-turbo", | ||||
|   "gpt-4-turbo-preview", | ||||
|   "gpt-4.1", | ||||
|   "gpt-4.1-2025-04-14", | ||||
|   "gpt-4.1-mini", | ||||
|   "gpt-4.1-mini-2025-04-14", | ||||
|   "gpt-4.1-nano", | ||||
|   "gpt-4.1-nano-2025-04-14", | ||||
|   "gpt-4.5-preview", | ||||
|   "gpt-4.5-preview-2025-02-27", | ||||
|   "gpt-4o", | ||||
|   "gpt-4o-2024-05-13", | ||||
|   "gpt-4o-2024-08-06", | ||||
| @@ -494,6 +518,8 @@ const openaiModels = [ | ||||
|   "o1-mini", | ||||
|   "o1-preview", | ||||
|   "o3-mini", | ||||
|   "o3", | ||||
|   "o4-mini", | ||||
| ]; | ||||
|  | ||||
| const googleModels = [ | ||||
| @@ -535,6 +561,8 @@ const anthropicModels = [ | ||||
|   "claude-3-5-sonnet-20240620", | ||||
|   "claude-3-5-sonnet-20241022", | ||||
|   "claude-3-5-sonnet-latest", | ||||
|   "claude-3-7-sonnet-20250219", | ||||
|   "claude-3-7-sonnet-latest", | ||||
| ]; | ||||
|  | ||||
| const baiduModels = [ | ||||
| @@ -568,6 +596,9 @@ const alibabaModes = [ | ||||
|   "qwen-max-0403", | ||||
|   "qwen-max-0107", | ||||
|   "qwen-max-longcontext", | ||||
|   "qwen-omni-turbo", | ||||
|   "qwen-vl-plus", | ||||
|   "qwen-vl-max", | ||||
| ]; | ||||
|  | ||||
| const tencentModels = [ | ||||
|   | ||||
							
								
								
									
										832
									
								
								app/locales/da.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										832
									
								
								app/locales/da.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,832 @@ | ||||
| import { getClientConfig } from "../config/client"; | ||||
| import { SubmitKey } from "../store/config"; | ||||
| import { SAAS_CHAT_UTM_URL } from "@/app/constant"; | ||||
| import { PartialLocaleType } from "./index"; | ||||
|  | ||||
| const isApp = !!getClientConfig()?.isApp; | ||||
| const da: PartialLocaleType = { | ||||
|   WIP: "Der kommer snart mere...", | ||||
|   Error: { | ||||
|     Unauthorized: isApp | ||||
|       ? `Hov, der skete en fejl. Sådan kan du komme videre: | ||||
|        \\ 1️⃣ Er du ny her? [Tryk for at starte nu 🚀](${SAAS_CHAT_UTM_URL}) | ||||
|        \\ 2️⃣ Vil du bruge dine egne OpenAI-nøgler? [Tryk her](/#/settings) for at ændre indstillinger ⚙️` | ||||
|       : `Hov, der skete en fejl. Lad os løse det: | ||||
|        \\ 1️⃣ Er du ny her? [Tryk for at starte nu 🚀](${SAAS_CHAT_UTM_URL}) | ||||
|        \\ 2️⃣ Bruger du en privat opsætning? [Tryk her](/#/auth) for at taste din nøgle 🔑 | ||||
|        \\ 3️⃣ Vil du bruge dine egne OpenAI-nøgler? [Tryk her](/#/settings) for at ændre indstillinger ⚙️ | ||||
|        `, | ||||
|   }, | ||||
|   Auth: { | ||||
|     Return: "Tilbage", | ||||
|     Title: "Adgangskode", | ||||
|     Tips: "Skriv venligst koden herunder", | ||||
|     SubTips: "Eller brug din egen OpenAI- eller Google-nøgle", | ||||
|     Input: "Adgangskode", | ||||
|     Confirm: "OK", | ||||
|     Later: "Senere", | ||||
|     SaasTips: "Hvis det er for svært, kan du starte nu", | ||||
|   }, | ||||
|   ChatItem: { | ||||
|     ChatItemCount: (count: number) => `${count} beskeder`, | ||||
|   }, | ||||
|   Chat: { | ||||
|     SubTitle: (count: number) => `${count} beskeder`, | ||||
|     EditMessage: { | ||||
|       Title: "Rediger beskeder", | ||||
|       Topic: { | ||||
|         Title: "Emne", | ||||
|         SubTitle: "Skift emne for denne chat", | ||||
|       }, | ||||
|     }, | ||||
|     Actions: { | ||||
|       ChatList: "Gå til chatliste", | ||||
|       CompressedHistory: "Komprimeret historie", | ||||
|       Export: "Eksporter alle beskeder som Markdown", | ||||
|       Copy: "Kopiér", | ||||
|       Stop: "Stop", | ||||
|       Retry: "Prøv igen", | ||||
|       Pin: "Fastgør", | ||||
|       PinToastContent: "1 besked er nu fastgjort", | ||||
|       PinToastAction: "Se", | ||||
|       Delete: "Slet", | ||||
|       Edit: "Rediger", | ||||
|       FullScreen: "Fuld skærm", | ||||
|       RefreshTitle: "Opdatér titel", | ||||
|       RefreshToast: "Anmodning om ny titel sendt", | ||||
|       Speech: "Afspil", | ||||
|       StopSpeech: "Stop", | ||||
|     }, | ||||
|     Commands: { | ||||
|       new: "Ny chat", | ||||
|       newm: "Ny chat med persona", | ||||
|       next: "Næste chat", | ||||
|       prev: "Forrige chat", | ||||
|       clear: "Ryd alt før", | ||||
|       fork: "Kopiér chat", | ||||
|       del: "Slet chat", | ||||
|     }, | ||||
|     InputActions: { | ||||
|       Stop: "Stop", | ||||
|       ToBottom: "Ned til nyeste", | ||||
|       Theme: { | ||||
|         auto: "Automatisk", | ||||
|         light: "Lyst tema", | ||||
|         dark: "Mørkt tema", | ||||
|       }, | ||||
|       Prompt: "Prompts", | ||||
|       Masks: "Personaer", | ||||
|       Clear: "Ryd kontekst", | ||||
|       Settings: "Indstillinger", | ||||
|       UploadImage: "Upload billeder", | ||||
|     }, | ||||
|     Rename: "Omdøb chat", | ||||
|     Typing: "Skriver…", | ||||
|     Input: (submitKey: string) => { | ||||
|       let inputHints = `${submitKey} for at sende`; | ||||
|       if (submitKey === String(SubmitKey.Enter)) { | ||||
|         inputHints += ", Shift + Enter for ny linje"; | ||||
|       } | ||||
|       return ( | ||||
|         inputHints + ", / for at søge i prompts, : for at bruge kommandoer" | ||||
|       ); | ||||
|     }, | ||||
|     Send: "Send", | ||||
|     StartSpeak: "Start oplæsning", | ||||
|     StopSpeak: "Stop oplæsning", | ||||
|     Config: { | ||||
|       Reset: "Nulstil til standard", | ||||
|       SaveAs: "Gem som persona", | ||||
|     }, | ||||
|     IsContext: "Ekstra prompt til baggrund", | ||||
|     ShortcutKey: { | ||||
|       Title: "Hurtigtaster", | ||||
|       newChat: "Åbn ny chat", | ||||
|       focusInput: "Fokus på tekstfeltet", | ||||
|       copyLastMessage: "Kopiér sidste svar", | ||||
|       copyLastCode: "Kopiér sidste kodeblok", | ||||
|       showShortcutKey: "Vis hurtigtaster", | ||||
|       clearContext: "Ryd kontekst", | ||||
|     }, | ||||
|   }, | ||||
|   Export: { | ||||
|     Title: "Eksportér beskeder", | ||||
|     Copy: "Kopiér alt", | ||||
|     Download: "Download", | ||||
|     MessageFromYou: "Fra dig", | ||||
|     MessageFromChatGPT: "Fra ChatGPT", | ||||
|     Share: "Del til ShareGPT", | ||||
|     Format: { | ||||
|       Title: "Filformat", | ||||
|       SubTitle: "Vælg enten Markdown eller PNG-billede", | ||||
|     }, | ||||
|     IncludeContext: { | ||||
|       Title: "Tag baggrund med", | ||||
|       SubTitle: "Skal ekstra baggrund (persona) med i eksporten?", | ||||
|     }, | ||||
|     Steps: { | ||||
|       Select: "Vælg", | ||||
|       Preview: "Forhåndsvis", | ||||
|     }, | ||||
|     Image: { | ||||
|       Toast: "Laver billede...", | ||||
|       Modal: "Tryk længe eller højreklik for at gemme", | ||||
|     }, | ||||
|     Artifacts: { | ||||
|       Title: "Del side", | ||||
|       Error: "Fejl ved deling", | ||||
|     }, | ||||
|   }, | ||||
|   Select: { | ||||
|     Search: "Søg", | ||||
|     All: "Vælg alle", | ||||
|     Latest: "Vælg nyeste", | ||||
|     Clear: "Ryd alt", | ||||
|   }, | ||||
|   Memory: { | ||||
|     Title: "Huskesætning", | ||||
|     EmptyContent: "Ingenting lige nu.", | ||||
|     Send: "Send huskesætning", | ||||
|     Copy: "Kopiér huskesætning", | ||||
|     Reset: "Nulstil chat", | ||||
|     ResetConfirm: | ||||
|       "Dette sletter nuværende samtale og hukommelse. Er du sikker?", | ||||
|   }, | ||||
|   Home: { | ||||
|     NewChat: "Ny Chat", | ||||
|     DeleteChat: "Vil du slette den valgte chat?", | ||||
|     DeleteToast: "Chat slettet", | ||||
|     Revert: "Fortryd", | ||||
|   }, | ||||
|   Settings: { | ||||
|     Title: "Indstillinger", | ||||
|     SubTitle: "Alle indstillinger", | ||||
|     ShowPassword: "Vis kodeord", | ||||
|     Danger: { | ||||
|       Reset: { | ||||
|         Title: "Nulstil alle indstillinger", | ||||
|         SubTitle: "Gendan alt til standard", | ||||
|         Action: "Nulstil", | ||||
|         Confirm: "Vil du virkelig nulstille alt?", | ||||
|       }, | ||||
|       Clear: { | ||||
|         Title: "Slet alle data", | ||||
|         SubTitle: "Sletter alt om beskeder og indstillinger", | ||||
|         Action: "Slet", | ||||
|         Confirm: "Er du sikker på, at du vil slette alt?", | ||||
|       }, | ||||
|     }, | ||||
|     Lang: { | ||||
|       Name: "Language", | ||||
|       All: "Alle sprog", | ||||
|     }, | ||||
|     Avatar: "Avatar", | ||||
|     FontSize: { | ||||
|       Title: "Skriftstørrelse", | ||||
|       SubTitle: "Vælg, hvor stor teksten skal være", | ||||
|     }, | ||||
|     FontFamily: { | ||||
|       Title: "Skrifttype", | ||||
|       SubTitle: "Hvis tom, bruger den standard skrifttype", | ||||
|       Placeholder: "Skrifttype-navn", | ||||
|     }, | ||||
|     InjectSystemPrompts: { | ||||
|       Title: "Tilføj system-prompt", | ||||
|       SubTitle: "Læg altid en ekstra prompt først i anmodninger", | ||||
|     }, | ||||
|     InputTemplate: { | ||||
|       Title: "Tekstskabelon", | ||||
|       SubTitle: "Den seneste besked placeres i denne skabelon", | ||||
|     }, | ||||
|     Update: { | ||||
|       Version: (x: string) => `Version: ${x}`, | ||||
|       IsLatest: "Du har nyeste version", | ||||
|       CheckUpdate: "Tjek efter opdatering", | ||||
|       IsChecking: "Tjekker...", | ||||
|       FoundUpdate: (x: string) => `Ny version fundet: ${x}`, | ||||
|       GoToUpdate: "Opdatér", | ||||
|       Success: "Opdatering lykkedes.", | ||||
|       Failed: "Opdatering mislykkedes.", | ||||
|     }, | ||||
|     SendKey: "Tast for send", | ||||
|     Theme: "Tema", | ||||
|     TightBorder: "Stram kant", | ||||
|     SendPreviewBubble: { | ||||
|       Title: "Forhåndsvisnings-boble", | ||||
|       SubTitle: "Vis tekst, før den sendes", | ||||
|     }, | ||||
|     AutoGenerateTitle: { | ||||
|       Title: "Lav titel automatisk", | ||||
|       SubTitle: "Foreslå en titel ud fra chatten", | ||||
|     }, | ||||
|     Sync: { | ||||
|       CloudState: "Seneste opdatering", | ||||
|       NotSyncYet: "Endnu ikke synkroniseret", | ||||
|       Success: "Synkronisering lykkedes", | ||||
|       Fail: "Synkronisering mislykkedes", | ||||
|       Config: { | ||||
|         Modal: { | ||||
|           Title: "Indstil synk", | ||||
|           Check: "Tjek forbindelse", | ||||
|         }, | ||||
|         SyncType: { | ||||
|           Title: "Synk-type", | ||||
|           SubTitle: "Vælg en synk-tjeneste", | ||||
|         }, | ||||
|         Proxy: { | ||||
|           Title: "Aktivér proxy", | ||||
|           SubTitle: "Brug proxy for at undgå netværksproblemer", | ||||
|         }, | ||||
|         ProxyUrl: { | ||||
|           Title: "Proxy-adresse", | ||||
|           SubTitle: "Bruges kun til projektets egen proxy", | ||||
|         }, | ||||
|         WebDav: { | ||||
|           Endpoint: "WebDAV-adresse", | ||||
|           UserName: "Brugernavn", | ||||
|           Password: "Kodeord", | ||||
|         }, | ||||
|         UpStash: { | ||||
|           Endpoint: "UpStash Redis REST URL", | ||||
|           UserName: "Backup-navn", | ||||
|           Password: "UpStash Redis REST Token", | ||||
|         }, | ||||
|       }, | ||||
|       LocalState: "Lokale data", | ||||
|       Overview: (overview: any) => | ||||
|         `${overview.chat} chats, ${overview.message} beskeder, ${overview.prompt} prompts, ${overview.mask} personaer`, | ||||
|       ImportFailed: "Import mislykkedes", | ||||
|     }, | ||||
|     Mask: { | ||||
|       Splash: { | ||||
|         Title: "Persona-forside", | ||||
|         SubTitle: "Vis denne side, når du opretter ny chat", | ||||
|       }, | ||||
|       Builtin: { | ||||
|         Title: "Skjul indbyggede personaer", | ||||
|         SubTitle: "Vis ikke de indbyggede personaer i listen", | ||||
|       }, | ||||
|     }, | ||||
|     Prompt: { | ||||
|       Disable: { | ||||
|         Title: "Slå auto-forslag fra", | ||||
|         SubTitle: "Tast / for at få forslag", | ||||
|       }, | ||||
|       List: "Prompt-liste", | ||||
|       ListCount: (builtin: number, custom: number) => | ||||
|         `${builtin} indbygget, ${custom} brugerdefineret`, | ||||
|       Edit: "Rediger", | ||||
|       Modal: { | ||||
|         Title: "Prompt-liste", | ||||
|         Add: "Tilføj", | ||||
|         Search: "Søg prompts", | ||||
|       }, | ||||
|       EditModal: { | ||||
|         Title: "Rediger prompt", | ||||
|       }, | ||||
|     }, | ||||
|     HistoryCount: { | ||||
|       Title: "Antal beskeder, der følger med", | ||||
|       SubTitle: "Hvor mange af de tidligere beskeder, der sendes hver gang", | ||||
|     }, | ||||
|     CompressThreshold: { | ||||
|       Title: "Komprimeringsgrænse", | ||||
|       SubTitle: | ||||
|         "Hvis chatten bliver for lang, vil den komprimeres efter dette antal tegn", | ||||
|     }, | ||||
|     Usage: { | ||||
|       Title: "Brug og saldo", | ||||
|       SubTitle(used: any, total: any) { | ||||
|         return `Du har brugt $${used} i denne måned, og din grænse er $${total}.`; | ||||
|       }, | ||||
|       IsChecking: "Tjekker...", | ||||
|       Check: "Tjek igen", | ||||
|       NoAccess: "Indtast API-nøgle for at se forbrug", | ||||
|     }, | ||||
|     Access: { | ||||
|       AccessCode: { | ||||
|         Title: "Adgangskode", | ||||
|         SubTitle: "Adgangskontrol er slået til", | ||||
|         Placeholder: "Skriv kode her", | ||||
|       }, | ||||
|       CustomEndpoint: { | ||||
|         Title: "Brugerdefineret adresse", | ||||
|         SubTitle: "Brug Azure eller OpenAI fra egen server", | ||||
|       }, | ||||
|       Provider: { | ||||
|         Title: "Model-udbyder", | ||||
|         SubTitle: "Vælg Azure eller OpenAI", | ||||
|       }, | ||||
|       OpenAI: { | ||||
|         ApiKey: { | ||||
|           Title: "OpenAI API-nøgle", | ||||
|           SubTitle: "Brug din egen nøgle", | ||||
|           Placeholder: "sk-xxx", | ||||
|         }, | ||||
|         Endpoint: { | ||||
|           Title: "OpenAI Endpoint", | ||||
|           SubTitle: "Skal starte med http(s):// eller /api/openai som standard", | ||||
|         }, | ||||
|       }, | ||||
|       Azure: { | ||||
|         ApiKey: { | ||||
|           Title: "Azure Api Key", | ||||
|           SubTitle: "Hent din nøgle fra Azure-portalen", | ||||
|           Placeholder: "Azure Api Key", | ||||
|         }, | ||||
|         Endpoint: { | ||||
|           Title: "Azure Endpoint", | ||||
|           SubTitle: "F.eks.: ", | ||||
|         }, | ||||
|         ApiVerion: { | ||||
|           Title: "Azure Api Version", | ||||
|           SubTitle: "Hentet fra Azure-portalen", | ||||
|         }, | ||||
|       }, | ||||
|       Anthropic: { | ||||
|         ApiKey: { | ||||
|           Title: "Anthropic API-nøgle", | ||||
|           SubTitle: "Brug din egen Anthropic-nøgle", | ||||
|           Placeholder: "Anthropic API Key", | ||||
|         }, | ||||
|         Endpoint: { | ||||
|           Title: "Endpoint-adresse", | ||||
|           SubTitle: "F.eks.: ", | ||||
|         }, | ||||
|         ApiVerion: { | ||||
|           Title: "API-version (Claude)", | ||||
|           SubTitle: "Vælg den ønskede version", | ||||
|         }, | ||||
|       }, | ||||
|       Baidu: { | ||||
|         ApiKey: { | ||||
|           Title: "Baidu-nøgle", | ||||
|           SubTitle: "Din egen Baidu-nøgle", | ||||
|           Placeholder: "Baidu API Key", | ||||
|         }, | ||||
|         SecretKey: { | ||||
|           Title: "Baidu hemmelig nøgle", | ||||
|           SubTitle: "Din egen hemmelige nøgle fra Baidu", | ||||
|           Placeholder: "Baidu Secret Key", | ||||
|         }, | ||||
|         Endpoint: { | ||||
|           Title: "Adresse", | ||||
|           SubTitle: "Kan ikke ændres, se .env", | ||||
|         }, | ||||
|       }, | ||||
|       Tencent: { | ||||
|         ApiKey: { | ||||
|           Title: "Tencent-nøgle", | ||||
|           SubTitle: "Din egen nøgle fra Tencent", | ||||
|           Placeholder: "Tencent API Key", | ||||
|         }, | ||||
|         SecretKey: { | ||||
|           Title: "Tencent hemmelig nøgle", | ||||
|           SubTitle: "Din egen hemmelige nøgle fra Tencent", | ||||
|           Placeholder: "Tencent Secret Key", | ||||
|         }, | ||||
|         Endpoint: { | ||||
|           Title: "Adresse", | ||||
|           SubTitle: "Kan ikke ændres, se .env", | ||||
|         }, | ||||
|       }, | ||||
|       ByteDance: { | ||||
|         ApiKey: { | ||||
|           Title: "ByteDance-nøgle", | ||||
|           SubTitle: "Din egen nøgle til ByteDance", | ||||
|           Placeholder: "ByteDance API Key", | ||||
|         }, | ||||
|         Endpoint: { | ||||
|           Title: "Adresse", | ||||
|           SubTitle: "F.eks.: ", | ||||
|         }, | ||||
|       }, | ||||
|       Alibaba: { | ||||
|         ApiKey: { | ||||
|           Title: "Alibaba-nøgle", | ||||
|           SubTitle: "Din egen Alibaba Cloud-nøgle", | ||||
|           Placeholder: "Alibaba Cloud API Key", | ||||
|         }, | ||||
|         Endpoint: { | ||||
|           Title: "Adresse", | ||||
|           SubTitle: "F.eks.: ", | ||||
|         }, | ||||
|       }, | ||||
|       Moonshot: { | ||||
|         ApiKey: { | ||||
|           Title: "Moonshot-nøgle", | ||||
|           SubTitle: "Din egen Moonshot-nøgle", | ||||
|           Placeholder: "Moonshot API Key", | ||||
|         }, | ||||
|         Endpoint: { | ||||
|           Title: "Adresse", | ||||
|           SubTitle: "F.eks.: ", | ||||
|         }, | ||||
|       }, | ||||
|       DeepSeek: { | ||||
|         ApiKey: { | ||||
|           Title: "DeepSeek-nøgle", | ||||
|           SubTitle: "Din egen DeepSeek-nøgle", | ||||
|           Placeholder: "DeepSeek API Key", | ||||
|         }, | ||||
|         Endpoint: { | ||||
|           Title: "Adresse", | ||||
|           SubTitle: "F.eks.: ", | ||||
|         }, | ||||
|       }, | ||||
|       XAI: { | ||||
|         ApiKey: { | ||||
|           Title: "XAI-nøgle", | ||||
|           SubTitle: "Din egen XAI-nøgle", | ||||
|           Placeholder: "XAI API Key", | ||||
|         }, | ||||
|         Endpoint: { | ||||
|           Title: "Adresse", | ||||
|           SubTitle: "F.eks.: ", | ||||
|         }, | ||||
|       }, | ||||
|       ChatGLM: { | ||||
|         ApiKey: { | ||||
|           Title: "ChatGLM-nøgle", | ||||
|           SubTitle: "Din egen ChatGLM-nøgle", | ||||
|           Placeholder: "ChatGLM API Key", | ||||
|         }, | ||||
|         Endpoint: { | ||||
|           Title: "Adresse", | ||||
|           SubTitle: "F.eks.: ", | ||||
|         }, | ||||
|       }, | ||||
|       SiliconFlow: { | ||||
|         ApiKey: { | ||||
|           Title: "SiliconFlow-nøgle", | ||||
|           SubTitle: "Din egen SiliconFlow-nøgle", | ||||
|           Placeholder: "SiliconFlow API Key", | ||||
|         }, | ||||
|         Endpoint: { | ||||
|           Title: "Adresse", | ||||
|           SubTitle: "F.eks.: ", | ||||
|         }, | ||||
|       }, | ||||
|       Stability: { | ||||
|         ApiKey: { | ||||
|           Title: "Stability-nøgle", | ||||
|           SubTitle: "Din egen Stability-nøgle", | ||||
|           Placeholder: "Stability API Key", | ||||
|         }, | ||||
|         Endpoint: { | ||||
|           Title: "Adresse", | ||||
|           SubTitle: "F.eks.: ", | ||||
|         }, | ||||
|       }, | ||||
|       Iflytek: { | ||||
|         ApiKey: { | ||||
|           Title: "Iflytek API Key", | ||||
|           SubTitle: "Nøgle fra Iflytek", | ||||
|           Placeholder: "Iflytek API Key", | ||||
|         }, | ||||
|         ApiSecret: { | ||||
|           Title: "Iflytek hemmelig nøgle", | ||||
|           SubTitle: "Hentet fra Iflytek", | ||||
|           Placeholder: "Iflytek API Secret", | ||||
|         }, | ||||
|         Endpoint: { | ||||
|           Title: "Adresse", | ||||
|           SubTitle: "F.eks.: ", | ||||
|         }, | ||||
|       }, | ||||
|       CustomModel: { | ||||
|         Title: "Egne modelnavne", | ||||
|         SubTitle: "Skriv komma-adskilte navne", | ||||
|       }, | ||||
|       Google: { | ||||
|         ApiKey: { | ||||
|           Title: "Google-nøgle", | ||||
|           SubTitle: "Få din nøgle hos Google AI", | ||||
|           Placeholder: "Google AI API Key", | ||||
|         }, | ||||
|         Endpoint: { | ||||
|           Title: "Adresse", | ||||
|           SubTitle: "F.eks.: ", | ||||
|         }, | ||||
|         ApiVersion: { | ||||
|           Title: "API-version (til gemini-pro)", | ||||
|           SubTitle: "Vælg en bestemt version", | ||||
|         }, | ||||
|         GoogleSafetySettings: { | ||||
|           Title: "Google sikkerhedsindstillinger", | ||||
|           SubTitle: "Vælg et niveau for indholdskontrol", | ||||
|         }, | ||||
|       }, | ||||
|     }, | ||||
|     Model: "Model", | ||||
|     CompressModel: { | ||||
|       Title: "Opsummeringsmodel", | ||||
|       SubTitle: "Bruges til at korte historik ned og lave titel", | ||||
|     }, | ||||
|     Temperature: { | ||||
|       Title: "Temperatur", | ||||
|       SubTitle: "Jo højere tal, jo mere kreativt svar", | ||||
|     }, | ||||
|     TopP: { | ||||
|       Title: "Top P", | ||||
|       SubTitle: "Skal ikke ændres sammen med temperatur", | ||||
|     }, | ||||
|     MaxTokens: { | ||||
|       Title: "Maks. længde", | ||||
|       SubTitle: "Hvor mange tokens (ord/stykker tekst) der kan bruges", | ||||
|     }, | ||||
|     PresencePenalty: { | ||||
|       Title: "Nye emner", | ||||
|       SubTitle: "Jo højere tal, jo mere nyt indhold", | ||||
|     }, | ||||
|     FrequencyPenalty: { | ||||
|       Title: "Gentagelsesstraf", | ||||
|       SubTitle: "Jo højere tal, jo mindre gentagelse", | ||||
|     }, | ||||
|     TTS: { | ||||
|       Enable: { | ||||
|         Title: "Tænd for oplæsning (TTS)", | ||||
|         SubTitle: "Slå tekst-til-tale til", | ||||
|       }, | ||||
|       Autoplay: { | ||||
|         Title: "Automatisk oplæsning", | ||||
|         SubTitle: "Laver lyd automatisk, hvis TTS er slået til", | ||||
|       }, | ||||
|       Model: "Model", | ||||
|       Voice: { | ||||
|         Title: "Stemme", | ||||
|         SubTitle: "Hvilken stemme der bruges til lyd", | ||||
|       }, | ||||
|       Speed: { | ||||
|         Title: "Hastighed", | ||||
|         SubTitle: "Hvor hurtigt der oplæses", | ||||
|       }, | ||||
|       Engine: "TTS-motor", | ||||
|     }, | ||||
|     Realtime: { | ||||
|       Enable: { | ||||
|         Title: "Live-chat", | ||||
|         SubTitle: "Slå live-svar til", | ||||
|       }, | ||||
|       Provider: { | ||||
|         Title: "Modeludbyder", | ||||
|         SubTitle: "Vælg forskellig udbyder", | ||||
|       }, | ||||
|       Model: { | ||||
|         Title: "Model", | ||||
|         SubTitle: "Vælg en model", | ||||
|       }, | ||||
|       ApiKey: { | ||||
|         Title: "API-nøgle", | ||||
|         SubTitle: "Din nøgle", | ||||
|         Placeholder: "API-nøgle", | ||||
|       }, | ||||
|       Azure: { | ||||
|         Endpoint: { | ||||
|           Title: "Adresse", | ||||
|           SubTitle: "Endpoint til Azure", | ||||
|         }, | ||||
|         Deployment: { | ||||
|           Title: "Udrulningsnavn", | ||||
|           SubTitle: "Navn for dit Azure-setup", | ||||
|         }, | ||||
|       }, | ||||
|       Temperature: { | ||||
|         Title: "Temperatur", | ||||
|         SubTitle: "Højere tal = mere varierede svar", | ||||
|       }, | ||||
|     }, | ||||
|   }, | ||||
|   Store: { | ||||
|     DefaultTopic: "Ny samtale", | ||||
|     BotHello: "Hej! Hvordan kan jeg hjælpe dig i dag?", | ||||
|     Error: "Noget gik galt. Prøv igen senere.", | ||||
|     Prompt: { | ||||
|       History: (content: string) => | ||||
|         "Her er et kort resume af, hvad vi har snakket om: " + content, | ||||
|       Topic: | ||||
|         "Find en kort overskrift med 4-5 ord om emnet. Ingen tegnsætning eller anførselstegn.", | ||||
|       Summarize: | ||||
|         "Skriv et kort resumé (under 200 ord) af vores samtale til senere brug.", | ||||
|     }, | ||||
|   }, | ||||
|   Copy: { | ||||
|     Success: "Kopieret", | ||||
|     Failed: "Kunne ikke kopiere. Giv adgang til udklipsholder.", | ||||
|   }, | ||||
|   Download: { | ||||
|     Success: "Filen er downloadet.", | ||||
|     Failed: "Download fejlede.", | ||||
|   }, | ||||
|   Context: { | ||||
|     Toast: (x: any) => `Inkluderer ${x} ekstra prompts`, | ||||
|     Edit: "Chatindstillinger", | ||||
|     Add: "Tilføj prompt", | ||||
|     Clear: "Kontekst ryddet", | ||||
|     Revert: "Fortryd", | ||||
|   }, | ||||
|   Discovery: { | ||||
|     Name: "Søgning og plugins", | ||||
|   }, | ||||
|   Mcp: { | ||||
|     Name: "MCP", | ||||
|   }, | ||||
|   FineTuned: { | ||||
|     Sysmessage: "Du er en hjælper, der skal...", | ||||
|   }, | ||||
|   SearchChat: { | ||||
|     Name: "Søg", | ||||
|     Page: { | ||||
|       Title: "Søg i tidligere chats", | ||||
|       Search: "Skriv her for at søge", | ||||
|       NoResult: "Ingen resultater", | ||||
|       NoData: "Ingen data", | ||||
|       Loading: "Henter...", | ||||
|       SubTitle: (count: number) => `Fandt ${count} resultater`, | ||||
|     }, | ||||
|     Item: { | ||||
|       View: "Vis", | ||||
|     }, | ||||
|   }, | ||||
|   Plugin: { | ||||
|     Name: "Plugin", | ||||
|     Page: { | ||||
|       Title: "Plugins", | ||||
|       SubTitle: (count: number) => `${count} plugins`, | ||||
|       Search: "Søg plugin", | ||||
|       Create: "Opret nyt", | ||||
|       Find: "Du kan finde flere plugins på GitHub: ", | ||||
|     }, | ||||
|     Item: { | ||||
|       Info: (count: number) => `${count} metode`, | ||||
|       View: "Vis", | ||||
|       Edit: "Rediger", | ||||
|       Delete: "Slet", | ||||
|       DeleteConfirm: "Vil du slette?", | ||||
|     }, | ||||
|     Auth: { | ||||
|       None: "Ingen", | ||||
|       Basic: "Basic", | ||||
|       Bearer: "Bearer", | ||||
|       Custom: "Tilpasset", | ||||
|       CustomHeader: "Parameternavn", | ||||
|       Token: "Token", | ||||
|       Proxy: "Brug Proxy", | ||||
|       ProxyDescription: "Løs CORS-problemer med Proxy", | ||||
|       Location: "Sted", | ||||
|       LocationHeader: "Header", | ||||
|       LocationQuery: "Query", | ||||
|       LocationBody: "Body", | ||||
|     }, | ||||
|     EditModal: { | ||||
|       Title: (readonly: boolean) => | ||||
|         `Rediger Plugin ${readonly ? "(skrivebeskyttet)" : ""}`, | ||||
|       Download: "Download", | ||||
|       Auth: "Godkendelsestype", | ||||
|       Content: "OpenAPI Schema", | ||||
|       Load: "Hent fra URL", | ||||
|       Method: "Metode", | ||||
|       Error: "Fejl i OpenAPI Schema", | ||||
|     }, | ||||
|   }, | ||||
|   Mask: { | ||||
|     Name: "Persona", | ||||
|     Page: { | ||||
|       Title: "Prompts som personaer", | ||||
|       SubTitle: (count: number) => `${count} skabeloner`, | ||||
|       Search: "Søg skabeloner", | ||||
|       Create: "Opret ny", | ||||
|     }, | ||||
|     Item: { | ||||
|       Info: (count: number) => `${count} prompts`, | ||||
|       Chat: "Chat", | ||||
|       View: "Vis", | ||||
|       Edit: "Rediger", | ||||
|       Delete: "Slet", | ||||
|       DeleteConfirm: "Vil du slette?", | ||||
|     }, | ||||
|     EditModal: { | ||||
|       Title: (readonly: boolean) => | ||||
|         `Rediger skabelon ${readonly ? "(skrivebeskyttet)" : ""}`, | ||||
|       Download: "Download", | ||||
|       Clone: "Klon", | ||||
|     }, | ||||
|     Config: { | ||||
|       Avatar: "Chat-avatar", | ||||
|       Name: "Chat-navn", | ||||
|       Sync: { | ||||
|         Title: "Brug globale indstillinger", | ||||
|         SubTitle: "Gældende for denne chat", | ||||
|         Confirm: "Erstat nuværende indstillinger med globale?", | ||||
|       }, | ||||
|       HideContext: { | ||||
|         Title: "Skjul ekstra prompts", | ||||
|         SubTitle: "Vis dem ikke på chat-skærmen", | ||||
|       }, | ||||
|       Artifacts: { | ||||
|         Title: "Brug Artefakter", | ||||
|         SubTitle: "Gør det muligt at vise HTML-sider", | ||||
|       }, | ||||
|       CodeFold: { | ||||
|         Title: "Fold kode sammen", | ||||
|         SubTitle: "Luk/åbn lange kodestykker automatisk", | ||||
|       }, | ||||
|       Share: { | ||||
|         Title: "Del denne persona", | ||||
|         SubTitle: "Få et link til denne skabelon", | ||||
|         Action: "Kopiér link", | ||||
|       }, | ||||
|     }, | ||||
|   }, | ||||
|   NewChat: { | ||||
|     Return: "Tilbage", | ||||
|     Skip: "Start straks", | ||||
|     Title: "Vælg en persona", | ||||
|     SubTitle: "Chat med den persona, du vælger", | ||||
|     More: "Se flere", | ||||
|     NotShow: "Vis ikke igen", | ||||
|     ConfirmNoShow: | ||||
|       "Er du sikker på, at du ikke vil se det igen? Du kan altid slå det til under indstillinger.", | ||||
|   }, | ||||
|   UI: { | ||||
|     Confirm: "OK", | ||||
|     Cancel: "Fortryd", | ||||
|     Close: "Luk", | ||||
|     Create: "Opret", | ||||
|     Edit: "Rediger", | ||||
|     Export: "Eksporter", | ||||
|     Import: "Importér", | ||||
|     Sync: "Synk", | ||||
|     Config: "Konfigurer", | ||||
|   }, | ||||
|   Exporter: { | ||||
|     Description: { | ||||
|       Title: "Kun beskeder efter sidste rydning vises", | ||||
|     }, | ||||
|     Model: "Model", | ||||
|     Messages: "Beskeder", | ||||
|     Topic: "Emne", | ||||
|     Time: "Tid", | ||||
|   }, | ||||
|   URLCommand: { | ||||
|     Code: "Så ud til, at der var en kode i linket. Vil du bruge den?", | ||||
|     Settings: "Så ud til, at der var indstillinger i linket. Vil du bruge dem?", | ||||
|   }, | ||||
|   SdPanel: { | ||||
|     Prompt: "Prompt", | ||||
|     NegativePrompt: "Negativ prompt", | ||||
|     PleaseInput: (name: string) => `Indtast: ${name}`, | ||||
|     AspectRatio: "Billedformat", | ||||
|     ImageStyle: "Stil", | ||||
|     OutFormat: "Uddataformat", | ||||
|     AIModel: "AI-model", | ||||
|     ModelVersion: "Version", | ||||
|     Submit: "Send", | ||||
|     ParamIsRequired: (name: string) => `${name} er krævet`, | ||||
|     Styles: { | ||||
|       D3Model: "3d-model", | ||||
|       AnalogFilm: "analog-film", | ||||
|       Anime: "anime", | ||||
|       Cinematic: "cinematisk", | ||||
|       ComicBook: "tegneserie", | ||||
|       DigitalArt: "digital-art", | ||||
|       Enhance: "enhance", | ||||
|       FantasyArt: "fantasy-art", | ||||
|       Isometric: "isometric", | ||||
|       LineArt: "line-art", | ||||
|       LowPoly: "low-poly", | ||||
|       ModelingCompound: "modeling-compound", | ||||
|       NeonPunk: "neon-punk", | ||||
|       Origami: "origami", | ||||
|       Photographic: "fotografisk", | ||||
|       PixelArt: "pixel-art", | ||||
|       TileTexture: "tile-texture", | ||||
|     }, | ||||
|   }, | ||||
|   Sd: { | ||||
|     SubTitle: (count: number) => `${count} billeder`, | ||||
|     Actions: { | ||||
|       Params: "Se indstillinger", | ||||
|       Copy: "Kopiér prompt", | ||||
|       Delete: "Slet", | ||||
|       Retry: "Prøv igen", | ||||
|       ReturnHome: "Til forsiden", | ||||
|       History: "Historik", | ||||
|     }, | ||||
|     EmptyRecord: "Ingen billeder endnu", | ||||
|     Status: { | ||||
|       Name: "Status", | ||||
|       Success: "Ok", | ||||
|       Error: "Fejl", | ||||
|       Wait: "Venter", | ||||
|       Running: "I gang", | ||||
|     }, | ||||
|     Danger: { | ||||
|       Delete: "Vil du slette?", | ||||
|     }, | ||||
|     GenerateParams: "Genereringsvalg", | ||||
|     Detail: "Detaljer", | ||||
|   }, | ||||
| }; | ||||
|  | ||||
| export default da; | ||||
| @@ -2,6 +2,7 @@ import cn from "./cn"; | ||||
| import en from "./en"; | ||||
| import pt from "./pt"; | ||||
| import tw from "./tw"; | ||||
| import da from "./da"; | ||||
| import id from "./id"; | ||||
| import fr from "./fr"; | ||||
| import es from "./es"; | ||||
| @@ -30,6 +31,7 @@ const ALL_LANGS = { | ||||
|   en, | ||||
|   tw, | ||||
|   pt, | ||||
|   da, | ||||
|   jp, | ||||
|   ko, | ||||
|   id, | ||||
| @@ -56,6 +58,7 @@ export const ALL_LANG_OPTIONS: Record<Lang, string> = { | ||||
|   en: "English", | ||||
|   pt: "Português", | ||||
|   tw: "繁體中文", | ||||
|   da: "Dansk", | ||||
|   jp: "日本語", | ||||
|   ko: "한국어", | ||||
|   id: "Indonesia", | ||||
| @@ -141,6 +144,7 @@ export const STT_LANG_MAP: Record<Lang, string> = { | ||||
|   en: "en-US", | ||||
|   pt: "pt-BR", | ||||
|   tw: "zh-TW", | ||||
|   da: "da-DK", | ||||
|   jp: "ja-JP", | ||||
|   ko: "ko-KR", | ||||
|   id: "id-ID", | ||||
|   | ||||
| @@ -3,7 +3,7 @@ import { | ||||
|   UPLOAD_URL, | ||||
|   REQUEST_TIMEOUT_MS, | ||||
| } from "@/app/constant"; | ||||
| import { RequestMessage } from "@/app/client/api"; | ||||
| import { MultimodalContent, RequestMessage } from "@/app/client/api"; | ||||
| import Locale from "@/app/locales"; | ||||
| import { | ||||
|   EventStreamContentType, | ||||
| @@ -70,8 +70,9 @@ export function compressImage(file: Blob, maxSize: number): Promise<string> { | ||||
|   }); | ||||
| } | ||||
|  | ||||
| export async function preProcessImageContent( | ||||
| export async function preProcessImageContentBase( | ||||
|   content: RequestMessage["content"], | ||||
|   transformImageUrl: (url: string) => Promise<{ [key: string]: any }>, | ||||
| ) { | ||||
|   if (typeof content === "string") { | ||||
|     return content; | ||||
| @@ -81,7 +82,7 @@ export async function preProcessImageContent( | ||||
|     if (part?.type == "image_url" && part?.image_url?.url) { | ||||
|       try { | ||||
|         const url = await cacheImageToBase64Image(part?.image_url?.url); | ||||
|         result.push({ type: part.type, image_url: { url } }); | ||||
|         result.push(await transformImageUrl(url)); | ||||
|       } catch (error) { | ||||
|         console.error("Error processing image URL:", error); | ||||
|       } | ||||
| @@ -92,6 +93,23 @@ export async function preProcessImageContent( | ||||
|   return result; | ||||
| } | ||||
|  | ||||
| export async function preProcessImageContent( | ||||
|   content: RequestMessage["content"], | ||||
| ) { | ||||
|   return preProcessImageContentBase(content, async (url) => ({ | ||||
|     type: "image_url", | ||||
|     image_url: { url }, | ||||
|   })) as Promise<MultimodalContent[] | string>; | ||||
| } | ||||
|  | ||||
| export async function preProcessImageContentForAlibabaDashScope( | ||||
|   content: RequestMessage["content"], | ||||
| ) { | ||||
|   return preProcessImageContentBase(content, async (url) => ({ | ||||
|     image: url, | ||||
|   })); | ||||
| } | ||||
|  | ||||
| const imageCaches: Record<string, string> = {}; | ||||
| export function cacheImageToBase64Image(imageUrl: string) { | ||||
|   if (imageUrl.includes(CACHE_URL_PREFIX)) { | ||||
|   | ||||
| @@ -15,6 +15,8 @@ const config: Config = { | ||||
|   moduleNameMapper: { | ||||
|     "^@/(.*)$": "<rootDir>/$1", | ||||
|   }, | ||||
|   extensionsToTreatAsEsm: [".ts", ".tsx"], | ||||
|   injectGlobals: true, | ||||
| }; | ||||
|  | ||||
| // createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async | ||||
|   | ||||
| @@ -1,24 +1,22 @@ | ||||
| // Learn more: https://github.com/testing-library/jest-dom | ||||
| import "@testing-library/jest-dom"; | ||||
| import { jest } from "@jest/globals"; | ||||
|  | ||||
| global.fetch = jest.fn(() => | ||||
|   Promise.resolve({ | ||||
|     ok: true, | ||||
|     status: 200, | ||||
|     json: () => Promise.resolve({}), | ||||
|     json: () => Promise.resolve([]), | ||||
|     headers: new Headers(), | ||||
|     redirected: false, | ||||
|     statusText: "OK", | ||||
|     type: "basic", | ||||
|     url: "", | ||||
|     clone: function () { | ||||
|       return this; | ||||
|     }, | ||||
|     body: null, | ||||
|     bodyUsed: false, | ||||
|     arrayBuffer: () => Promise.resolve(new ArrayBuffer(0)), | ||||
|     blob: () => Promise.resolve(new Blob()), | ||||
|     formData: () => Promise.resolve(new FormData()), | ||||
|     text: () => Promise.resolve(""), | ||||
|   }), | ||||
|   } as Response), | ||||
| ); | ||||
|   | ||||
| @@ -17,8 +17,8 @@ | ||||
|     "prompts": "node ./scripts/fetch-prompts.mjs", | ||||
|     "prepare": "husky install", | ||||
|     "proxy-dev": "sh ./scripts/init-proxy.sh && proxychains -f ./scripts/proxychains.conf yarn dev", | ||||
|     "test": "jest --watch", | ||||
|     "test:ci": "jest --ci" | ||||
|     "test": "node --no-warnings --experimental-vm-modules $(yarn bin jest) --watch", | ||||
|     "test:ci": "node --no-warnings --experimental-vm-modules $(yarn bin jest) --ci" | ||||
|   }, | ||||
|   "dependencies": { | ||||
|     "@fortaine/fetch-event-source": "^3.0.6", | ||||
|   | ||||
| @@ -2,16 +2,16 @@ | ||||
|   { | ||||
|     "id": "dalle3", | ||||
|     "name": "Dalle3", | ||||
|     "schema": "https://ghp.ci/https://raw.githubusercontent.com/ChatGPTNextWeb/NextChat-Awesome-Plugins/main/plugins/dalle/openapi.json" | ||||
|     "schema": "https://cdn.jsdelivr.net/gh/ChatGPTNextWeb/NextChat-Awesome-Plugins@main/plugins/dalle/openapi.json" | ||||
|   }, | ||||
|   { | ||||
|     "id": "arxivsearch", | ||||
|     "name": "ArxivSearch", | ||||
|     "schema": "https://ghp.ci/https://raw.githubusercontent.com/ChatGPTNextWeb/NextChat-Awesome-Plugins/main/plugins/arxivsearch/openapi.json" | ||||
|     "schema": "https://cdn.jsdelivr.net/gh/ChatGPTNextWeb/NextChat-Awesome-Plugins@main/plugins/arxivsearch/openapi.json" | ||||
|   }, | ||||
|   { | ||||
|     "id": "duckduckgolite", | ||||
|     "name": "DuckDuckGoLiteSearch", | ||||
|     "schema": "https://ghp.ci/https://raw.githubusercontent.com/ChatGPTNextWeb/NextChat-Awesome-Plugins/main/plugins/duckduckgolite/openapi.json" | ||||
|     "schema": "https://cdn.jsdelivr.net/gh/ChatGPTNextWeb/NextChat-Awesome-Plugins@main/plugins/duckduckgolite/openapi.json" | ||||
|   } | ||||
| ] | ||||
|   | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,14 +1,13 @@ | ||||
| import fetch from "node-fetch"; | ||||
| import fs from "fs/promises"; | ||||
|  | ||||
| const RAW_FILE_URL = "https://raw.githubusercontent.com/"; | ||||
| const MIRRORF_FILE_URL = "http://raw.fgit.ml/"; | ||||
| const MIRRORF_FILE_URL = "https://cdn.jsdelivr.net/gh/"; | ||||
|  | ||||
| const RAW_CN_URL = "PlexPt/awesome-chatgpt-prompts-zh/main/prompts-zh.json"; | ||||
| const RAW_CN_URL = "PlexPt/awesome-chatgpt-prompts-zh@main/prompts-zh.json"; | ||||
| const CN_URL = MIRRORF_FILE_URL + RAW_CN_URL; | ||||
| const RAW_TW_URL = "PlexPt/awesome-chatgpt-prompts-zh/main/prompts-zh-TW.json"; | ||||
| const RAW_TW_URL = "PlexPt/awesome-chatgpt-prompts-zh@main/prompts-zh-TW.json"; | ||||
| const TW_URL = MIRRORF_FILE_URL + RAW_TW_URL; | ||||
| const RAW_EN_URL = "f/awesome-chatgpt-prompts/main/prompts.csv"; | ||||
| const RAW_EN_URL = "f/awesome-chatgpt-prompts@main/prompts.csv"; | ||||
| const EN_URL = MIRRORF_FILE_URL + RAW_EN_URL; | ||||
| const FILE = "./public/prompts.json"; | ||||
|  | ||||
| @@ -84,11 +83,11 @@ async function fetchEN() { | ||||
| async function main() { | ||||
|   Promise.all([fetchCN(), fetchTW(), fetchEN()]) | ||||
|     .then(([cn, tw, en]) => { | ||||
|       fs.writeFile(FILE, JSON.stringify({ cn, tw, en })); | ||||
|       fs.writeFile(FILE, JSON.stringify({ cn, tw, en }, null, 2)); | ||||
|     }) | ||||
|     .catch((e) => { | ||||
|       console.error("[Fetch] failed to fetch prompts"); | ||||
|       fs.writeFile(FILE, JSON.stringify({ cn: [], tw: [], en: [] })); | ||||
|       fs.writeFile(FILE, JSON.stringify({ cn: [], tw: [], en: [] }, null, 2)); | ||||
|     }) | ||||
|     .finally(() => { | ||||
|       console.log("[Fetch] saved to " + FILE); | ||||
|   | ||||
| @@ -1,3 +1,4 @@ | ||||
| import { jest } from "@jest/globals"; | ||||
| import { isVisionModel } from "../app/utils"; | ||||
|  | ||||
| describe("isVisionModel", () => { | ||||
| @@ -50,7 +51,7 @@ describe("isVisionModel", () => { | ||||
|  | ||||
|   test("should identify models from VISION_MODELS env var", () => { | ||||
|     process.env.VISION_MODELS = "custom-vision-model,another-vision-model"; | ||||
|      | ||||
|  | ||||
|     expect(isVisionModel("custom-vision-model")).toBe(true); | ||||
|     expect(isVisionModel("another-vision-model")).toBe(true); | ||||
|     expect(isVisionModel("unrelated-model")).toBe(false); | ||||
| @@ -64,4 +65,4 @@ describe("isVisionModel", () => { | ||||
|     expect(isVisionModel("unrelated-model")).toBe(false); | ||||
|     expect(isVisionModel("gpt-4-vision")).toBe(true); | ||||
|   }); | ||||
| }); | ||||
| }); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user