├── .eslintignore ├── scripts ├── .gitignore ├── proxychains.template.conf ├── init-proxy.sh ├── delete-deployment-preview.sh └── setup.sh ├── src-tauri ├── build.rs ├── .gitignore ├── icons │ ├── 32x32.png │ ├── icon.icns │ ├── icon.ico │ ├── icon.png │ ├── 128x128.png │ ├── 128x128@2x.png │ ├── StoreLogo.png │ ├── Square30x30Logo.png │ ├── Square44x44Logo.png │ ├── Square71x71Logo.png │ ├── Square89x89Logo.png │ ├── Square107x107Logo.png │ ├── Square142x142Logo.png │ ├── Square150x150Logo.png │ ├── Square284x284Logo.png │ └── Square310x310Logo.png ├── src │ └── main.rs └── Cargo.toml ├── vercel.json ├── public ├── macos.png ├── favicon.ico ├── robots.txt ├── favicon-16x16.png ├── favicon-32x32.png ├── apple-touch-icon.png ├── android-chrome-192x192.png ├── android-chrome-512x512.png ├── serviceWorkerRegister.js ├── site.webmanifest ├── expansion-rules.json └── serviceWorker.js ├── .eslintrc.json ├── .husky └── pre-commit ├── app ├── icons │ ├── bot.png │ ├── chatgpt.png │ ├── zoom.svg │ ├── home.svg │ ├── edit_input.svg │ ├── bot-ai.svg │ ├── discovery.svg │ ├── continue.svg │ ├── collapse.svg │ ├── expand.svg │ ├── llm-icons │ │ ├── suno.svg │ │ ├── flux.svg │ │ ├── default-ai.svg │ │ ├── gemini.svg │ │ ├── yi.svg │ │ ├── runway.svg │ │ ├── grok.svg │ │ ├── mistral.svg │ │ ├── stepfun.svg │ │ ├── cohere.svg │ │ ├── sparkdesk.svg │ │ ├── stability.svg │ │ ├── doubao.svg │ │ ├── hunyuan.svg │ │ ├── glm.svg │ │ ├── minimax.svg │ │ ├── openai.svg │ │ ├── claude.svg │ │ ├── azure.svg │ │ ├── wenxin.svg │ │ ├── qwen.svg │ │ ├── deepseek.svg │ │ └── luma.svg │ ├── eraser.svg │ ├── speak.svg │ ├── speak-stop.svg │ ├── left.svg │ ├── down.svg │ ├── share.svg │ ├── plus.svg │ ├── cloud.svg │ ├── incognito.svg │ ├── bottom.svg │ ├── shortcutkey.svg │ ├── voice-white.svg │ ├── dark.svg │ ├── close.svg │ ├── send-white.svg │ ├── ai-logo.svg │ ├── rename.svg │ ├── return.svg │ ├── copy.svg │ ├── paperclip.svg │ ├── settings.svg │ ├── menu.svg │ ├── toolbox.svg │ ├── upload-doc.svg │ ├── lightOn.svg │ ├── pause.svg │ ├── reload.svg │ ├── add.svg │ ├── three-dots.svg │ ├── custom-models.svg │ ├── export.svg │ ├── eye.svg │ ├── clear.svg │ ├── prompt.svg │ ├── translate.svg │ ├── auto.svg │ ├── loading.svg │ ├── chat.svg │ ├── drag.svg │ ├── edit.svg │ ├── openai.svg │ ├── openai_plus.svg │ ├── download.svg │ ├── brain.svg │ ├── sun.svg │ ├── eye-off.svg │ ├── ocr.svg │ ├── github.svg │ ├── max.svg │ ├── min.svg │ ├── mask.svg │ ├── unpin.svg │ ├── moon.svg │ ├── image.svg │ └── confirm.svg ├── masks │ ├── typing.ts │ ├── build.ts │ └── index.ts ├── store │ ├── index.ts │ └── customCss.ts ├── typing.ts ├── azure.ts ├── utils │ ├── clone.ts │ ├── object.ts │ ├── cors.ts │ ├── checkers.ts │ ├── token.ts │ ├── cloud │ │ └── index.ts │ ├── format.ts │ ├── merge.ts │ ├── indexedDB-storage.ts │ ├── audio.ts │ └── store.ts ├── components │ ├── input-range.module.scss │ ├── model-config.module.scss │ ├── auth.module.scss │ ├── input-range.tsx │ ├── error.module.scss │ ├── share.module.scss │ ├── CustomCssProvider.tsx │ ├── message-selector.module.scss │ ├── button.tsx │ └── artifacts.module.scss ├── styles │ ├── animation.scss │ ├── window.scss │ └── highlight.scss ├── page.tsx ├── types │ ├── xlsx.d.ts │ ├── mammoth.d.ts │ └── pdfjs-dist.d.ts ├── config │ ├── client.ts │ └── build.ts ├── polyfill.ts ├── client │ └── controller.ts ├── global.d.ts ├── api │ ├── config │ │ └── route.ts │ └── upstash │ │ └── [action] │ │ └── [...key] │ │ └── route.ts ├── layout.tsx └── command.ts ├── docs ├── images │ ├── cover.png │ ├── demo.png │ ├── more.png │ ├── ocr-1.png │ ├── ocr-2.png │ ├── think.png │ ├── toolbox.gif │ ├── getModels.png │ ├── head-cover.png │ ├── improve-1.png │ ├── improve-2.png │ ├── privacy-1.png │ ├── privacy-2.png │ ├── settings.png │ ├── theme-demo.png │ ├── thinking.png │ ├── upstash-1.png │ ├── upstash-2.png │ ├── upstash-3.png │ ├── upstash-4.png │ ├── upstash-5.png │ ├── upstash-6.png │ ├── upstash-7.png │ ├── details 标签渲染.png │ ├── translate-1.png │ ├── translate-2.png │ ├── enable-actions.jpg │ ├── expansion-rules.gif │ ├── floating-button.png │ ├── icon_position.png │ ├── select_labels.png │ ├── completions-stat.png │ ├── customProvider_1.png │ ├── customProvider_2.png │ ├── customProvider_3.png │ ├── model-description.png │ ├── enable-actions-sync.jpg │ └── vercel │ │ ├── vercel-create-1.jpg │ │ ├── vercel-create-2.jpg │ │ ├── vercel-create-3.jpg │ │ ├── vercel-env-edit.jpg │ │ └── vercel-redeploy.jpg ├── translation.md ├── synchronise-chat-logs-cn.md ├── synchronise-chat-logs-ko.md ├── synchronise-chat-logs-ja.md ├── synchronise-chat-logs-en.md ├── synchronise-chat-logs-es.md ├── vercel-cn.md ├── vercel-ja.md ├── cloudflare-pages-ko.md ├── vercel-ko.md ├── cloudflare-pages-cn.md ├── cloudflare-pages-ja.md ├── cloudflare-pages-en.md ├── cloudflare-pages-es.md └── vercel-es.md ├── .lintstagedrc.json ├── .prettierrc.js ├── .babelrc ├── .github ├── workflows │ ├── issue-translator.yml │ ├── remove_deploy_preview.yml │ └── sync.yml ├── dependabot.yml └── ISSUE_TEMPLATE │ └── feature_request.yml ├── .gitpod.yml ├── .gitignore ├── tsconfig.json ├── LICENSE ├── docker-compose.yml ├── .dockerignore └── .env.template /.eslintignore: -------------------------------------------------------------------------------- 1 | public/serviceWorker.js -------------------------------------------------------------------------------- /scripts/.gitignore: -------------------------------------------------------------------------------- 1 | proxychains.conf 2 | -------------------------------------------------------------------------------- /src-tauri/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | tauri_build::build() 3 | } 4 | -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "github": { 3 | "silent": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /public/macos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QAbot-zh/ChatGPT-Next-Web/HEAD/public/macos.png -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals", 3 | "plugins": ["prettier"] 4 | } 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx lint-staged -------------------------------------------------------------------------------- /app/icons/bot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QAbot-zh/ChatGPT-Next-Web/HEAD/app/icons/bot.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QAbot-zh/ChatGPT-Next-Web/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /app/icons/chatgpt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QAbot-zh/ChatGPT-Next-Web/HEAD/app/icons/chatgpt.png -------------------------------------------------------------------------------- /docs/images/cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QAbot-zh/ChatGPT-Next-Web/HEAD/docs/images/cover.png -------------------------------------------------------------------------------- /docs/images/demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QAbot-zh/ChatGPT-Next-Web/HEAD/docs/images/demo.png -------------------------------------------------------------------------------- /docs/images/more.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QAbot-zh/ChatGPT-Next-Web/HEAD/docs/images/more.png -------------------------------------------------------------------------------- /docs/images/ocr-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QAbot-zh/ChatGPT-Next-Web/HEAD/docs/images/ocr-1.png -------------------------------------------------------------------------------- /docs/images/ocr-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QAbot-zh/ChatGPT-Next-Web/HEAD/docs/images/ocr-2.png -------------------------------------------------------------------------------- /docs/images/think.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QAbot-zh/ChatGPT-Next-Web/HEAD/docs/images/think.png -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: / 3 | User-agent: vitals.vercel-insights.com 4 | Allow: / -------------------------------------------------------------------------------- /docs/images/toolbox.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QAbot-zh/ChatGPT-Next-Web/HEAD/docs/images/toolbox.gif -------------------------------------------------------------------------------- /src-tauri/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | -------------------------------------------------------------------------------- /docs/images/getModels.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QAbot-zh/ChatGPT-Next-Web/HEAD/docs/images/getModels.png -------------------------------------------------------------------------------- /docs/images/head-cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QAbot-zh/ChatGPT-Next-Web/HEAD/docs/images/head-cover.png -------------------------------------------------------------------------------- /docs/images/improve-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QAbot-zh/ChatGPT-Next-Web/HEAD/docs/images/improve-1.png -------------------------------------------------------------------------------- /docs/images/improve-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QAbot-zh/ChatGPT-Next-Web/HEAD/docs/images/improve-2.png -------------------------------------------------------------------------------- /docs/images/privacy-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QAbot-zh/ChatGPT-Next-Web/HEAD/docs/images/privacy-1.png -------------------------------------------------------------------------------- /docs/images/privacy-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QAbot-zh/ChatGPT-Next-Web/HEAD/docs/images/privacy-2.png -------------------------------------------------------------------------------- /docs/images/settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QAbot-zh/ChatGPT-Next-Web/HEAD/docs/images/settings.png -------------------------------------------------------------------------------- /docs/images/theme-demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QAbot-zh/ChatGPT-Next-Web/HEAD/docs/images/theme-demo.png -------------------------------------------------------------------------------- /docs/images/thinking.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QAbot-zh/ChatGPT-Next-Web/HEAD/docs/images/thinking.png -------------------------------------------------------------------------------- /docs/images/upstash-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QAbot-zh/ChatGPT-Next-Web/HEAD/docs/images/upstash-1.png -------------------------------------------------------------------------------- /docs/images/upstash-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QAbot-zh/ChatGPT-Next-Web/HEAD/docs/images/upstash-2.png -------------------------------------------------------------------------------- /docs/images/upstash-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QAbot-zh/ChatGPT-Next-Web/HEAD/docs/images/upstash-3.png -------------------------------------------------------------------------------- /docs/images/upstash-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QAbot-zh/ChatGPT-Next-Web/HEAD/docs/images/upstash-4.png -------------------------------------------------------------------------------- /docs/images/upstash-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QAbot-zh/ChatGPT-Next-Web/HEAD/docs/images/upstash-5.png -------------------------------------------------------------------------------- /docs/images/upstash-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QAbot-zh/ChatGPT-Next-Web/HEAD/docs/images/upstash-6.png -------------------------------------------------------------------------------- /docs/images/upstash-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QAbot-zh/ChatGPT-Next-Web/HEAD/docs/images/upstash-7.png -------------------------------------------------------------------------------- /public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QAbot-zh/ChatGPT-Next-Web/HEAD/public/favicon-16x16.png -------------------------------------------------------------------------------- /public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QAbot-zh/ChatGPT-Next-Web/HEAD/public/favicon-32x32.png -------------------------------------------------------------------------------- /src-tauri/icons/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QAbot-zh/ChatGPT-Next-Web/HEAD/src-tauri/icons/32x32.png -------------------------------------------------------------------------------- /src-tauri/icons/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QAbot-zh/ChatGPT-Next-Web/HEAD/src-tauri/icons/icon.icns -------------------------------------------------------------------------------- /src-tauri/icons/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QAbot-zh/ChatGPT-Next-Web/HEAD/src-tauri/icons/icon.ico -------------------------------------------------------------------------------- /src-tauri/icons/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QAbot-zh/ChatGPT-Next-Web/HEAD/src-tauri/icons/icon.png -------------------------------------------------------------------------------- /docs/images/details 标签渲染.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QAbot-zh/ChatGPT-Next-Web/HEAD/docs/images/details 标签渲染.png -------------------------------------------------------------------------------- /docs/images/translate-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QAbot-zh/ChatGPT-Next-Web/HEAD/docs/images/translate-1.png -------------------------------------------------------------------------------- /docs/images/translate-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QAbot-zh/ChatGPT-Next-Web/HEAD/docs/images/translate-2.png -------------------------------------------------------------------------------- /public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QAbot-zh/ChatGPT-Next-Web/HEAD/public/apple-touch-icon.png -------------------------------------------------------------------------------- /src-tauri/icons/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QAbot-zh/ChatGPT-Next-Web/HEAD/src-tauri/icons/128x128.png -------------------------------------------------------------------------------- /docs/images/enable-actions.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QAbot-zh/ChatGPT-Next-Web/HEAD/docs/images/enable-actions.jpg -------------------------------------------------------------------------------- /docs/images/expansion-rules.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QAbot-zh/ChatGPT-Next-Web/HEAD/docs/images/expansion-rules.gif -------------------------------------------------------------------------------- /docs/images/floating-button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QAbot-zh/ChatGPT-Next-Web/HEAD/docs/images/floating-button.png -------------------------------------------------------------------------------- /docs/images/icon_position.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QAbot-zh/ChatGPT-Next-Web/HEAD/docs/images/icon_position.png -------------------------------------------------------------------------------- /docs/images/select_labels.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QAbot-zh/ChatGPT-Next-Web/HEAD/docs/images/select_labels.png -------------------------------------------------------------------------------- /src-tauri/icons/128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QAbot-zh/ChatGPT-Next-Web/HEAD/src-tauri/icons/128x128@2x.png -------------------------------------------------------------------------------- /src-tauri/icons/StoreLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QAbot-zh/ChatGPT-Next-Web/HEAD/src-tauri/icons/StoreLogo.png -------------------------------------------------------------------------------- /docs/images/completions-stat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QAbot-zh/ChatGPT-Next-Web/HEAD/docs/images/completions-stat.png -------------------------------------------------------------------------------- /docs/images/customProvider_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QAbot-zh/ChatGPT-Next-Web/HEAD/docs/images/customProvider_1.png -------------------------------------------------------------------------------- /docs/images/customProvider_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QAbot-zh/ChatGPT-Next-Web/HEAD/docs/images/customProvider_2.png -------------------------------------------------------------------------------- /docs/images/customProvider_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QAbot-zh/ChatGPT-Next-Web/HEAD/docs/images/customProvider_3.png -------------------------------------------------------------------------------- /docs/images/model-description.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QAbot-zh/ChatGPT-Next-Web/HEAD/docs/images/model-description.png -------------------------------------------------------------------------------- /public/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QAbot-zh/ChatGPT-Next-Web/HEAD/public/android-chrome-192x192.png -------------------------------------------------------------------------------- /public/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QAbot-zh/ChatGPT-Next-Web/HEAD/public/android-chrome-512x512.png -------------------------------------------------------------------------------- /docs/images/enable-actions-sync.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QAbot-zh/ChatGPT-Next-Web/HEAD/docs/images/enable-actions-sync.jpg -------------------------------------------------------------------------------- /src-tauri/icons/Square30x30Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QAbot-zh/ChatGPT-Next-Web/HEAD/src-tauri/icons/Square30x30Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square44x44Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QAbot-zh/ChatGPT-Next-Web/HEAD/src-tauri/icons/Square44x44Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square71x71Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QAbot-zh/ChatGPT-Next-Web/HEAD/src-tauri/icons/Square71x71Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square89x89Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QAbot-zh/ChatGPT-Next-Web/HEAD/src-tauri/icons/Square89x89Logo.png -------------------------------------------------------------------------------- /docs/images/vercel/vercel-create-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QAbot-zh/ChatGPT-Next-Web/HEAD/docs/images/vercel/vercel-create-1.jpg -------------------------------------------------------------------------------- /docs/images/vercel/vercel-create-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QAbot-zh/ChatGPT-Next-Web/HEAD/docs/images/vercel/vercel-create-2.jpg -------------------------------------------------------------------------------- /docs/images/vercel/vercel-create-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QAbot-zh/ChatGPT-Next-Web/HEAD/docs/images/vercel/vercel-create-3.jpg -------------------------------------------------------------------------------- /docs/images/vercel/vercel-env-edit.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QAbot-zh/ChatGPT-Next-Web/HEAD/docs/images/vercel/vercel-env-edit.jpg -------------------------------------------------------------------------------- /docs/images/vercel/vercel-redeploy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QAbot-zh/ChatGPT-Next-Web/HEAD/docs/images/vercel/vercel-redeploy.jpg -------------------------------------------------------------------------------- /src-tauri/icons/Square107x107Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QAbot-zh/ChatGPT-Next-Web/HEAD/src-tauri/icons/Square107x107Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square142x142Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QAbot-zh/ChatGPT-Next-Web/HEAD/src-tauri/icons/Square142x142Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square150x150Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QAbot-zh/ChatGPT-Next-Web/HEAD/src-tauri/icons/Square150x150Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square284x284Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QAbot-zh/ChatGPT-Next-Web/HEAD/src-tauri/icons/Square284x284Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square310x310Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QAbot-zh/ChatGPT-Next-Web/HEAD/src-tauri/icons/Square310x310Logo.png -------------------------------------------------------------------------------- /.lintstagedrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "./app/**/*.{js,ts,jsx,tsx,json,html,css,md}": [ 3 | "eslint --fix", 4 | "prettier --write" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 80, 3 | tabWidth: 2, 4 | useTabs: false, 5 | semi: true, 6 | singleQuote: false, 7 | trailingComma: 'all', 8 | bracketSpacing: true, 9 | arrowParens: 'always', 10 | }; 11 | -------------------------------------------------------------------------------- /app/masks/typing.ts: -------------------------------------------------------------------------------- 1 | import { ModelConfig } from "../store"; 2 | import { type Mask } from "../store/mask"; 3 | 4 | export type BuiltinMask = Omit & { 5 | builtin: Boolean; 6 | modelConfig: Partial; 7 | }; 8 | -------------------------------------------------------------------------------- /scripts/proxychains.template.conf: -------------------------------------------------------------------------------- 1 | strict_chain 2 | proxy_dns 3 | 4 | remote_dns_subnet 224 5 | 6 | tcp_read_time_out 15000 7 | tcp_connect_time_out 8000 8 | 9 | localnet 127.0.0.0/255.0.0.0 10 | 11 | [ProxyList] 12 | socks4 127.0.0.1 9050 13 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "next/babel", 5 | { 6 | "preset-env": { 7 | "targets": { 8 | "browsers": ["> 0.25%, not dead"] 9 | } 10 | } 11 | } 12 | ] 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /scripts/init-proxy.sh: -------------------------------------------------------------------------------- 1 | dir="$(dirname "$0")" 2 | config=$dir/proxychains.conf 3 | host_ip=$(grep nameserver /etc/resolv.conf | sed 's/nameserver //') 4 | echo "proxying to $host_ip" 5 | cp $dir/proxychains.template.conf $config 6 | sed -i "\$s/.*/http $host_ip 7890/" $config 7 | -------------------------------------------------------------------------------- /app/icons/zoom.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/store/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./chat"; 2 | export * from "./update"; 3 | export * from "./access"; 4 | export * from "./config"; 5 | export * from "./provider"; 6 | export * from "./customCss"; 7 | export * from "./expansionRules"; 8 | export * from "./mask"; 9 | export * from "./prompt"; 10 | -------------------------------------------------------------------------------- /app/icons/home.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/typing.ts: -------------------------------------------------------------------------------- 1 | export type Updater = (updater: (value: T) => void) => void; 2 | 3 | export const ROLES = ["system", "user", "assistant"] as const; 4 | export type MessageRole = (typeof ROLES)[number]; 5 | 6 | export interface RequestMessage { 7 | role: MessageRole; 8 | content: string; 9 | } 10 | -------------------------------------------------------------------------------- /app/azure.ts: -------------------------------------------------------------------------------- 1 | export function makeAzurePath(path: string, apiVersion: string) { 2 | // should omit /v1 prefix 3 | path = path.replaceAll("v1/", ""); 4 | 5 | // should add api-key to query string 6 | path += `${path.includes("?") ? "&" : "?"}api-version=${apiVersion}`; 7 | 8 | return path; 9 | } 10 | -------------------------------------------------------------------------------- /app/icons/edit_input.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/utils/clone.ts: -------------------------------------------------------------------------------- 1 | export function deepClone(obj: T) { 2 | return JSON.parse(JSON.stringify(obj)); 3 | } 4 | 5 | export function ensure( 6 | obj: T, 7 | keys: Array<[keyof T][number]>, 8 | ) { 9 | return keys.every( 10 | (k) => obj[k] !== undefined && obj[k] !== null && obj[k] !== "", 11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /app/components/input-range.module.scss: -------------------------------------------------------------------------------- 1 | .input-range { 2 | border: var(--border-in-light); 3 | border-radius: 10px; 4 | padding: 5px 10px 5px 10px; 5 | font-size: var(--text-xs); 6 | display: flex; 7 | justify-content: space-between; 8 | max-width: 40%; 9 | 10 | input[type="range"] { 11 | max-width: calc(100% - 34px); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /app/icons/bot-ai.svg: -------------------------------------------------------------------------------- 1 | Anthropic -------------------------------------------------------------------------------- /src-tauri/src/main.rs: -------------------------------------------------------------------------------- 1 | // Prevents additional console window on Windows in release, DO NOT REMOVE!! 2 | #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] 3 | 4 | fn main() { 5 | tauri::Builder::default() 6 | .plugin(tauri_plugin_window_state::Builder::default().build()) 7 | .run(tauri::generate_context!()) 8 | .expect("error while running tauri application"); 9 | } 10 | -------------------------------------------------------------------------------- /app/icons/discovery.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /app/icons/continue.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/serviceWorkerRegister.js: -------------------------------------------------------------------------------- 1 | if ('serviceWorker' in navigator) { 2 | window.addEventListener('load', function () { 3 | navigator.serviceWorker.register('/serviceWorker.js').then(function (registration) { 4 | console.log('ServiceWorker registration successful with scope: ', registration.scope); 5 | }, function (err) { 6 | console.error('ServiceWorker registration failed: ', err); 7 | }); 8 | }); 9 | } -------------------------------------------------------------------------------- /app/icons/collapse.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/components/model-config.module.scss: -------------------------------------------------------------------------------- 1 | .select-compress-model { 2 | width: 60%; 3 | select { 4 | max-width: 100%; 5 | white-space: normal; 6 | } 7 | } 8 | .select-translate-model { 9 | width: 60%; 10 | select { 11 | max-width: 100%; 12 | white-space: normal; 13 | } 14 | } 15 | .select-ocr-model { 16 | width: 60%; 17 | select { 18 | max-width: 100%; 19 | white-space: normal; 20 | } 21 | } -------------------------------------------------------------------------------- /app/icons/expand.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/styles/animation.scss: -------------------------------------------------------------------------------- 1 | @keyframes slide-in { 2 | from { 3 | opacity: 0; 4 | transform: translateY(20px); 5 | } 6 | 7 | to { 8 | opacity: 1; 9 | transform: translateY(0px); 10 | } 11 | } 12 | 13 | @keyframes slide-in-from-top { 14 | from { 15 | opacity: 0; 16 | transform: translateY(-20px); 17 | } 18 | 19 | to { 20 | opacity: 1; 21 | transform: translateY(0px); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /.github/workflows/issue-translator.yml: -------------------------------------------------------------------------------- 1 | name: Issue Translator 2 | on: 3 | issue_comment: 4 | types: [created] 5 | issues: 6 | types: [opened] 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: usthe/issues-translate-action@v2.7 13 | with: 14 | IS_MODIFY_TITLE: false 15 | CUSTOM_BOT_NOTE: Bot detected the issue body's language is not English, translate it automatically. 16 | -------------------------------------------------------------------------------- /app/icons/llm-icons/suno.svg: -------------------------------------------------------------------------------- 1 | 2 | Suno 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/icons/eraser.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/page.tsx: -------------------------------------------------------------------------------- 1 | import { Analytics } from "@vercel/analytics/react"; 2 | 3 | import { Home } from "./components/home"; 4 | 5 | import { getServerSideConfig } from "./config/server"; 6 | 7 | const serverConfig = getServerSideConfig(); 8 | 9 | export default async function App() { 10 | return ( 11 | <> 12 | 13 | {serverConfig?.isVercel && ( 14 | <> 15 | 16 | 17 | )} 18 | 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /app/utils/object.ts: -------------------------------------------------------------------------------- 1 | export function omit( 2 | obj: T, 3 | ...keys: U 4 | ): Omit { 5 | const ret: any = { ...obj }; 6 | keys.forEach((key) => delete ret[key]); 7 | return ret; 8 | } 9 | 10 | export function pick( 11 | obj: T, 12 | ...keys: U 13 | ): Pick { 14 | const ret: any = {}; 15 | keys.forEach((key) => (ret[key] = obj[key])); 16 | return ret; 17 | } 18 | -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | # This configuration file was automatically generated by Gitpod. 2 | # Please adjust to your needs (see https://www.gitpod.io/docs/introduction/learn-gitpod/gitpod-yaml) 3 | # and commit this file to your remote git repository to share the goodness with others. 4 | 5 | # Learn more from ready-to-use templates: https://www.gitpod.io/docs/introduction/getting-started/quickstart 6 | 7 | tasks: 8 | - init: yarn install && yarn run dev 9 | command: yarn run dev 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/icons/speak.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "NextChat", 3 | "short_name": "NextChat", 4 | "icons": [ 5 | { 6 | "src": "/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/android-chrome-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "start_url": "/", 17 | "theme_color": "#ffffff", 18 | "background_color": "#ffffff", 19 | "display": "standalone" 20 | } -------------------------------------------------------------------------------- /app/icons/speak-stop.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/types/xlsx.d.ts: -------------------------------------------------------------------------------- 1 | declare module "xlsx" { 2 | export function read(data: Uint8Array, options: { type: string }): Workbook; 3 | 4 | export interface Workbook { 5 | SheetNames: string[]; 6 | Sheets: { [key: string]: WorkSheet }; 7 | } 8 | 9 | export interface WorkSheet { 10 | [key: string]: any; 11 | } 12 | 13 | export const utils: { 14 | sheet_to_json( 15 | worksheet: WorkSheet, 16 | options?: { header?: number | string[] }, 17 | ): T[]; 18 | }; 19 | } 20 | -------------------------------------------------------------------------------- /app/utils/cors.ts: -------------------------------------------------------------------------------- 1 | import { getClientConfig } from "../config/client"; 2 | import { ApiPath, DEFAULT_API_HOST } from "../constant"; 3 | 4 | export function corsPath(path: string) { 5 | const baseUrl = getClientConfig()?.isApp ? `${DEFAULT_API_HOST}` : ""; 6 | 7 | if (baseUrl === "" && path === "") { 8 | return ""; 9 | } 10 | if (!path.startsWith("/")) { 11 | path = "/" + path; 12 | } 13 | 14 | if (!path.endsWith("/")) { 15 | path += "/"; 16 | } 17 | 18 | return `${baseUrl}${path}`; 19 | } 20 | -------------------------------------------------------------------------------- /app/types/mammoth.d.ts: -------------------------------------------------------------------------------- 1 | declare module "mammoth" { 2 | interface MammothOptions { 3 | arrayBuffer: ArrayBuffer; 4 | styleMap?: string; 5 | } 6 | 7 | interface MammothResult { 8 | value: string; 9 | messages: Array<{ 10 | type: string; 11 | message: string; 12 | }>; 13 | } 14 | 15 | export function convertToHtml( 16 | options: MammothOptions, 17 | ): Promise; 18 | export function extractRawText( 19 | options: MammothOptions, 20 | ): Promise; 21 | } 22 | -------------------------------------------------------------------------------- /app/icons/left.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/icons/down.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/translation.md: -------------------------------------------------------------------------------- 1 | # How to add a new translation? 2 | 3 | Assume that we are adding a new translation for `new`. 4 | 5 | 1. copy `app/locales/en.ts` to `app/locales/new.ts`; 6 | 2. edit `new.ts`, change `const en: LocaleType = ` to `const new: PartialLocaleType`, and `export default new;`; 7 | 3. edit `app/locales/index.ts`: 8 | 4. `import new from './new.ts'`; 9 | 5. add `new` to `ALL_LANGS`; 10 | 6. add `new: "new lang"` to `ALL_LANG_OPTIONS`; 11 | 7. translate the strings in `new.ts`; 12 | 8. submit a pull request, and the author will merge it. 13 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "npm" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /app/icons/share.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/icons/llm-icons/flux.svg: -------------------------------------------------------------------------------- 1 | 2 | Flux 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/icons/plus.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Layer 1 10 | 11 | plus 12 | 13 | -------------------------------------------------------------------------------- /app/icons/cloud.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | .pnpm-debug.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | dev 38 | 39 | .vscode 40 | .idea 41 | 42 | # docker-compose env files 43 | .env 44 | 45 | *.key 46 | *.key.pub -------------------------------------------------------------------------------- /app/masks/build.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import path from "path"; 3 | import { CN_MASKS } from "./cn"; 4 | import { TW_MASKS } from "./tw"; 5 | import { EN_MASKS } from "./en"; 6 | 7 | import { type BuiltinMask } from "./typing"; 8 | 9 | const BUILTIN_MASKS: Record = { 10 | cn: CN_MASKS, 11 | tw: TW_MASKS, 12 | en: EN_MASKS, 13 | }; 14 | 15 | const dirname = path.dirname(__filename); 16 | 17 | fs.writeFile( 18 | dirname + "/../../public/masks.json", 19 | JSON.stringify(BUILTIN_MASKS, null, 4), 20 | function (error) { 21 | if (error) { 22 | console.error("[Build] failed to build masks", error); 23 | } 24 | }, 25 | ); 26 | -------------------------------------------------------------------------------- /app/icons/incognito.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/icons/bottom.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/components/auth.module.scss: -------------------------------------------------------------------------------- 1 | .auth-page { 2 | display: flex; 3 | justify-content: center; 4 | align-items: center; 5 | height: 100%; 6 | width: 100%; 7 | flex-direction: column; 8 | 9 | .auth-logo { 10 | transform: scale(1.4); 11 | } 12 | 13 | .auth-title { 14 | font-size: var(--text-2xl); 15 | font-weight: bold; 16 | line-height: 2; 17 | } 18 | 19 | .auth-tips { 20 | font-size: var(--text-sm); 21 | } 22 | 23 | .auth-input { 24 | margin: 3vh 0; 25 | } 26 | 27 | .auth-actions { 28 | display: flex; 29 | justify-content: center; 30 | flex-direction: column; 31 | 32 | button:not(:last-child) { 33 | margin-bottom: 10px; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/types/pdfjs-dist.d.ts: -------------------------------------------------------------------------------- 1 | declare module "pdfjs-dist" { 2 | export function getDocument(options: { data: ArrayBuffer }): { 3 | promise: Promise; 4 | }; 5 | 6 | export interface PDFDocumentProxy { 7 | numPages: number; 8 | getPage(pageNumber: number): Promise; 9 | } 10 | 11 | export interface PDFPageProxy { 12 | getTextContent(): Promise; 13 | } 14 | 15 | export interface PDFTextContent { 16 | items: Array<{ str: string }>; 17 | } 18 | 19 | export const GlobalWorkerOptions: { 20 | workerSrc: any; 21 | }; 22 | } 23 | 24 | declare module "pdfjs-dist/build/pdf.worker.mjs" { 25 | const worker: any; 26 | export default worker; 27 | } 28 | -------------------------------------------------------------------------------- /app/icons/llm-icons/default-ai.svg: -------------------------------------------------------------------------------- 1 | 2 | Unknown AI 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/config/client.ts: -------------------------------------------------------------------------------- 1 | import { BuildConfig, getBuildConfig } from "./build"; 2 | 3 | export function getClientConfig() { 4 | if (typeof document !== "undefined") { 5 | // client side 6 | return JSON.parse(queryMeta("config")) as BuildConfig; 7 | } 8 | 9 | if (typeof process !== "undefined") { 10 | // server side 11 | return getBuildConfig(); 12 | } 13 | } 14 | 15 | function queryMeta(key: string, defaultValue?: string): string { 16 | let ret: string; 17 | if (document) { 18 | const meta = document.head.querySelector( 19 | `meta[name='${key}']`, 20 | ) as HTMLMetaElement; 21 | ret = meta?.content ?? ""; 22 | } else { 23 | ret = defaultValue ?? ""; 24 | } 25 | 26 | return ret; 27 | } 28 | -------------------------------------------------------------------------------- /app/icons/shortcutkey.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/synchronise-chat-logs-cn.md: -------------------------------------------------------------------------------- 1 | # 同步聊天记录 2 | ## 准备工作 3 | - GitHub账号 4 | - 拥有自己搭建过的ChatGPT-Next-Web的服务器 5 | - [UpStash](https://upstash.com) 6 | 7 | ## 开始教程 8 | 1. 注册UpStash账号 9 | 2. 创建数据库 10 | 11 | ![注册登录](./images/upstash-1.png) 12 | 13 | ![创建数据库](./images/upstash-2.png) 14 | 15 | ![选择服务器](./images/upstash-3.png) 16 | 17 | 3. 找到REST API,分别复制UPSTASH_REDIS_REST_URL和UPSTASH_REDIS_REST_TOKEN(⚠切记⚠:不要泄露Token!) 18 | 19 | ![复制](./images/upstash-4.png) 20 | 21 | 4. UPSTASH_REDIS_REST_URL和UPSTASH_REDIS_REST_TOKEN复制到你的同步配置,点击**检查可用性** 22 | 23 | ![同步1](./images/upstash-5.png) 24 | 25 | 如果没什么问题,那就成功了 26 | 27 | ![同步可用性完成的样子](./images/upstash-6.png) 28 | 29 | 5. Success! 30 | 31 | ![好耶~!](./images/upstash-7.png) 32 | -------------------------------------------------------------------------------- /app/polyfill.ts: -------------------------------------------------------------------------------- 1 | declare global { 2 | interface Array { 3 | at(index: number): T | undefined; 4 | } 5 | } 6 | 7 | if (!Array.prototype.at) { 8 | Array.prototype.at = function (index: number) { 9 | // Get the length of the array 10 | const length = this.length; 11 | 12 | // Convert negative index to a positive index 13 | if (index < 0) { 14 | index = length + index; 15 | } 16 | 17 | // Return undefined if the index is out of range 18 | if (index < 0 || index >= length) { 19 | return undefined; 20 | } 21 | 22 | // Use Array.prototype.slice method to get value at the specified index 23 | return Array.prototype.slice.call(this, index, index + 1)[0]; 24 | }; 25 | } 26 | 27 | export {}; 28 | -------------------------------------------------------------------------------- /app/utils/checkers.ts: -------------------------------------------------------------------------------- 1 | import { useAccessStore } from "../store/access"; 2 | import { useAppConfig } from "../store/config"; 3 | import { collectModels } from "./model"; 4 | 5 | export function identifyDefaultClaudeModel(modelName: string) { 6 | const accessStore = useAccessStore.getState(); 7 | const configStore = useAppConfig.getState(); 8 | 9 | const allModals = collectModels( 10 | configStore.models, 11 | [configStore.customModels, accessStore.customModels].join(","), 12 | accessStore, 13 | ); 14 | 15 | const modelMeta = allModals.find((m) => m.name === modelName); 16 | 17 | return ( 18 | modelName.startsWith("claude") && 19 | modelMeta && 20 | modelMeta.provider?.providerType === "anthropic" 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /app/icons/voice-white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2015", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true, 17 | "plugins": [ 18 | { 19 | "name": "next" 20 | } 21 | ], 22 | "paths": { 23 | "@/*": ["./*"] 24 | } 25 | }, 26 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "app/calcTextareaHeight.ts"], 27 | "exclude": ["node_modules"] 28 | } 29 | -------------------------------------------------------------------------------- /app/utils/token.ts: -------------------------------------------------------------------------------- 1 | import { encodingForModel } from "js-tiktoken"; 2 | 3 | const enc = encodingForModel("gpt-3.5-turbo"); 4 | 5 | export function estimateTokenLengthInLLM(input: string): number { 6 | return enc.encode(input).length; 7 | } 8 | 9 | export function estimateTokenLength(input: string): number { 10 | let tokenLength = 0; 11 | 12 | for (let i = 0; i < input.length; i++) { 13 | const charCode = input.charCodeAt(i); 14 | 15 | if (charCode < 128) { 16 | // ASCII character 17 | if (charCode <= 122 && charCode >= 65) { 18 | // a-Z 19 | tokenLength += 0.25; 20 | } else { 21 | tokenLength += 0.5; 22 | } 23 | } else { 24 | // Unicode character 25 | tokenLength += 1.5; 26 | } 27 | } 28 | 29 | return tokenLength; 30 | } 31 | -------------------------------------------------------------------------------- /docs/synchronise-chat-logs-ko.md: -------------------------------------------------------------------------------- 1 | # UpStash를 사용하여 채팅 기록 동기화 2 | ## 사전 준비물 3 | - GitHub 계정 4 | - 자체 ChatGPT-Next-Web 서버 설정 5 | - [UpStash](https://upstash.com) 6 | 7 | ## 시작하기 8 | 1. UpStash 계정 등록 9 | 2. 데이터베이스 생성 10 | 11 | ![등록 및 로그인](./images/upstash-1.png) 12 | 13 | ![데이터베이스 생성](./images/upstash-2.png) 14 | 15 | ![서버 선택](./images/upstash-3.png) 16 | 17 | 3. REST API를 찾아 UPSTASH_REDIS_REST_URL 및 UPSTASH_REDIS_REST_TOKEN을 복사합니다 (⚠주의⚠: 토큰을 공유하지 마십시오!) 18 | 19 | ![복사](./images/upstash-4.png) 20 | 21 | 4. UPSTASH_REDIS_REST_URL 및 UPSTASH_REDIS_REST_TOKEN을 동기화 구성에 복사한 다음 **가용성 확인**을 클릭합니다. 22 | 23 | ![동기화 1](./images/upstash-5.png) 24 | 25 | 모든 것이 정상인 경우,이 단계를 성공적으로 완료했습니다. 26 | 27 | ![동기화 가용성 확인 완료](./images/upstash-6.png) 28 | 29 | 5. 성공! 30 | 31 | ![잘 했어요~!](./images/upstash-7.png) -------------------------------------------------------------------------------- /app/icons/dark.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/styles/window.scss: -------------------------------------------------------------------------------- 1 | .window-header { 2 | padding: 14px 20px; 3 | border-bottom: rgba(0, 0, 0, 0.1) 1px solid; 4 | position: relative; 5 | 6 | display: flex; 7 | justify-content: space-between; 8 | align-items: center; 9 | } 10 | 11 | .window-header-title { 12 | max-width: calc(100% - 100px); 13 | overflow: hidden; 14 | 15 | .window-header-main-title { 16 | font-size: var(--text-xl); 17 | font-weight: bolder; 18 | overflow: hidden; 19 | text-overflow: ellipsis; 20 | white-space: nowrap; 21 | display: block; 22 | max-width: 50vw; 23 | } 24 | 25 | .window-header-sub-title { 26 | font-size: var(--text-sm); 27 | } 28 | } 29 | 30 | .window-actions { 31 | display: inline-flex; 32 | } 33 | 34 | .window-action-button:not(:first-child) { 35 | margin-left: 10px; 36 | } 37 | -------------------------------------------------------------------------------- /docs/synchronise-chat-logs-ja.md: -------------------------------------------------------------------------------- 1 | # UpStashを使用してチャットログを同期する 2 | ## 事前準備 3 | - GitHubアカウント 4 | - 自分自身でChatGPT-Next-Webのサーバーをセットアップしていること 5 | - [UpStash](https://upstash.com) 6 | 7 | ## 始める 8 | 1. UpStashアカウントを登録します。 9 | 2. データベースを作成します。 10 | 11 | ![登録とログイン](./images/upstash-1.png) 12 | 13 | ![データベースの作成](./images/upstash-2.png) 14 | 15 | ![サーバーの選択](./images/upstash-3.png) 16 | 17 | 3. REST APIを見つけ、UPSTASH_REDIS_REST_URLとUPSTASH_REDIS_REST_TOKENをコピーします(⚠重要⚠:トークンを共有しないでください!) 18 | 19 | ![コピー](./images/upstash-4.png) 20 | 21 | 4. UPSTASH_REDIS_REST_URLとUPSTASH_REDIS_REST_TOKENを同期設定にコピーし、次に「可用性を確認」をクリックします。 22 | 23 | ![同期1](./images/upstash-5.png) 24 | 25 | すべてが正常であれば、このステップは成功です。 26 | 27 | ![同期可用性チェックが完了しました](./images/upstash-6.png) 28 | 29 | 5. 成功! 30 | 31 | ![お疲れ様でした~!](./images/upstash-7.png) -------------------------------------------------------------------------------- /app/icons/close.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/icons/send-white.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/icons/ai-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | AI 16 | 17 | 18 | 19 | 27 | 28 | -------------------------------------------------------------------------------- /app/icons/rename.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/icons/return.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/store/customCss.ts: -------------------------------------------------------------------------------- 1 | // store/customCss.ts 2 | import { StoreKey } from "../constant"; 3 | import { createPersistStore } from "../utils/store"; 4 | 5 | export interface CustomCssConfig { 6 | enabled: boolean; 7 | content: string; 8 | lastUpdated: number; 9 | } 10 | 11 | export const DEFAULT_CUSTOM_CSS_CONFIG: CustomCssConfig = { 12 | enabled: false, 13 | content: "", 14 | lastUpdated: 0, 15 | }; 16 | 17 | export const useCustomCssStore = createPersistStore( 18 | DEFAULT_CUSTOM_CSS_CONFIG, 19 | (set, get) => ({ 20 | enable() { 21 | set((state) => ({ enabled: true })); 22 | }, 23 | disable() { 24 | set((state) => ({ enabled: false })); 25 | }, 26 | reset() { 27 | set(() => ({ ...DEFAULT_CUSTOM_CSS_CONFIG })); 28 | }, 29 | }), 30 | { 31 | name: StoreKey.CustomCSS, 32 | version: 1, 33 | }, 34 | ); 35 | -------------------------------------------------------------------------------- /app/icons/llm-icons/gemini.svg: -------------------------------------------------------------------------------- 1 | 2 | Gemini 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 14 | 15 | -------------------------------------------------------------------------------- /app/icons/llm-icons/yi.svg: -------------------------------------------------------------------------------- 1 | 2 | Yi 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/masks/index.ts: -------------------------------------------------------------------------------- 1 | import { Mask } from "../store/mask"; 2 | import { CN_MASKS } from "./cn"; 3 | import { TW_MASKS } from "./tw"; 4 | import { EN_MASKS } from "./en"; 5 | 6 | import { type BuiltinMask } from "./typing"; 7 | export { type BuiltinMask } from "./typing"; 8 | 9 | export const BUILTIN_MASK_ID = 100000; 10 | 11 | export const BUILTIN_MASK_STORE = { 12 | buildinId: BUILTIN_MASK_ID, 13 | masks: {} as Record, 14 | get(id?: string) { 15 | if (!id) return undefined; 16 | return this.masks[id] as Mask | undefined; 17 | }, 18 | add(m: BuiltinMask) { 19 | const mask = { ...m, id: this.buildinId++, builtin: true }; 20 | this.masks[mask.id] = mask; 21 | return mask; 22 | }, 23 | }; 24 | 25 | export const BUILTIN_MASKS: BuiltinMask[] = [...CN_MASKS, ...TW_MASKS, ...EN_MASKS].map( 26 | (m) => BUILTIN_MASK_STORE.add(m), 27 | ); 28 | -------------------------------------------------------------------------------- /app/icons/llm-icons/runway.svg: -------------------------------------------------------------------------------- 1 | 2 | Runway 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/icons/llm-icons/grok.svg: -------------------------------------------------------------------------------- 1 | 3 | Grok 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/icons/llm-icons/mistral.svg: -------------------------------------------------------------------------------- 1 | 2 | Mistral 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/icons/llm-icons/stepfun.svg: -------------------------------------------------------------------------------- 1 | 2 | Stepfun 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/icons/llm-icons/cohere.svg: -------------------------------------------------------------------------------- 1 | 2 | Cohere 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/components/input-range.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import styles from "./input-range.module.scss"; 3 | 4 | interface InputRangeProps { 5 | onChange: React.ChangeEventHandler; 6 | title?: string; 7 | value: number | string; 8 | className?: string; 9 | min: string; 10 | max: string; 11 | step: string; 12 | aria: string; 13 | } 14 | 15 | export function InputRange({ 16 | onChange, 17 | title, 18 | value, 19 | className, 20 | min, 21 | max, 22 | step, 23 | aria, 24 | }: InputRangeProps) { 25 | return ( 26 |
27 | {title || value} 28 | 38 |
39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /app/utils/cloud/index.ts: -------------------------------------------------------------------------------- 1 | import { createWebDavClient } from "./webdav"; 2 | import { createUpstashClient } from "./upstash"; 3 | 4 | export enum ProviderType { 5 | WebDAV = "webdav", 6 | UpStash = "upstash", 7 | } 8 | 9 | export const SyncClients = { 10 | [ProviderType.UpStash]: createUpstashClient, 11 | [ProviderType.WebDAV]: createWebDavClient, 12 | } as const; 13 | 14 | type SyncClientConfig = { 15 | [K in keyof typeof SyncClients]: (typeof SyncClients)[K] extends ( 16 | _: infer C, 17 | ) => any 18 | ? C 19 | : never; 20 | }; 21 | 22 | export type SyncClient = { 23 | get: (key: string) => Promise; 24 | set: (key: string, value: string) => Promise; 25 | check: () => Promise; 26 | }; 27 | 28 | export function createSyncClient( 29 | provider: T, 30 | config: SyncClientConfig[T], 31 | ): SyncClient { 32 | return SyncClients[provider](config as any) as any; 33 | } 34 | -------------------------------------------------------------------------------- /app/icons/copy.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/icons/paperclip.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/icons/settings.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/utils/format.ts: -------------------------------------------------------------------------------- 1 | export function prettyObject(msg: any) { 2 | const obj = msg; 3 | if (typeof msg !== "string") { 4 | msg = JSON.stringify(msg, null, " "); 5 | } 6 | if (msg === "{}") { 7 | return obj.toString(); 8 | } 9 | if (msg.startsWith("```json")) { 10 | return msg; 11 | } 12 | return ["```json", msg, "```"].join("\n"); 13 | } 14 | 15 | export function* chunks(s: string, maxBytes = 1000 * 1000) { 16 | const decoder = new TextDecoder("utf-8"); 17 | let buf = new TextEncoder().encode(s); 18 | while (buf.length) { 19 | let i = buf.lastIndexOf(32, maxBytes + 1); 20 | // If no space found, try forward search 21 | if (i < 0) i = buf.indexOf(32, maxBytes); 22 | // If there's no space at all, take all 23 | if (i < 0) i = buf.length; 24 | // This is a safe cut-off point; never half-way a multi-byte 25 | yield decoder.decode(buf.slice(0, i)); 26 | buf = buf.slice(i + 1); // Skip space (if any) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/icons/menu.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/icons/toolbox.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/icons/upload-doc.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/icons/lightOn.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/icons/llm-icons/sparkdesk.svg: -------------------------------------------------------------------------------- 1 | 2 | Spark 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/icons/pause.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/synchronise-chat-logs-en.md: -------------------------------------------------------------------------------- 1 | # Synchronize Chat Logs with UpStash 2 | ## Prerequisites 3 | - GitHub account 4 | - Your own ChatGPT-Next-Web server set up 5 | - [UpStash](https://upstash.com) 6 | 7 | ## Getting Started 8 | 1. Register for an UpStash account. 9 | 2. Create a database. 10 | 11 | ![Register and Login](./images/upstash-1.png) 12 | 13 | ![Create Database](./images/upstash-2.png) 14 | 15 | ![Select Server](./images/upstash-3.png) 16 | 17 | 3. Find the REST API and copy UPSTASH_REDIS_REST_URL and UPSTASH_REDIS_REST_TOKEN (⚠Important⚠: Do not share your token!) 18 | 19 | ![Copy](./images/upstash-4.png) 20 | 21 | 4. Copy UPSTASH_REDIS_REST_URL and UPSTASH_REDIS_REST_TOKEN into your synchronization configuration, then click **Check Availability**. 22 | 23 | ![Synchronize 1](./images/upstash-5.png) 24 | 25 | If everything is in order, you've successfully completed this step. 26 | 27 | ![Sync Availability Check Completed](./images/upstash-6.png) 28 | 29 | 5. Success! 30 | 31 | ![Great job~!](./images/upstash-7.png) -------------------------------------------------------------------------------- /app/icons/reload.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/icons/add.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/client/controller.ts: -------------------------------------------------------------------------------- 1 | // To store message streaming controller 2 | export const ChatControllerPool = { 3 | controllers: {} as Record, 4 | 5 | addController( 6 | sessionId: string, 7 | messageId: string, 8 | controller: AbortController, 9 | ) { 10 | const key = this.key(sessionId, messageId); 11 | this.controllers[key] = controller; 12 | return key; 13 | }, 14 | 15 | stop(sessionId: string, messageId: string) { 16 | const key = this.key(sessionId, messageId); 17 | const controller = this.controllers[key]; 18 | controller?.abort(); 19 | }, 20 | 21 | stopAll() { 22 | Object.values(this.controllers).forEach((v) => v.abort()); 23 | }, 24 | 25 | hasPending() { 26 | return Object.values(this.controllers).length > 0; 27 | }, 28 | 29 | remove(sessionId: string, messageId: string) { 30 | const key = this.key(sessionId, messageId); 31 | delete this.controllers[key]; 32 | }, 33 | 34 | key(sessionId: string, messageIndex: string) { 35 | return `${sessionId},${messageIndex}`; 36 | }, 37 | }; 38 | -------------------------------------------------------------------------------- /app/components/error.module.scss: -------------------------------------------------------------------------------- 1 | .error { 2 | width: 80vw; 3 | height: 100vh; 4 | display: flex; 5 | flex-direction: column; 6 | box-sizing: border-box; 7 | padding: 16px; 8 | gap: 12px; 9 | overflow: hidden; 10 | } 11 | 12 | .title { 13 | margin: 0; 14 | } 15 | 16 | .panel { 17 | flex: 1; 18 | min-height: 0; 19 | overflow: auto; 20 | -webkit-overflow-scrolling: touch; 21 | border: 1px solid rgba(0, 0, 0, 0.08); 22 | border-radius: 8px; 23 | padding: 12px; 24 | background: #f7f7f8; 25 | } 26 | 27 | .pre { 28 | margin: 0; 29 | white-space: pre-wrap; 30 | word-break: break-word; 31 | 32 | /* 保证错误信息能复制 */ 33 | user-select: text; 34 | -webkit-user-select: text; 35 | -moz-user-select: text; 36 | -ms-user-select: text; 37 | 38 | cursor: text; 39 | } 40 | 41 | .actions { 42 | display: flex; 43 | justify-content: space-between; 44 | align-items: center; 45 | gap: 8px; 46 | flex-wrap: wrap; 47 | position: sticky; 48 | bottom: 0; 49 | padding-top: 8px; 50 | background: inherit; 51 | } 52 | 53 | .report { 54 | text-decoration: none; 55 | } 56 | -------------------------------------------------------------------------------- /app/icons/three-dots.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/global.d.ts: -------------------------------------------------------------------------------- 1 | declare module "*.jpg"; 2 | declare module "*.png"; 3 | declare module "*.woff2"; 4 | declare module "*.woff"; 5 | declare module "*.ttf"; 6 | declare module "*.scss" { 7 | const content: Record; 8 | export default content; 9 | } 10 | 11 | declare module "*.svg"; 12 | 13 | declare interface Window { 14 | __TAURI__?: { 15 | writeText(text: string): Promise; 16 | invoke(command: string, payload?: Record): Promise; 17 | dialog: { 18 | save(options?: Record): Promise; 19 | }; 20 | fs: { 21 | writeBinaryFile(path: string, data: Uint8Array): Promise; 22 | writeTextFile(path: string, data: string): Promise; 23 | }; 24 | notification:{ 25 | requestPermission(): Promise; 26 | isPermissionGranted(): Promise; 27 | sendNotification(options: string | Options): void; 28 | }; 29 | http: { 30 | fetch( 31 | url: string, 32 | options?: Record, 33 | ): Promise>; 34 | }; 35 | }; 36 | } 37 | -------------------------------------------------------------------------------- /docs/synchronise-chat-logs-es.md: -------------------------------------------------------------------------------- 1 | # Sincronizzare i Log delle Chat con UpStash 2 | ## Prerequisiti 3 | - Account GitHub 4 | - Server ChatGPT-Next-Web di propria configurazione 5 | - [UpStash](https://upstash.com) 6 | 7 | ## Per iniziare 8 | 1. Registrarsi per un account UpStash. 9 | 2. Creare un database. 10 | 11 | ![Registrarsi ed Accedere](./images/upstash-1.png) 12 | 13 | ![Creare un Database](./images/upstash-2.png) 14 | 15 | ![Selezionare il Server](./images/upstash-3.png) 16 | 17 | 3. Trovare l'API REST e copiare UPSTASH_REDIS_REST_URL e UPSTASH_REDIS_REST_TOKEN (⚠Importante⚠: Non condividere il token!) 18 | 19 | ![Copia](./images/upstash-4.png) 20 | 21 | 4. Copiare UPSTASH_REDIS_REST_URL e UPSTASH_REDIS_REST_TOKEN nella configurazione di sincronizzazione, quindi fare clic su **Verifica la Disponibilità**. 22 | 23 | ![Sincronizzazione 1](./images/upstash-5.png) 24 | 25 | Se tutto è in ordine, hai completato con successo questa fase. 26 | 27 | ![Verifica la Disponibilità della Sincronizzazione Completata](./images/upstash-6.png) 28 | 29 | 5. Successo! 30 | 31 | ![Ottimo lavoro~!](./images/upstash-7.png) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Zhang Yifei 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /app/icons/custom-models.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/icons/export.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/icons/eye.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/icons/clear.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/workflows/remove_deploy_preview.yml: -------------------------------------------------------------------------------- 1 | name: Removedeploypreview 2 | 3 | permissions: 4 | contents: read 5 | statuses: write 6 | pull-requests: write 7 | 8 | env: 9 | VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} 10 | VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} 11 | VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} 12 | 13 | on: 14 | pull_request_target: 15 | types: 16 | - closed 17 | 18 | jobs: 19 | delete-deployments: 20 | runs-on: ubuntu-latest 21 | steps: 22 | - uses: actions/checkout@v2 23 | 24 | - name: Extract branch name 25 | shell: bash 26 | run: echo "branch=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}" >> $GITHUB_OUTPUT 27 | id: extract_branch 28 | 29 | - name: Hash branch name 30 | uses: pplanel/hash-calculator-action@v1.3.1 31 | id: hash_branch 32 | with: 33 | input: ${{ steps.extract_branch.outputs.branch }} 34 | method: MD5 35 | 36 | - name: Call the delete-deployment-preview.sh script 37 | env: 38 | META_TAG: ${{ steps.hash_branch.outputs.digest }} 39 | run: | 40 | bash ./scripts/delete-deployment-preview.sh 41 | -------------------------------------------------------------------------------- /app/icons/prompt.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/icons/translate.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/icons/llm-icons/stability.svg: -------------------------------------------------------------------------------- 1 | 2 | Stability 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/utils/merge.ts: -------------------------------------------------------------------------------- 1 | export function merge(target: any, source: any) { 2 | if (!source || typeof source !== "object") { 3 | // 如果 source 不是对象或为 null,则无需合并 4 | return; 5 | } 6 | 7 | Object.keys(source).forEach((key) => { 8 | if ( 9 | !source.hasOwnProperty(key) || 10 | key === "__proto__" || 11 | key === "constructor" 12 | ) { 13 | return; 14 | } 15 | 16 | const sourceValue = source[key]; 17 | const targetValue = target[key]; 18 | 19 | if (Array.isArray(sourceValue)) { 20 | // 如果源属性是数组 21 | // 策略:通常用源数组完全替换目标数组(进行深拷贝以避免引用问题) 22 | // 如果目标不是数组或者不存在,也直接用源数组的拷贝 23 | target[key] = JSON.parse(JSON.stringify(sourceValue)); 24 | } else if (sourceValue !== null && typeof sourceValue === "object") { 25 | // 如果源属性是对象(但不是数组,也不是 null) 26 | if ( 27 | !targetValue || 28 | typeof targetValue !== "object" || 29 | Array.isArray(targetValue) 30 | ) { 31 | // 如果目标不是对象,或者是数组,或者不存在,则初始化为一个新对象 32 | target[key] = {}; 33 | } 34 | merge(target[key], sourceValue); // 递归合并 35 | } else { 36 | // 基本类型、null 或 undefined(来自 sourceValue) 37 | target[key] = sourceValue; 38 | } 39 | }); 40 | } 41 | -------------------------------------------------------------------------------- /app/icons/auto.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/icons/loading.svg: -------------------------------------------------------------------------------- 1 | Layer 1 -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.9" 2 | services: 3 | chatgpt-next-web: 4 | profiles: [ "no-proxy" ] 5 | container_name: chatgpt-next-web 6 | image: justma/chatgpt-next-web 7 | ports: 8 | - 3000:3000 9 | environment: 10 | - OPENAI_API_KEY=$OPENAI_API_KEY 11 | - GOOGLE_API_KEY=$GOOGLE_API_KEY 12 | - CODE=$CODE 13 | - BASE_URL=$BASE_URL 14 | - OPENAI_ORG_ID=$OPENAI_ORG_ID 15 | - HIDE_USER_API_KEY=$HIDE_USER_API_KEY 16 | - DISABLE_GPT4=$DISABLE_GPT4 17 | - ENABLE_BALANCE_QUERY=$ENABLE_BALANCE_QUERY 18 | - DISABLE_FAST_LINK=$DISABLE_FAST_LINK 19 | - OPENAI_SB=$OPENAI_SB 20 | 21 | chatgpt-next-web-proxy: 22 | profiles: [ "proxy" ] 23 | container_name: chatgpt-next-web-proxy 24 | image: justma/chatgpt-next-web 25 | ports: 26 | - 3000:3000 27 | environment: 28 | - OPENAI_API_KEY=$OPENAI_API_KEY 29 | - GOOGLE_API_KEY=$GOOGLE_API_KEY 30 | - CODE=$CODE 31 | - PROXY_URL=$PROXY_URL 32 | - BASE_URL=$BASE_URL 33 | - OPENAI_ORG_ID=$OPENAI_ORG_ID 34 | - HIDE_USER_API_KEY=$HIDE_USER_API_KEY 35 | - DISABLE_GPT4=$DISABLE_GPT4 36 | - ENABLE_BALANCE_QUERY=$ENABLE_BALANCE_QUERY 37 | - DISABLE_FAST_LINK=$DISABLE_FAST_LINK 38 | - OPENAI_SB=$OPENAI_SB 39 | -------------------------------------------------------------------------------- /app/components/share.module.scss: -------------------------------------------------------------------------------- 1 | // app/components/share.module.scss 2 | 3 | .share-page { 4 | display: flex; 5 | flex-direction: column; 6 | background-color: var(--c-page-bg); 7 | height: 100vh; 8 | padding: 0 20px; 9 | } 10 | 11 | .share-header { 12 | display: flex; 13 | justify-content: space-between; 14 | align-items: center; 15 | max-width: 800px; 16 | margin: 0 auto 20px; 17 | 18 | h1 { 19 | font-size: 1.5em; 20 | font-weight: bold; 21 | } 22 | } 23 | .share-content-wrapper { 24 | flex-grow: 1; /* Allow this wrapper to fill all remaining vertical space */ 25 | overflow-y: auto; /* Add a vertical scrollbar ONLY when content overflows */ 26 | min-height: 0; /* A flexbox fix to ensure scrolling works correctly in all browsers */ 27 | padding-bottom: 20px; /* Add some space at the very bottom */ 28 | } 29 | .share-error { 30 | display: flex; 31 | flex-direction: column; 32 | align-items: center; 33 | justify-content: center; 34 | height: 80vh; 35 | text-align: center; 36 | 37 | h1 { 38 | font-size: 2em; 39 | margin-bottom: 10px; 40 | } 41 | 42 | p { 43 | color: var(--text-light-color); 44 | } 45 | } 46 | 47 | // Override to hide actions on the share page 48 | :global(.share-page) .preview-actions { 49 | display: none !important; 50 | } -------------------------------------------------------------------------------- /app/icons/chat.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/icons/drag.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/utils/indexedDB-storage.ts: -------------------------------------------------------------------------------- 1 | import { StateStorage } from "zustand/middleware"; 2 | import { get, set, del, clear } from "idb-keyval"; 3 | import { safeLocalStorage } from "../utils"; 4 | 5 | const localStorage = safeLocalStorage(); 6 | 7 | class IndexedDBStorage implements StateStorage { 8 | public async getItem(name: string): Promise { 9 | try { 10 | const value = (await get(name)) || localStorage.getItem(name); 11 | return value; 12 | } catch (error) { 13 | return localStorage.getItem(name); 14 | } 15 | } 16 | 17 | public async setItem(name: string, value: string): Promise { 18 | try { 19 | const _value = JSON.parse(value); 20 | if (!_value?.state?._hasHydrated) { 21 | console.warn("skip setItem", name); 22 | return; 23 | } 24 | await set(name, value); 25 | } catch (error) { 26 | localStorage.setItem(name, value); 27 | } 28 | } 29 | 30 | public async removeItem(name: string): Promise { 31 | try { 32 | await del(name); 33 | } catch (error) { 34 | localStorage.removeItem(name); 35 | } 36 | } 37 | 38 | public async clear(): Promise { 39 | try { 40 | await clear(); 41 | } catch (error) { 42 | localStorage.clear(); 43 | } 44 | } 45 | } 46 | 47 | export const indexedDBStorage = new IndexedDBStorage(); 48 | -------------------------------------------------------------------------------- /docs/vercel-cn.md: -------------------------------------------------------------------------------- 1 | # Vercel 的使用说明 2 | 3 | ## 如何新建项目 4 | 当你从 Github fork 本项目之后,需要重新在 Vercel 创建一个全新的 Vercel 项目来重新部署,你需要按照下列步骤进行。 5 | 6 | ![vercel-create-1](./images/vercel/vercel-create-1.jpg) 7 | 1. 进入 Vercel 控制台首页; 8 | 2. 点击 Add New; 9 | 3. 选择 Project。 10 | 11 | ![vercel-create-2](./images/vercel/vercel-create-2.jpg) 12 | 1. 在 Import Git Repository 处,搜索 chatgpt-next-web; 13 | 2. 选中新 fork 的项目,点击 Import。 14 | 15 | ![vercel-create-3](./images/vercel/vercel-create-3.jpg) 16 | 1. 在项目配置页,点开 Environmane Variables 开始配置环境变量; 17 | 2. 依次新增名为 OPENAI_API_KEY 和 CODE ([访问密码](https://github.com/Yidadaa/ChatGPT-Next-Web/blob/357296986609c14de10bf210871d30e2f67a8784/docs/faq-cn.md#%E7%8E%AF%E5%A2%83%E5%8F%98%E9%87%8F-code-%E6%98%AF%E4%BB%80%E4%B9%88%E5%BF%85%E9%A1%BB%E8%AE%BE%E7%BD%AE%E5%90%97)) 的环境变量; 18 | 3. 填入环境变量对应的值; 19 | 4. 点击 Add 确认增加环境变量; 20 | 5. 请确保你添加了 OPENAI_API_KEY,否则无法使用; 21 | 6. 点击 Deploy,创建完成,耐心等待 5 分钟左右部署完成。 22 | 23 | ## 如何增加自定义域名 24 | [TODO] 25 | 26 | ## 如何更改环境变量 27 | ![vercel-env-edit](./images/vercel/vercel-env-edit.jpg) 28 | 1. 进去 Vercel 项目内部控制台,点击顶部的 Settings 按钮; 29 | 2. 点击左侧的 Environment Variables; 30 | 3. 点击已有条目的右侧按钮; 31 | 4. 选择 Edit 进行编辑,然后保存即可。 32 | 33 | ⚠️️ 注意:每次修改完环境变量,你都需要[重新部署项目](#如何重新部署)来让改动生效! 34 | 35 | ## 如何重新部署 36 | ![vercel-redeploy](./images/vercel/vercel-redeploy.jpg) 37 | 1. 进入 Vercel 项目内部控制台,点击顶部的 Deployments 按钮; 38 | 2. 选择列表最顶部一条的右侧按钮; 39 | 3. 点击 Redeploy 即可重新部署。 40 | -------------------------------------------------------------------------------- /docs/vercel-ja.md: -------------------------------------------------------------------------------- 1 | # Vercel 使用説明書 2 | 3 | ## 新規プロジェクトの作成方法 4 | 5 | このプロジェクトを GitHub からフォークし、Vercel で新しい Vercel プロジェクトを作成して再デプロイする必要がある場合は、以下の手順に従ってください。 6 | 7 | ![vercel-create-1](./images/vercel/vercel-create-1.jpg) 8 | 9 | 1. Vercel コンソールのホームページにアクセスします; 10 | 2. 新規追加をクリックする; 11 | 3. プロジェクトを選択します。 12 | 13 | ![vercel-create-2](./images/vercel/vercel-create-2.jpg) 14 | 15 | 1. Git リポジトリのインポートで、chatgpt-next-web を検索します; 16 | 2 .新しいフォークプロジェクトを選択し、インポートをクリックします。 17 | 18 | ![vercel-create-3](./images/vercel/vercel-create-3.jpg) 19 | 20 | 1. Project Settings ページで、Environment Variables をクリックして環境変数を設定する; 21 | 2. OPENAI_API_KEY と CODE という名前の環境変数を追加します; 22 | 3. 環境変数に対応する値を入力します; 23 | 4. Add をクリックして、環境変数の追加を確認する; 24 | 5. OPENAI_API_KEY を必ず追加してください; 25 | 6. Deploy をクリックして作成し、デプロイが完了するまで約 5 分間辛抱強く待つ。 26 | 27 | ## カスタムドメイン名の追加方法 28 | 29 | \[TODO] 30 | 31 | ## 環境変数の変更方法 32 | 33 | ![vercel-env-edit](./images/vercel/vercel-env-edit.jpg) 34 | 35 | 1. 内部 Vercel プロジェクトコンソールに移動し、上部の設定ボタンをクリックします; 36 | 2. 左側の Environment Variables をクリックします; 37 | 3. 既存のエントリーの右側のボタンをクリックします; 38 | 4. 編集を選択して編集し、保存する。 39 | 40 | ⚠️️ 注意: [プロジェクトの再デプロイ](#再実装の方法)環境変数を変更するたびに、変更を有効にするために必要です! 41 | 42 | ## 再実装の方法 43 | 44 | ![vercel-redeploy](./images/vercel/vercel-redeploy.jpg) 45 | 46 | 1. Vercelプロジェクトの内部コンソールに移動し、一番上のDeploymentsボタンをクリックします; 47 | 2. リストの一番上の項目の右のボタンを選択します; 48 | 3. 再デプロイをクリックして再デプロイします。 49 | -------------------------------------------------------------------------------- /docs/cloudflare-pages-ko.md: -------------------------------------------------------------------------------- 1 | ## Cloudflare 페이지 배포 가이드 2 | 3 | ## 새 프로젝트를 만드는 방법 4 | 이 프로젝트를 Github에서 포크한 다음 dash.cloudflare.com에 로그인하고 페이지로 이동합니다. 5 | 6 | 1. "프로젝트 만들기"를 클릭합니다. 7 | 2. "Git에 연결"을 선택합니다. 8 | 3. Cloudflare 페이지를 GitHub 계정과 연결합니다. 9 | 4. 포크한 프로젝트를 선택합니다. 10 | 5. "설정 시작"을 클릭합니다. 11 | 6. "프로젝트 이름" 및 "프로덕션 브랜치"의 기본값을 사용하거나 필요에 따라 변경합니다. 12 | 7. "빌드 설정"에서 "프레임워크 프리셋" 옵션을 선택하고 "Next.js"를 선택합니다. 13 | 8. node:buffer 버그로 인해 지금은 기본 "빌드 명령어"를 사용하지 마세요. 다음 명령을 사용하세요: 14 | ``` 15 | npx @cloudflare/next-on-pages --experimental-minify 16 | ``` 17 | 9. "빌드 출력 디렉토리"의 경우 기본값을 사용하고 수정하지 마십시오. 18 | 10. "루트 디렉토리"는 수정하지 마십시오. 19 | 11. "환경 변수"의 경우 ">"를 클릭한 다음 "변수 추가"를 클릭합니다. 다음에 따라 정보를 입력합니다: 20 | 21 | - node_version=20.1`. 22 | - next_telemetry_disable=1`. 23 | - `OPENAI_API_KEY=자신의 API 키` 24 | - ``yarn_version=1.22.19`` 25 | - ``php_version=7.4``. 26 | 27 | 실제 필요에 따라 다음 옵션을 선택적으로 입력합니다: 28 | 29 | - `CODE= 선택적으로 액세스 비밀번호를 입력하며 쉼표를 사용하여 여러 비밀번호를 구분할 수 있습니다`. 30 | - `OPENAI_ORG_ID= 선택 사항, OpenAI에서 조직 ID 지정` 31 | - `HIDE_USER_API_KEY=1 선택 사항, 사용자가 API 키를 입력하지 못하도록 합니다. 32 | - `DISABLE_GPT4=1 옵션, 사용자가 GPT-4를 사용하지 못하도록 설정` 12. 33 | 34 | 12. "저장 후 배포"를 클릭합니다. 35 | 13. 호환성 플래그를 입력해야 하므로 "배포 취소"를 클릭합니다. 36 | 14. "빌드 설정", "기능"으로 이동하여 "호환성 플래그"를 찾습니다. 37 | "프로덕션 호환성 플래그 구성" 및 "프리뷰 호환성 플래그 구성"에서 "nodejs_compat"를 입력합니다. 38 | 16. "배포"로 이동하여 "배포 다시 시도"를 클릭합니다. 39 | 17. 즐기세요! -------------------------------------------------------------------------------- /app/icons/llm-icons/doubao.svg: -------------------------------------------------------------------------------- 1 | 2 | Doubao 3 | 4 | 5 | 7 | 9 | 11 | 13 | 14 | -------------------------------------------------------------------------------- /app/components/CustomCssProvider.tsx: -------------------------------------------------------------------------------- 1 | // app/components/CustomCssProvider.tsx 2 | "use client"; 3 | 4 | import { useEffect, useMemo, useState } from "react"; 5 | import { useCustomCssStore } from "../store/customCss"; 6 | import { useAppConfig } from "../store"; 7 | 8 | export function CustomCssProvider() { 9 | const customCss = useCustomCssStore(); 10 | const config = useAppConfig(); 11 | const [mounted, setMounted] = useState(false); 12 | 13 | useEffect(() => { 14 | setMounted(true); 15 | }, []); 16 | 17 | // 基准字体样式,独立于自定义 CSS 18 | const baseFontCss = useMemo( 19 | () => `:root { font-size: ${config.fontSize}px; }`, 20 | [config.fontSize], 21 | ); 22 | 23 | if (!mounted) { 24 | return null; 25 | } 26 | 27 | return ( 28 | //