mirror of
				https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web.git
				synced 2025-10-31 14:23:43 +08:00 
			
		
		
		
	Compare commits
	
		
			26 Commits
		
	
	
		
			v2.16.0
			...
			Leizhenpen
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | c9ef6d58ed | ||
|  | 11b37c15bd | ||
|  | 1d0038f17d | ||
|  | 619fa519c0 | ||
|  | 48469bd8ca | ||
|  | 5a5e887f2b | ||
|  | b6f5d75656 | ||
|  | 0d41a17ef6 | ||
|  | f7cde17919 | ||
|  | 570cbb34b6 | ||
|  | 7aa9ae0a3e | ||
|  | 2d4180f5be | ||
|  | 9f0182b55e | ||
|  | ad6666eeaf | ||
|  | a2c4e468a0 | ||
|  | 2167076652 | ||
|  | e123076250 | ||
|  | ebcb4db245 | ||
|  | 0a25a1a8cb | ||
|  | f3154b20a5 | ||
|  | b709ee3983 | ||
|  | f5f3ce94f6 | ||
|  | 2b5f600308 | ||
|  | b966107117 | ||
|  | 90827fc593 | ||
|  | 008e339b6d | 
							
								
								
									
										10
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								README.md
									
									
									
									
									
								
							| @@ -7,7 +7,7 @@ | |||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| <h1 align="center">NextChat (ChatGPT Next Web)</h1> | <h1 align="center">NextChat</h1> | ||||||
|  |  | ||||||
| English / [简体中文](./README_CN.md) | English / [简体中文](./README_CN.md) | ||||||
|  |  | ||||||
| @@ -22,7 +22,6 @@ English / [简体中文](./README_CN.md) | |||||||
| [![MacOS][MacOS-image]][download-url] | [![MacOS][MacOS-image]][download-url] | ||||||
| [![Linux][Linux-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) / [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) | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -41,11 +40,10 @@ English / [简体中文](./README_CN.md) | |||||||
|  |  | ||||||
| </div> | </div> | ||||||
|  |  | ||||||
| ## 🥳 Cheer for DeepSeek, China's AI star! | ## 🥳 Cheer for NextChat iOS Version Online! | ||||||
|  > Purpose-Built UI for DeepSeek Reasoner Model |  > [ 👉 Click Here Install Now](https://apps.apple.com/us/app/nextchat-ai/id6743085599) | ||||||
|   |  | ||||||
| <img src="https://github.com/user-attachments/assets/f3952210-3af1-4dc0-9b81-40eaa4847d9a"/> |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|   |   | ||||||
| ## 🫣 NextChat Support MCP  !  | ## 🫣 NextChat Support MCP  !  | ||||||
|   | |||||||
| @@ -40,6 +40,11 @@ export interface MultimodalContent { | |||||||
|   }; |   }; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | export interface MultimodalContentForAlibaba { | ||||||
|  |   text?: string; | ||||||
|  |   image?: string; | ||||||
|  | } | ||||||
|  |  | ||||||
| export interface RequestMessage { | export interface RequestMessage { | ||||||
|   role: MessageRole; |   role: MessageRole; | ||||||
|   content: string | MultimodalContent[]; |   content: string | MultimodalContent[]; | ||||||
|   | |||||||
| @@ -7,7 +7,10 @@ import { | |||||||
|   ChatMessageTool, |   ChatMessageTool, | ||||||
|   usePluginStore, |   usePluginStore, | ||||||
| } from "@/app/store"; | } from "@/app/store"; | ||||||
| import { streamWithThink } from "@/app/utils/chat"; | import { | ||||||
|  |   preProcessImageContentForAlibabaDashScope, | ||||||
|  |   streamWithThink, | ||||||
|  | } from "@/app/utils/chat"; | ||||||
| import { | import { | ||||||
|   ChatOptions, |   ChatOptions, | ||||||
|   getHeaders, |   getHeaders, | ||||||
| @@ -15,12 +18,14 @@ import { | |||||||
|   LLMModel, |   LLMModel, | ||||||
|   SpeechOptions, |   SpeechOptions, | ||||||
|   MultimodalContent, |   MultimodalContent, | ||||||
|  |   MultimodalContentForAlibaba, | ||||||
| } from "../api"; | } from "../api"; | ||||||
| import { getClientConfig } from "@/app/config/client"; | import { getClientConfig } from "@/app/config/client"; | ||||||
| import { | import { | ||||||
|   getMessageTextContent, |   getMessageTextContent, | ||||||
|   getMessageTextContentWithoutThinking, |   getMessageTextContentWithoutThinking, | ||||||
|   getTimeoutMSByModel, |   getTimeoutMSByModel, | ||||||
|  |   isVisionModel, | ||||||
| } from "@/app/utils"; | } from "@/app/utils"; | ||||||
| import { fetch } from "@/app/utils/stream"; | import { fetch } from "@/app/utils/stream"; | ||||||
|  |  | ||||||
| @@ -89,14 +94,6 @@ export class QwenApi implements LLMApi { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   async chat(options: ChatOptions) { |   async chat(options: ChatOptions) { | ||||||
|     const messages = options.messages.map((v) => ({ |  | ||||||
|       role: v.role, |  | ||||||
|       content: |  | ||||||
|         v.role === "assistant" |  | ||||||
|           ? getMessageTextContentWithoutThinking(v) |  | ||||||
|           : getMessageTextContent(v), |  | ||||||
|     })); |  | ||||||
|  |  | ||||||
|     const modelConfig = { |     const modelConfig = { | ||||||
|       ...useAppConfig.getState().modelConfig, |       ...useAppConfig.getState().modelConfig, | ||||||
|       ...useChatStore.getState().currentSession().mask.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 shouldStream = !!options.config.stream; | ||||||
|     const requestPayload: RequestPayload = { |     const requestPayload: RequestPayload = { | ||||||
|       model: modelConfig.model, |       model: modelConfig.model, | ||||||
| @@ -129,7 +141,7 @@ export class QwenApi implements LLMApi { | |||||||
|         "X-DashScope-SSE": shouldStream ? "enable" : "disable", |         "X-DashScope-SSE": shouldStream ? "enable" : "disable", | ||||||
|       }; |       }; | ||||||
|  |  | ||||||
|       const chatPath = this.path(Alibaba.ChatPath); |       const chatPath = this.path(Alibaba.ChatPath(modelConfig.model)); | ||||||
|       const chatPayload = { |       const chatPayload = { | ||||||
|         method: "POST", |         method: "POST", | ||||||
|         body: JSON.stringify(requestPayload), |         body: JSON.stringify(requestPayload), | ||||||
| @@ -162,7 +174,7 @@ export class QwenApi implements LLMApi { | |||||||
|             const json = JSON.parse(text); |             const json = JSON.parse(text); | ||||||
|             const choices = json.output.choices as Array<{ |             const choices = json.output.choices as Array<{ | ||||||
|               message: { |               message: { | ||||||
|                 content: string | null; |                 content: string | null | MultimodalContentForAlibaba[]; | ||||||
|                 tool_calls: ChatMessageTool[]; |                 tool_calls: ChatMessageTool[]; | ||||||
|                 reasoning_content: string | null; |                 reasoning_content: string | null; | ||||||
|               }; |               }; | ||||||
| @@ -212,7 +224,9 @@ export class QwenApi implements LLMApi { | |||||||
|             } else if (content && content.length > 0) { |             } else if (content && content.length > 0) { | ||||||
|               return { |               return { | ||||||
|                 isThinking: false, |                 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 = { |     const modelConfig = { | ||||||
|       ...useAppConfig.getState().modelConfig, |       ...useAppConfig.getState().modelConfig, | ||||||
|       ...useChatStore.getState().currentSession().mask.modelConfig, |       ...useChatStore.getState().currentSession().mask.modelConfig, | ||||||
| @@ -85,7 +104,7 @@ export class DeepSeekApi implements LLMApi { | |||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     const requestPayload: RequestPayload = { |     const requestPayload: RequestPayload = { | ||||||
|       messages, |       messages: filteredMessages, | ||||||
|       stream: options.config.stream, |       stream: options.config.stream, | ||||||
|       model: modelConfig.model, |       model: modelConfig.model, | ||||||
|       temperature: modelConfig.temperature, |       temperature: modelConfig.temperature, | ||||||
|   | |||||||
| @@ -66,11 +66,11 @@ export function Avatar(props: { model?: ModelType; avatar?: string }) { | |||||||
|       LlmIcon = BotIconGemma; |       LlmIcon = BotIconGemma; | ||||||
|     } else if (modelName.startsWith("claude")) { |     } else if (modelName.startsWith("claude")) { | ||||||
|       LlmIcon = BotIconClaude; |       LlmIcon = BotIconClaude; | ||||||
|     } else if (modelName.toLowerCase().includes("llama")) { |     } else if (modelName.includes("llama")) { | ||||||
|       LlmIcon = BotIconMeta; |       LlmIcon = BotIconMeta; | ||||||
|     } else if (modelName.startsWith("mixtral")) { |     } else if (modelName.startsWith("mixtral") || modelName.startsWith("codestral")) { | ||||||
|       LlmIcon = BotIconMistral; |       LlmIcon = BotIconMistral; | ||||||
|     } else if (modelName.toLowerCase().includes("deepseek")) { |     } else if (modelName.includes("deepseek")) { | ||||||
|       LlmIcon = BotIconDeepseek; |       LlmIcon = BotIconDeepseek; | ||||||
|     } else if (modelName.startsWith("moonshot")) { |     } else if (modelName.startsWith("moonshot")) { | ||||||
|       LlmIcon = BotIconMoonshot; |       LlmIcon = BotIconMoonshot; | ||||||
| @@ -85,7 +85,7 @@ export function Avatar(props: { model?: ModelType; avatar?: string }) { | |||||||
|     } else if (modelName.startsWith("doubao") || modelName.startsWith("ep-")) { |     } else if (modelName.startsWith("doubao") || modelName.startsWith("ep-")) { | ||||||
|       LlmIcon = BotIconDoubao; |       LlmIcon = BotIconDoubao; | ||||||
|     } else if ( |     } else if ( | ||||||
|       modelName.toLowerCase().includes("glm") || |       modelName.includes("glm") || | ||||||
|       modelName.startsWith("cogview-") || |       modelName.startsWith("cogview-") || | ||||||
|       modelName.startsWith("cogvideox-") |       modelName.startsWith("cogvideox-") | ||||||
|     ) { |     ) { | ||||||
|   | |||||||
| @@ -221,7 +221,12 @@ export const ByteDance = { | |||||||
|  |  | ||||||
| export const Alibaba = { | export const Alibaba = { | ||||||
|   ExampleEndpoint: ALIBABA_BASE_URL, |   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 = { | export const Tencent = { | ||||||
| @@ -412,6 +417,14 @@ export const KnowledgeCutOffDate: Record<string, string> = { | |||||||
|   "gpt-4-turbo": "2023-12", |   "gpt-4-turbo": "2023-12", | ||||||
|   "gpt-4-turbo-2024-04-09": "2023-12", |   "gpt-4-turbo-2024-04-09": "2023-12", | ||||||
|   "gpt-4-turbo-preview": "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": "2023-10", | ||||||
|   "gpt-4o-2024-05-13": "2023-10", |   "gpt-4o-2024-05-13": "2023-10", | ||||||
|   "gpt-4o-2024-08-06": "2023-10", |   "gpt-4o-2024-08-06": "2023-10", | ||||||
| @@ -453,6 +466,7 @@ export const DEFAULT_TTS_VOICES = [ | |||||||
| export const VISION_MODEL_REGEXES = [ | export const VISION_MODEL_REGEXES = [ | ||||||
|   /vision/, |   /vision/, | ||||||
|   /gpt-4o/, |   /gpt-4o/, | ||||||
|  |   /gpt-4\.1/, | ||||||
|   /claude-3/, |   /claude-3/, | ||||||
|   /gemini-1\.5/, |   /gemini-1\.5/, | ||||||
|   /gemini-exp/, |   /gemini-exp/, | ||||||
| @@ -480,6 +494,14 @@ const openaiModels = [ | |||||||
|   "gpt-4-32k-0613", |   "gpt-4-32k-0613", | ||||||
|   "gpt-4-turbo", |   "gpt-4-turbo", | ||||||
|   "gpt-4-turbo-preview", |   "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", | ||||||
|   "gpt-4o-2024-05-13", |   "gpt-4o-2024-05-13", | ||||||
|   "gpt-4o-2024-08-06", |   "gpt-4o-2024-08-06", | ||||||
| @@ -535,6 +557,8 @@ const anthropicModels = [ | |||||||
|   "claude-3-5-sonnet-20240620", |   "claude-3-5-sonnet-20240620", | ||||||
|   "claude-3-5-sonnet-20241022", |   "claude-3-5-sonnet-20241022", | ||||||
|   "claude-3-5-sonnet-latest", |   "claude-3-5-sonnet-latest", | ||||||
|  |   "claude-3-7-sonnet-20250219", | ||||||
|  |   "claude-3-7-sonnet-latest", | ||||||
| ]; | ]; | ||||||
|  |  | ||||||
| const baiduModels = [ | const baiduModels = [ | ||||||
| @@ -568,6 +592,9 @@ const alibabaModes = [ | |||||||
|   "qwen-max-0403", |   "qwen-max-0403", | ||||||
|   "qwen-max-0107", |   "qwen-max-0107", | ||||||
|   "qwen-max-longcontext", |   "qwen-max-longcontext", | ||||||
|  |   "qwen-omni-turbo", | ||||||
|  |   "qwen-vl-plus", | ||||||
|  |   "qwen-vl-max", | ||||||
| ]; | ]; | ||||||
|  |  | ||||||
| const tencentModels = [ | 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 en from "./en"; | ||||||
| import pt from "./pt"; | import pt from "./pt"; | ||||||
| import tw from "./tw"; | import tw from "./tw"; | ||||||
|  | import da from "./da"; | ||||||
| import id from "./id"; | import id from "./id"; | ||||||
| import fr from "./fr"; | import fr from "./fr"; | ||||||
| import es from "./es"; | import es from "./es"; | ||||||
| @@ -30,6 +31,7 @@ const ALL_LANGS = { | |||||||
|   en, |   en, | ||||||
|   tw, |   tw, | ||||||
|   pt, |   pt, | ||||||
|  |   da, | ||||||
|   jp, |   jp, | ||||||
|   ko, |   ko, | ||||||
|   id, |   id, | ||||||
| @@ -56,6 +58,7 @@ export const ALL_LANG_OPTIONS: Record<Lang, string> = { | |||||||
|   en: "English", |   en: "English", | ||||||
|   pt: "Português", |   pt: "Português", | ||||||
|   tw: "繁體中文", |   tw: "繁體中文", | ||||||
|  |   da: "Dansk", | ||||||
|   jp: "日本語", |   jp: "日本語", | ||||||
|   ko: "한국어", |   ko: "한국어", | ||||||
|   id: "Indonesia", |   id: "Indonesia", | ||||||
| @@ -141,6 +144,7 @@ export const STT_LANG_MAP: Record<Lang, string> = { | |||||||
|   en: "en-US", |   en: "en-US", | ||||||
|   pt: "pt-BR", |   pt: "pt-BR", | ||||||
|   tw: "zh-TW", |   tw: "zh-TW", | ||||||
|  |   da: "da-DK", | ||||||
|   jp: "ja-JP", |   jp: "ja-JP", | ||||||
|   ko: "ko-KR", |   ko: "ko-KR", | ||||||
|   id: "id-ID", |   id: "id-ID", | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ import { | |||||||
|   UPLOAD_URL, |   UPLOAD_URL, | ||||||
|   REQUEST_TIMEOUT_MS, |   REQUEST_TIMEOUT_MS, | ||||||
| } from "@/app/constant"; | } from "@/app/constant"; | ||||||
| import { RequestMessage } from "@/app/client/api"; | import { MultimodalContent, RequestMessage } from "@/app/client/api"; | ||||||
| import Locale from "@/app/locales"; | import Locale from "@/app/locales"; | ||||||
| import { | import { | ||||||
|   EventStreamContentType, |   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"], |   content: RequestMessage["content"], | ||||||
|  |   transformImageUrl: (url: string) => Promise<{ [key: string]: any }>, | ||||||
| ) { | ) { | ||||||
|   if (typeof content === "string") { |   if (typeof content === "string") { | ||||||
|     return content; |     return content; | ||||||
| @@ -81,7 +82,7 @@ export async function preProcessImageContent( | |||||||
|     if (part?.type == "image_url" && part?.image_url?.url) { |     if (part?.type == "image_url" && part?.image_url?.url) { | ||||||
|       try { |       try { | ||||||
|         const url = await cacheImageToBase64Image(part?.image_url?.url); |         const url = await cacheImageToBase64Image(part?.image_url?.url); | ||||||
|         result.push({ type: part.type, image_url: { url } }); |         result.push(await transformImageUrl(url)); | ||||||
|       } catch (error) { |       } catch (error) { | ||||||
|         console.error("Error processing image URL:", error); |         console.error("Error processing image URL:", error); | ||||||
|       } |       } | ||||||
| @@ -92,6 +93,23 @@ export async function preProcessImageContent( | |||||||
|   return result; |   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> = {}; | const imageCaches: Record<string, string> = {}; | ||||||
| export function cacheImageToBase64Image(imageUrl: string) { | export function cacheImageToBase64Image(imageUrl: string) { | ||||||
|   if (imageUrl.includes(CACHE_URL_PREFIX)) { |   if (imageUrl.includes(CACHE_URL_PREFIX)) { | ||||||
|   | |||||||
| @@ -15,6 +15,8 @@ const config: Config = { | |||||||
|   moduleNameMapper: { |   moduleNameMapper: { | ||||||
|     "^@/(.*)$": "<rootDir>/$1", |     "^@/(.*)$": "<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 | // 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 | // Learn more: https://github.com/testing-library/jest-dom | ||||||
| import "@testing-library/jest-dom"; | import "@testing-library/jest-dom"; | ||||||
|  | import { jest } from "@jest/globals"; | ||||||
|  |  | ||||||
| global.fetch = jest.fn(() => | global.fetch = jest.fn(() => | ||||||
|   Promise.resolve({ |   Promise.resolve({ | ||||||
|     ok: true, |     ok: true, | ||||||
|     status: 200, |     status: 200, | ||||||
|     json: () => Promise.resolve({}), |     json: () => Promise.resolve([]), | ||||||
|     headers: new Headers(), |     headers: new Headers(), | ||||||
|     redirected: false, |     redirected: false, | ||||||
|     statusText: "OK", |     statusText: "OK", | ||||||
|     type: "basic", |     type: "basic", | ||||||
|     url: "", |     url: "", | ||||||
|     clone: function () { |  | ||||||
|       return this; |  | ||||||
|     }, |  | ||||||
|     body: null, |     body: null, | ||||||
|     bodyUsed: false, |     bodyUsed: false, | ||||||
|     arrayBuffer: () => Promise.resolve(new ArrayBuffer(0)), |     arrayBuffer: () => Promise.resolve(new ArrayBuffer(0)), | ||||||
|     blob: () => Promise.resolve(new Blob()), |     blob: () => Promise.resolve(new Blob()), | ||||||
|     formData: () => Promise.resolve(new FormData()), |     formData: () => Promise.resolve(new FormData()), | ||||||
|     text: () => Promise.resolve(""), |     text: () => Promise.resolve(""), | ||||||
|   }), |   } as Response), | ||||||
| ); | ); | ||||||
|   | |||||||
| @@ -17,8 +17,8 @@ | |||||||
|     "prompts": "node ./scripts/fetch-prompts.mjs", |     "prompts": "node ./scripts/fetch-prompts.mjs", | ||||||
|     "prepare": "husky install", |     "prepare": "husky install", | ||||||
|     "proxy-dev": "sh ./scripts/init-proxy.sh && proxychains -f ./scripts/proxychains.conf yarn dev", |     "proxy-dev": "sh ./scripts/init-proxy.sh && proxychains -f ./scripts/proxychains.conf yarn dev", | ||||||
|     "test": "jest --watch", |     "test": "node --no-warnings --experimental-vm-modules $(yarn bin jest) --watch", | ||||||
|     "test:ci": "jest --ci" |     "test:ci": "node --no-warnings --experimental-vm-modules $(yarn bin jest) --ci" | ||||||
|   }, |   }, | ||||||
|   "dependencies": { |   "dependencies": { | ||||||
|     "@fortaine/fetch-event-source": "^3.0.6", |     "@fortaine/fetch-event-source": "^3.0.6", | ||||||
|   | |||||||
| @@ -1,3 +1,4 @@ | |||||||
|  | import { jest } from "@jest/globals"; | ||||||
| import { isVisionModel } from "../app/utils"; | import { isVisionModel } from "../app/utils"; | ||||||
|  |  | ||||||
| describe("isVisionModel", () => { | describe("isVisionModel", () => { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user