Compare commits

..

63 Commits
v1.7 ... v1.9

Author SHA1 Message Date
Yifei Zhang
d226090926 Merge pull request #346 from AprilNEA/reset
The Clear Data button on the Settings page is only clear for all dialog data.
2023-04-02 18:36:51 +08:00
Yifei Zhang
2d534bfdf4 Merge pull request #347 from SadPencil/patch-1
Fix typos
2023-04-02 18:34:16 +08:00
Sad Pencil
ed5cd11d6a Fix typos 2023-04-02 14:23:12 +08:00
AprilNEA
0a60a87c9f Merge branch 'main' into reset
# Conflicts:
#	app/components/settings.tsx
2023-04-02 13:45:34 +08:00
AprilNEA
506cdbc83c feat: clear session only 2023-04-02 13:42:47 +08:00
Yifei Zhang
a64c4384b1 Merge pull request #322 from quark-zju/wexin-compat
fix: 微信 Android 内置浏览器兼容性
2023-04-02 13:17:56 +08:00
Yifei Zhang
d54c983187 Merge pull request #333 from qirong77/main
fix:修复中文输入法下enter错误发送消息问题
2023-04-02 13:12:42 +08:00
Jun Wu
cd5f8f7407 app: polyfill Array.at
This fixes compatibility issue with older browsers like WeChat webview.
The summary feature now works as expected.
2023-04-01 11:38:52 -07:00
linqirong
00a282214e fix:修复中文输入法下enter错误发送消息问题 2023-04-02 00:12:31 +08:00
Yifei Zhang
44145f11db Merge pull request #324 from yorunning/main
ci: update sync action
2023-04-01 23:25:59 +08:00
Yorun
ad63b10aea ci: update sync action 2023-04-01 11:52:10 +00:00
Jun Wu
327ac765df utils: simplify trimTopic
Also avoid using Array.prototype.at, which does not seem to exist
in the Wexin builtin webview (Android Wexin 8.0.30).
2023-04-01 03:37:28 -07:00
Jun Wu
83cea2adb8 api: set Content-Type to json
This avoids issues in browsers like WeChat where the encoding is
incorrect and the summary feature does not work if it contains
zh-CN characters.
2023-04-01 03:37:09 -07:00
Yifei Zhang
0385f6ede9 fix: #305 disable double click to copy on pc 2023-04-01 15:46:34 +08:00
Yifei Zhang
45bf2c3d25 fix: remove scroll anchor height 2023-04-01 15:39:30 +08:00
Yifei Zhang
e6b64b0f2c Merge pull request #303 from leedom92/add-confirm-when-remove-chatitem
feat: add confirm tips when deleting conversation on pc
2023-04-01 13:08:06 +08:00
leedom
4dc1e025e1 feat: add confirm tips when deleting conversation on pc 2023-04-01 10:24:06 +08:00
Yifei Zhang
ba08b10de1 Merge pull request #285 from Dogtiti/main
修复在移动端高度被搜索栏占用导致无法完整显示一屏问题
2023-04-01 02:02:05 +08:00
Yifei Zhang
de35862cc5 Merge pull request #294 from hibobmaster/fix-deploy
Update docker.yml
2023-04-01 01:53:07 +08:00
hibobmaster
407c9fc9c3 Update docker.yml 2023-03-31 23:03:57 +08:00
Dogtiti
536358cb3c Merge branch 'Yidadaa:main' into main 2023-03-31 19:21:46 +08:00
Dogtiti
5f7a264e52 fix: 修复在手机浏览器高度样式问题 2023-03-31 19:21:11 +08:00
Yifei Zhang
c70c311989 Merge pull request #283 from Yidadaa/fix-credit-cache
fix: #277 no cache for credit query
2023-03-31 18:41:21 +08:00
Yifei Zhang
e5aa72af76 fix: #277 no cache for credit query 2023-03-31 18:33:26 +08:00
Yifei Zhang
eb586ba361 Merge pull request #259 from DanielRondonGarcia/main
Update: locales in spanish
2023-03-31 16:45:29 +08:00
Daniel Gerardo Rondón García
7f16698f01 Update: language options to "Language".
- Update Name option in language files to display 'Language' consistently
- Fix locale issues in 'tw' and 'cn' files that were mistakenly changed
2023-03-30 23:32:56 -05:00
Yifei Zhang
61eb356fd9 Update README.md 2023-03-31 12:02:14 +08:00
Yifei Zhang
35a402c67e Merge pull request #262 from XiaoMiku01/main
add auto sync fork action daily
2023-03-31 11:19:50 +08:00
Yifei Zhang
5a910e0f29 Update README.md 2023-03-31 10:37:33 +08:00
XiaoMiku01
be8a35063c add auto sync fork action 2023-03-31 10:05:58 +08:00
Daniel Gerardo Rondón García
df75b9973a Update: locales in spanish 2023-03-30 19:48:57 -05:00
Yifei Zhang
2f2e0b6762 fix: commit id as version id 2023-03-30 18:15:49 +00:00
Yifei Zhang
83862eae44 Update docker.yml 2023-03-31 01:56:42 +08:00
Yifei Zhang
6e6faec398 Merge pull request #255 from Yidadaa/docker-fix
fix: docker build
2023-03-31 01:42:37 +08:00
Yifei Zhang
e7e39ba56e fix: docker build 2023-03-30 17:41:43 +00:00
Yifei Zhang
fe9dd88c3f Merge pull request #254 from Yidadaa/bugfix-0330
Bugfix 0330
2023-03-31 01:10:36 +08:00
Yifei Zhang
b3fdf3efec fix: #182 prompt cannot be selected 2023-03-30 17:08:50 +00:00
Yifei Zhang
802ea20ec4 fix: auto scroll on enter 2023-03-30 17:04:32 +00:00
Yifei Zhang
52a217883d Merge pull request #234 from GOWxx/feat_set_history_to_zero
feat: support history message count to zero
2023-03-31 00:56:57 +08:00
Yifei Zhang
7783545bff feat: use tag as version number 2023-03-30 16:46:17 +00:00
Yifei Zhang
1b140a1ed3 fix: tight border on mobile style 2023-03-30 16:20:47 +00:00
Yifei Zhang
bf50ebac94 fix: #229 disable light code theme 2023-03-30 15:50:08 +00:00
Yifei Zhang
7599ae385b fix: #244 better scroll ux 2023-03-30 15:42:54 +00:00
Yifei Zhang
7827b40f17 fix: #185 input and select align center 2023-03-30 15:25:38 +00:00
Yifei Zhang
dea3d26335 fix: crash caused by filter config 2023-03-30 15:20:19 +00:00
Yifei Zhang
9eb77207fb Merge pull request #226 from jack0pan/fix/deps
fix: update yarn.lock file
2023-03-30 19:01:04 +08:00
Yifei Zhang
164d3fb4fe Merge pull request #228 from yanglyu902/fix-title
fix: remove English punctuation in generated title
2023-03-30 19:00:05 +08:00
GOWxx
0c9add7988 feat: support history message count to zero 2023-03-30 18:50:58 +08:00
Jack
166329abee fix: update yarn.lock file 2023-03-30 09:55:41 +00:00
Leotrinos
7d7abca2c4 fix trimTopic 2023-03-30 02:55:19 -07:00
Yifei Zhang
dab69c7507 Merge pull request #215 from hibobmaster/multi-arch-docker-build
CI: Multi Arch docker builds
2023-03-30 16:06:22 +08:00
hibobmaster
852f8b8aa5 Merge branch 'main' of https://github.com/Yidadaa/ChatGPT-Next-Web into multi-arch-docker-build 2023-03-30 15:53:44 +08:00
Yifei Zhang
fe858621f2 Merge pull request #213 from leedom92/copy-content-in-mobile
feat: setUserInput with onDoubleClickCapture in mobile phone
2023-03-30 15:38:54 +08:00
Leedom
6e4e804af8 Update home.tsx 2023-03-30 15:32:03 +08:00
leedom
e68aaf24f1 feat: setUserInput with onDoubleClickCapture in mobile phone 2023-03-30 15:22:10 +08:00
Yifei Zhang
29c20a3d5c Merge pull request #202 from RugerMcCarthy/feat/display_line_break
feat: import remark-breaks plugin
2023-03-30 14:56:21 +08:00
hibobmaster
6ed61f533a Merge branch 'main' of https://github.com/Yidadaa/ChatGPT-Next-Web into multi-arch-docker-build 2023-03-30 13:48:06 +08:00
hibobmaster
ad7a365f32 Support multi-arch docker build 2023-03-30 13:19:32 +08:00
RugerMc
2c5420ab9e feat: import ramarkBreaks plugin 2023-03-30 12:48:38 +08:00
Yifei Zhang
8d6d6bbf5d Update README.md 2023-03-30 11:22:27 +08:00
Yifei Zhang
d5235c81d0 Update README.md 2023-03-30 11:21:44 +08:00
Yifei Zhang
9e5b119e92 Merge pull request #197 from angular-moon/patch-1
onUserSubmit hide promptHints
2023-03-30 10:53:54 +08:00
angular-moon
d9fc9cd198 onUserSubmit hide promptHints 2023-03-30 10:16:00 +08:00
26 changed files with 5581 additions and 7791 deletions

View File

@@ -1,6 +1,7 @@
name: Publish Docker image
on:
workflow_dispatch:
release:
types: [published]
@@ -9,25 +10,43 @@ jobs:
name: Push Docker image to Docker Hub
runs-on: ubuntu-latest
steps:
- name: Check out the repo
-
name: Check out the repo
uses: actions/checkout@v3
- name: Log in to Docker Hub
uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9
-
name: Log in to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Extract metadata (tags, labels) for Docker
-
name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38
uses: docker/metadata-action@v4
with:
images: yidadaa/chatgpt-next-web
tags: |
type=raw,value=latest
type=ref,event=tag
- name: Build and push Docker image
uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc
-
name: Set up QEMU
uses: docker/setup-qemu-action@v2
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
-
name: Build and push Docker image
uses: docker/build-push-action@v4
with:
context: .
platforms: linux/amd64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max

29
.github/workflows/sync.yml vendored Normal file
View File

@@ -0,0 +1,29 @@
name: Upstream Sync
on:
schedule:
- cron: '0 */12 * * *' # every 12 hours
workflow_dispatch: # on button click
jobs:
sync_latest_from_upstream:
name: Sync latest commits from upstream repo
runs-on: ubuntu-latest
steps:
# Step 1: run a standard checkout action, provided by github
- name: Checkout target repo
uses: actions/checkout@v3
# Step 2: run the sync action
- name: Sync upstream changes
id: sync
uses: aormsby/Fork-Sync-With-Upstream-action@v3.4
with:
upstream_sync_repo: Yidadaa/ChatGPT-Next-Web
upstream_sync_branch: main
target_sync_branch: main
target_repo_token: ${{ secrets.GITHUB_TOKEN }} # automatically generated, no need to set
# Set test_mode true to run tests instead of the true action!!
test_mode: false

View File

@@ -6,13 +6,9 @@ RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package.json yarn.lock* package-lock.json* ./
COPY package.json yarn.lock ./
RUN \
if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
elif [ -f package-lock.json ]; then npm ci; \
else echo "Lockfile not found." && exit 1; \
fi
RUN yarn install
FROM base AS builder

View File

@@ -7,7 +7,7 @@
One-Click to deploy your own ChatGPT web UI.
[演示 Demo](https://chat-gpt-next-web.vercel.app/) / [反馈 Issues](https://github.com/Yidadaa/ChatGPT-Next-Web/issues) / [加入 Discord](https://discord.gg/zrhvHCr79N) / [QQ 群](https://user-images.githubusercontent.com/16968934/228190818-7dd00845-e9b9-4363-97e5-44c507ac76da.jpeg) / [打赏开发者](https://user-images.githubusercontent.com/16968934/227772541-5bcd52d8-61b7-488c-a203-0330d8006e2b.jpg)
[演示 Demo](https://chat-gpt-next-web.vercel.app/) / [反馈 Issues](https://github.com/Yidadaa/ChatGPT-Next-Web/issues) / [加入 Discord](https://discord.gg/zrhvHCr79N) / [QQ 群](https://user-images.githubusercontent.com/16968934/228190818-7dd00845-e9b9-4363-97e5-44c507ac76da.jpeg) / [打赏开发者](https://user-images.githubusercontent.com/16968934/227772541-5bcd52d8-61b7-488c-a203-0330d8006e2b.jpg) / [Donate](#捐赠-donate-usdt)
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FYidadaa%2FChatGPT-Next-Web&env=OPENAI_API_KEY&env=CODE&project-name=chatgpt-next-web&repository-name=ChatGPT-Next-Web)
@@ -36,11 +36,21 @@ One-Click to deploy your own ChatGPT web UI.
- Automatically compresses chat history to support long conversations while also saving your tokens
- One-click export all chat history with full Markdown support
## 使用
## 开发计划 Roadmap
- System Prompt: pin a user defined prompt as system prompt 为每个对话设置系统 Prompt [#138](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/138)
- User Prompt: user can edit and save custom prompts to prompt list 允许用户自行编辑内置 Prompt 列表
- Self-host Model: support llama, alpaca, ChatGLM, BELLE etc. 支持自部署的大语言模型
- Plugins: support network search, caculator, any other apis etc. 插件机制,支持联网搜索、计算器、调用其他平台 api [#165](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/165)
### 不会开发的功能 Not in Plan
- User login, accounts, cloud sync 用户登录、账号管理、消息云同步
- UI text customize 界面文字自定义
## 开始使用
1. 准备好你的 [OpenAI API Key](https://platform.openai.com/account/api-keys);
2. 点击右侧按钮开始部署:
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FYidadaa%2FChatGPT-Next-Web&env=OPENAI_API_KEY&env=CODE&project-name=chatgpt-next-web&repository-name=ChatGPT-Next-Web),直接使用 Github 账号登即可,记得在环境变量页填入 API Key
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FYidadaa%2FChatGPT-Next-Web&env=OPENAI_API_KEY&env=CODE&project-name=chatgpt-next-web&repository-name=ChatGPT-Next-Web),直接使用 Github 账号登即可,记得在环境变量页填入 API Key
3. 部署完毕后,即可开始使用;
4. (可选)[绑定自定义域名](https://vercel.com/docs/concepts/projects/domains/add-a-domain)Vercel 分配的域名 DNS 在某些区域被污染了,绑定自定义域名即可直连。
@@ -169,15 +179,12 @@ docker run -d -p 3000:3000 -e OPENAI_API_KEY="" -e CODE="" yidadaa/chatgpt-next-
![更多展示 More](./static/more.png)
## 说明 Attention
本项目的演示地址所用的 OpenAI 账户的免费额度将于 2023-04-01 过期,届时将无法通过演示地址在线体验。
如果你想贡献出自己的 API Key可以通过作者主页的邮箱发送给作者并标注过期时间。
The free trial of the OpenAI account used by the demo will expire on April 1, 2023, and the demo will not be available at that time.
If you would like to contribute your API key, you can email it to the author and indicate the expiration date of the API key.
## 捐赠 Donate USDT
> BNB Smart Chain (BEP 20)
```
0x67cD02c7EB62641De576a1fA3EdB32eA0c3ffD89
```
## 鸣谢 Special Thanks
@@ -194,4 +201,4 @@ If you would like to contribute your API key, you can email it to the author and
## LICENSE
- [Anti 996 License](https://github.com/kattgu7/Anti-996-License/blob/master/LICENSE_CN_EN)
[Anti 996 License](https://github.com/kattgu7/Anti-996-License/blob/master/LICENSE_CN_EN)

View File

@@ -3,8 +3,10 @@ import { requestOpenai } from "../common";
async function makeRequest(req: NextRequest) {
try {
const res = await requestOpenai(req);
return new Response(res.body);
const api = await requestOpenai(req);
const res = new NextResponse(api.body);
res.headers.set("Content-Type", "application/json");
return res;
} catch (e) {
console.error("[OpenAI] ", req.body, e);
return NextResponse.json(

View File

@@ -26,13 +26,13 @@
@media only screen and (min-width: 600px) {
.tight-container {
--window-width: 100vw;
--window-height: 100vh;
--window-height: var(--full-height);
--window-content-width: calc(100% - var(--sidebar-width));
@include container();
max-width: 100vw;
max-height: 100vh;
max-height: var(--full-height);
border-radius: 0;
}
@@ -74,7 +74,7 @@
position: absolute;
left: -100%;
z-index: 999;
height: 100vh;
height: var(--full-height);
transition: all ease 0.3s;
box-shadow: none;
}
@@ -218,7 +218,6 @@
flex: 1;
overflow: auto;
padding: 20px;
margin-bottom: 100px;
}
.chat-body-title {
@@ -342,9 +341,6 @@
}
.chat-input-panel {
position: absolute;
bottom: 0px;
display: flex;
width: 100%;
padding: 20px;
box-sizing: border-box;

View File

@@ -23,7 +23,13 @@ import DownloadIcon from "../icons/download.svg";
import { Message, SubmitKey, useChatStore, ChatSession } from "../store";
import { showModal, showToast } from "./ui-lib";
import { copyToClipboard, downloadAs, isIOS, selectOrCopy } from "../utils";
import {
copyToClipboard,
downloadAs,
isIOS,
isMobileScreen,
selectOrCopy,
} from "../utils";
import Locale from "../locales";
import dynamic from "next/dynamic";
@@ -115,7 +121,7 @@ export function ChatList() {
key={i}
selected={i === selectedIndex}
onClick={() => selectSession(i)}
onDelete={() => removeSession(i)}
onDelete={() => confirm(Locale.Home.DeleteChat) && removeSession(i)}
/>
))}
</div>
@@ -126,9 +132,9 @@ function useSubmitHandler() {
const config = useChatStore((state) => state.config);
const submitKey = config.submitKey;
const shouldSubmit = (e: KeyboardEvent) => {
const shouldSubmit = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
if (e.key !== "Enter") return false;
if (e.key === "Enter" && e.nativeEvent.isComposing) return false;
return (
(config.submitKey === SubmitKey.AltEnter && e.altKey) ||
(config.submitKey === SubmitKey.CtrlEnter && e.ctrlKey) ||
@@ -239,6 +245,7 @@ export function Chat(props: {
setIsLoading(true);
chatStore.onUserInput(userInput).then(() => setIsLoading(false));
setUserInput("");
setPromptHints([]);
inputRef.current?.focus();
};
@@ -249,7 +256,7 @@ export function Chat(props: {
};
// check if should send message
const onInputKeyDown = (e: KeyboardEvent) => {
const onInputKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
if (shouldSubmit(e)) {
onUserSubmit();
e.preventDefault();
@@ -283,9 +290,7 @@ export function Chat(props: {
// for auto-scroll
const latestMessageRef = useRef<HTMLDivElement>(null);
// wont scroll while hovering messages
const [autoScroll, setAutoScroll] = useState(false);
const [autoScroll, setAutoScroll] = useState(true);
// preview messages
const messages = (session.messages as RenderMessage[])
@@ -318,7 +323,17 @@ export function Chat(props: {
useLayoutEffect(() => {
setTimeout(() => {
const dom = latestMessageRef.current;
if (dom && !isIOS() && autoScroll) {
const inputDom = inputRef.current;
// only scroll when input overlaped message body
let shouldScroll = true;
if (dom && inputDom) {
const domRect = dom.getBoundingClientRect();
const inputRect = inputDom.getBoundingClientRect();
shouldScroll = domRect.top > inputRect.top;
}
if (dom && autoScroll && shouldScroll) {
dom.scrollIntoView({
block: "end",
});
@@ -438,6 +453,10 @@ export function Chat(props: {
className="markdown-body"
style={{ fontSize: `${fontSize}px` }}
onContextMenu={(e) => onRightClick(e, message)}
onDoubleClickCapture={() => {
if (!isMobileScreen()) return;
setUserInput(message.content);
}}
>
<Markdown content={message.content} />
</div>
@@ -454,7 +473,7 @@ export function Chat(props: {
</div>
);
})}
<div ref={latestMessageRef} style={{ opacity: 0, height: "4em" }}>
<div ref={latestMessageRef} style={{ opacity: 0, height: "1px" }}>
-
</div>
</div>
@@ -469,11 +488,11 @@ export function Chat(props: {
rows={4}
onInput={(e) => onInput(e.currentTarget.value)}
value={userInput}
onKeyDown={(e) => onInputKeyDown(e as any)}
onKeyDown={onInputKeyDown}
onFocus={() => setAutoScroll(true)}
onBlur={() => {
setAutoScroll(false);
setTimeout(() => setPromptHints([]), 100);
setTimeout(() => setPromptHints([]), 500);
}}
autoFocus={!props?.sideBarShowing}
/>
@@ -602,7 +621,9 @@ export function Home() {
return (
<div
className={`${
config.tightBorder ? styles["tight-container"] : styles.container
config.tightBorder && !isMobileScreen()
? styles["tight-container"]
: styles.container
}`}
>
<div

View File

@@ -1,6 +1,7 @@
import ReactMarkdown from "react-markdown";
import "katex/dist/katex.min.css";
import RemarkMath from "remark-math";
import RemarkBreaks from "remark-breaks";
import RehypeKatex from "rehype-katex";
import RemarkGfm from "remark-gfm";
import RehypePrsim from "rehype-prism-plus";
@@ -29,7 +30,7 @@ export function PreCode(props: { children: any }) {
export function Markdown(props: { content: string }) {
return (
<ReactMarkdown
remarkPlugins={[RemarkMath, RemarkGfm]}
remarkPlugins={[RemarkMath, RemarkGfm, RemarkBreaks]}
rehypePlugins={[RehypeKatex, [RehypePrsim, { ignoreMissing: true }]]}
components={{
pre: PreCode,

View File

@@ -23,7 +23,7 @@ import {
import { Avatar, PromptHints } from "./home";
import Locale, { AllLangs, changeLang, getLang } from "../locales";
import { getCurrentCommitId } from "../utils";
import { getCurrentVersion } from "../utils";
import Link from "next/link";
import { UPDATE_URL } from "../constant";
import { SearchService, usePromptStore } from "../store/prompt";
@@ -49,18 +49,18 @@ function SettingItem(props: {
export function Settings(props: { closeSettings: () => void }) {
const [showEmojiPicker, setShowEmojiPicker] = useState(false);
const [config, updateConfig, resetConfig, clearAllData] = useChatStore(
(state) => [
const [config, updateConfig, resetConfig, clearAllData, clearSessions] =
useChatStore((state) => [
state.config,
state.updateConfig,
state.resetConfig,
state.clearAllData,
],
);
state.clearSessions,
]);
const updateStore = useUpdateStore();
const [checkingUpdate, setCheckingUpdate] = useState(false);
const currentId = getCurrentCommitId();
const currentId = getCurrentVersion();
const remoteId = updateStore.remoteId;
const hasNewVersion = currentId !== remoteId;
@@ -120,7 +120,7 @@ export function Settings(props: { closeSettings: () => void }) {
<div className={styles["window-action-button"]}>
<IconButton
icon={<ClearIcon />}
onClick={clearAllData}
onClick={clearSessions}
bordered
title={Locale.Settings.Actions.ClearAll}
/>
@@ -267,19 +267,17 @@ export function Settings(props: { closeSettings: () => void }) {
></input>
</SettingItem>
<div className="no-mobile">
<SettingItem title={Locale.Settings.TightBorder}>
<input
type="checkbox"
checked={config.tightBorder}
onChange={(e) =>
updateConfig(
(config) => (config.tightBorder = e.currentTarget.checked),
)
}
></input>
</SettingItem>
</div>
<SettingItem title={Locale.Settings.TightBorder}>
<input
type="checkbox"
checked={config.tightBorder}
onChange={(e) =>
updateConfig(
(config) => (config.tightBorder = e.currentTarget.checked),
)
}
></input>
</SettingItem>
</List>
<List>
<SettingItem
@@ -375,7 +373,7 @@ export function Settings(props: { closeSettings: () => void }) {
type="range"
title={config.historyMessageCount.toString()}
value={config.historyMessageCount}
min="2"
min="0"
max="25"
step="2"
onChange={(e) =>

View File

@@ -3,3 +3,4 @@ export const REPO = "ChatGPT-Next-Web";
export const REPO_URL = `https://github.com/${OWNER}/${REPO}`;
export const UPDATE_URL = `${REPO_URL}#%E4%BF%9D%E6%8C%81%E6%9B%B4%E6%96%B0-keep-updated`;
export const FETCH_COMMIT_URL = `https://api.github.com/repos/${OWNER}/${REPO}/commits?per_page=1`;
export const FETCH_TAG_URL = `https://api.github.com/repos/${OWNER}/${REPO}/tags?per_page=1`;

View File

@@ -8,11 +8,12 @@ import { ACCESS_CODES, IS_IN_DOCKER } from "./api/access";
let COMMIT_ID: string | undefined;
try {
COMMIT_ID = process
// .execSync("git describe --tags --abbrev=0")
.execSync("git rev-parse --short HEAD")
.toString()
.trim();
} catch (e) {
console.error("No git or not from git repo.")
console.error("No git or not from git repo.");
}
export const metadata = {
@@ -22,13 +23,13 @@ export const metadata = {
title: "ChatGPT Next Web",
statusBarStyle: "black-translucent",
},
themeColor: "#fafafa"
themeColor: "#fafafa",
};
function Meta() {
const metas = {
version: COMMIT_ID ?? "unknown",
access: (ACCESS_CODES.size > 0 || IS_IN_DOCKER) ? "enabled" : "disabled",
access: ACCESS_CODES.size > 0 || IS_IN_DOCKER ? "enabled" : "disabled",
};
return (

View File

@@ -57,6 +57,7 @@ const cn = {
cn: "简体中文",
en: "English",
tw: "繁體中文",
es: "Español",
},
},
Avatar: "头像",

View File

@@ -54,11 +54,12 @@ const en: LocaleType = {
Close: "Close",
},
Lang: {
Name: "语言",
Name: "Language",
Options: {
cn: "简体中文",
en: "English",
tw: "繁體中文",
es: "Español",
},
},
Avatar: "Avatar",

156
app/locales/es.ts Normal file
View File

@@ -0,0 +1,156 @@
import { SubmitKey } from "../store/app";
import type { LocaleType } from "./index";
const es: LocaleType = {
WIP: "En construcción...",
Error: {
Unauthorized:
"Acceso no autorizado, por favor ingrese el código de acceso en la página de configuración.",
},
ChatItem: {
ChatItemCount: (count: number) => `${count} mensajes`,
},
Chat: {
SubTitle: (count: number) => `${count} mensajes con ChatGPT`,
Actions: {
ChatList: "Ir a la lista de chats",
CompressedHistory: "Historial de memoria comprimido",
Export: "Exportar todos los mensajes como Markdown",
Copy: "Copiar",
Stop: "Detener",
Retry: "Reintentar",
},
Rename: "Renombrar chat",
Typing: "Escribiendo...",
Input: (submitKey: string) => {
var inputHints = `Escribe algo y presiona ${submitKey} para enviar`;
if (submitKey === String(SubmitKey.Enter)) {
inputHints += ", presiona Shift + Enter para nueva línea";
}
return inputHints;
},
Send: "Enviar",
},
Export: {
Title: "Todos los mensajes",
Copy: "Copiar todo",
Download: "Descargar",
},
Memory: {
Title: "Historial de memoria",
EmptyContent: "Aún no hay nada.",
Copy: "Copiar todo",
},
Home: {
NewChat: "Nuevo chat",
DeleteChat: "¿Confirmar eliminación de la conversación seleccionada?",
},
Settings: {
Title: "Configuración",
SubTitle: "Todas las configuraciones",
Actions: {
ClearAll: "Borrar todos los datos",
ResetAll: "Restablecer todas las configuraciones",
Close: "Cerrar",
},
Lang: {
Name: "Language",
Options: {
cn: "简体中文",
en: "Inglés",
tw: "繁體中文",
es: "Español",
},
},
Avatar: "Avatar",
FontSize: {
Title: "Tamaño de fuente",
SubTitle: "Ajustar el tamaño de fuente del contenido del chat",
},
Update: {
Version: (x: string) => `Versión: ${x}`,
IsLatest: "Última versión",
CheckUpdate: "Buscar actualizaciones",
IsChecking: "Buscando actualizaciones...",
FoundUpdate: (x: string) => `Se encontró una nueva versión: ${x}`,
GoToUpdate: "Actualizar",
},
SendKey: "Tecla de envío",
Theme: "Tema",
TightBorder: "Borde ajustado",
Prompt: {
Disable: {
Title: "Desactivar autocompletado",
SubTitle: "Escribe / para activar el autocompletado",
},
List: "Lista de autocompletado",
ListCount: (builtin: number, custom: number) =>
`${builtin} incorporado, ${custom} definido por el usuario`,
Edit: "Editar",
},
HistoryCount: {
Title: "Cantidad de mensajes adjuntos",
SubTitle: "Número de mensajes enviados adjuntos por solicitud",
},
CompressThreshold: {
Title: "Umbral de compresión de historial",
SubTitle:
"Se comprimirán los mensajes si la longitud de los mensajes no comprimidos supera el valor",
},
Token: {
Title: "Clave de API",
SubTitle: "Utiliza tu clave para ignorar el límite de código de acceso",
Placeholder: "Clave de la API de OpenAI",
},
Usage: {
Title: "Saldo de la cuenta",
SubTitle(granted: any, used: any) {
return `Total $${granted}, Usado $${used}`;
},
IsChecking: "Comprobando...",
Check: "Comprobar de nuevo",
},
AccessCode: {
Title: "Código de acceso",
SubTitle: "Control de acceso habilitado",
Placeholder: "Necesita código de acceso",
},
Model: "Modelo",
Temperature: {
Title: "Temperatura",
SubTitle: "Un valor mayor genera una salida más aleatoria",
},
MaxTokens: {
Title: "Máximo de tokens",
SubTitle: "Longitud máxima de tokens de entrada y tokens generados",
},
PresencePenlty: {
Title: "Penalización de presencia",
SubTitle:
"Un valor mayor aumenta la probabilidad de hablar sobre nuevos temas",
},
},
Store: {
DefaultTopic: "Nueva conversación",
BotHello: "¡Hola! ¿Cómo puedo ayudarte hoy?",
Error: "Algo salió mal, por favor intenta nuevamente más tarde.",
Prompt: {
History: (content: string) =>
"Este es un resumen del historial del chat entre la IA y el usuario como recapitulación: " +
content,
Topic:
"Por favor, genera un título de cuatro a cinco palabras que resuma nuestra conversación sin ningún inicio, puntuación, comillas, puntos, símbolos o texto adicional. Elimina las comillas que lo envuelven.",
Summarize:
"Resuma nuestra discusión brevemente en 50 caracteres o menos para usarlo como un recordatorio para futuros contextos.",
},
ConfirmClearAll:
"¿Confirmar para borrar todos los datos de chat y configuración?",
},
Copy: {
Success: "Copiado al portapapeles",
Failed:
"La copia falló, por favor concede permiso para acceder al portapapeles",
},
};
export default es;

View File

@@ -1,10 +1,11 @@
import CN from "./cn";
import EN from "./en";
import TW from "./tw";
import ES from "./es";
export type { LocaleType } from "./cn";
export const AllLangs = ["en", "cn", "tw"] as const;
export const AllLangs = ["en", "cn", "tw", "es"] as const;
type Lang = (typeof AllLangs)[number];
const LANG_KEY = "lang";
@@ -44,6 +45,8 @@ export function getLang(): Lang {
return "cn";
} else if (lang.includes("tw")) {
return "tw";
} else if (lang.includes("es")) {
return "es";
} else {
return "en";
}
@@ -54,4 +57,4 @@ export function changeLang(lang: Lang) {
location.reload();
}
export default { en: EN, cn: CN, tw: TW }[getLang()];
export default { en: EN, cn: CN, tw: TW, es: ES }[getLang()];

View File

@@ -53,11 +53,12 @@ const tw: LocaleType = {
Close: "關閉",
},
Lang: {
Name: "語言",
Name: "Language",
Options: {
cn: "简体中文",
en: "English",
tw: "繁體中文",
es: "Español",
},
},
Avatar: "大頭貼",

View File

@@ -2,6 +2,10 @@ import type { ChatRequest, ChatReponse } from "./api/openai/typing";
import { filterConfig, Message, ModelConfig, useAccessStore } from "./store";
import Locale from "./locales";
if (!Array.prototype.at) {
require("array.prototype.at/auto");
}
const TIME_OUT_MS = 30000;
const makeRequestParam = (
@@ -69,10 +73,9 @@ export async function requestChat(messages: Message[]) {
}
export async function requestUsage() {
const res = await requestOpenaiClient("dashboard/billing/credit_grants")(
null,
"GET",
);
const res = await requestOpenaiClient(
"dashboard/billing/credit_grants?_vercel_no_cache=1",
)(null, "GET");
try {
const response = (await res.json()) as {

View File

@@ -11,6 +11,10 @@ import { trimTopic } from "../utils";
import Locale from "../locales";
if (!Array.prototype.at) {
require("array.prototype.at/auto");
}
export type Message = ChatCompletionResponseMessage & {
date: string;
streaming?: boolean;
@@ -89,7 +93,9 @@ export function isValidNumber(x: number, min: number, max: number) {
return typeof x === "number" && x <= max && x >= min;
}
export function filterConfig(config: ModelConfig): Partial<ModelConfig> {
export function filterConfig(oldConfig: ModelConfig): Partial<ModelConfig> {
const config = Object.assign({}, oldConfig);
const validator: {
[k in keyof ModelConfig]: (x: ModelConfig[keyof ModelConfig]) => boolean;
} = {
@@ -103,7 +109,7 @@ export function filterConfig(config: ModelConfig): Partial<ModelConfig> {
return isValidNumber(x as number, -2, 2);
},
temperature(x) {
return isValidNumber(x as number, 0, 1);
return isValidNumber(x as number, 0, 2);
},
};
@@ -183,6 +189,7 @@ interface ChatStore {
config: ChatConfig;
sessions: ChatSession[];
currentSessionIndex: number;
clearSessions: () => void;
removeSession: (index: number) => void;
selectSession: (index: number) => void;
newSession: () => void;
@@ -221,6 +228,13 @@ export const useChatStore = create<ChatStore>()(
...DEFAULT_CONFIG,
},
clearSessions() {
set(() => ({
sessions: [createEmptySession()],
currentSessionIndex: 0,
}));
},
resetConfig() {
set(() => ({ config: { ...DEFAULT_CONFIG } }));
},

View File

@@ -99,19 +99,19 @@ export const usePromptStore = create<PromptStore>()(
({
title,
content,
} as Prompt)
} as Prompt),
);
})
.concat([...(state?.prompts?.values() ?? [])]);
const allPromptsForSearch = builtinPrompts.reduce(
(pre, cur) => pre.concat(cur),
[]
[],
);
SearchService.count.builtin = res.en.length + res.cn.length;
SearchService.init(allPromptsForSearch);
});
},
}
)
},
),
);

View File

@@ -1,7 +1,7 @@
import { create } from "zustand";
import { persist } from "zustand/middleware";
import { FETCH_COMMIT_URL } from "../constant";
import { getCurrentCommitId } from "../utils";
import { FETCH_COMMIT_URL, FETCH_TAG_URL } from "../constant";
import { getCurrentVersion } from "../utils";
export interface UpdateStore {
lastUpdate: number;
@@ -19,16 +19,17 @@ export const useUpdateStore = create<UpdateStore>()(
remoteId: "",
async getLatestCommitId(force = false) {
const overOneHour = Date.now() - get().lastUpdate > 3600 * 1000;
const shouldFetch = force || overOneHour;
const overTenMins = Date.now() - get().lastUpdate > 10 * 60 * 1000;
const shouldFetch = force || overTenMins;
if (!shouldFetch) {
return getCurrentCommitId();
return getCurrentVersion();
}
try {
// const data = await (await fetch(FETCH_TAG_URL)).json();
// const remoteId = data[0].name as string;
const data = await (await fetch(FETCH_COMMIT_URL)).json();
const sha = data[0].sha as string;
const remoteId = sha.substring(0, 7);
const remoteId = (data[0].sha as string).substring(0, 7);
set(() => ({
lastUpdate: Date.now(),
remoteId,
@@ -37,13 +38,13 @@ export const useUpdateStore = create<UpdateStore>()(
return remoteId;
} catch (error) {
console.error("[Fetch Upstream Commit Id]", error);
return getCurrentCommitId();
return getCurrentVersion();
}
},
}),
{
name: UPDATE_KEY,
version: 1,
}
)
},
),
);

View File

@@ -53,12 +53,13 @@
--sidebar-width: 300px;
--window-content-width: calc(100% - var(--sidebar-width));
--message-max-width: 80%;
--full-height: 100%;
}
@media only screen and (max-width: 600px) {
:root {
--window-width: 100vw;
--window-height: 100vh;
--window-height: var(--full-height);
--sidebar-width: 100vw;
--window-content-width: var(--window-width);
--message-max-width: 100%;
@@ -74,20 +75,23 @@
@include dark;
}
}
html {
height: var(--full-height);
}
body {
background-color: var(--gray);
color: var(--black);
margin: 0;
padding: 0;
height: 100vh;
height: var(--full-height);
width: 100vw;
display: flex;
justify-content: center;
align-items: center;
user-select: none;
font-family: "Noto Sans SC", "SF Pro SC", "SF Pro Text", "SF Pro Icons",
"PingFang SC", "Helvetica Neue", "Helvetica", "Arial", sans-serif;
"PingFang SC", "Helvetica Neue", "Helvetica", "Arial", sans-serif;
@media only screen and (max-width: 600px) {
background-color: var(--second);
@@ -119,6 +123,11 @@ select {
cursor: pointer;
background-color: var(--white);
color: var(--black);
text-align: center;
}
input {
text-align: center;
}
input[type="checkbox"] {
@@ -196,7 +205,7 @@ div.math {
position: fixed;
top: 0;
left: 0;
height: 100vh;
height: var(--full-height);
width: 100vw;
background-color: rgba($color: #000000, $alpha: 0.5);
display: flex;

View File

@@ -120,33 +120,3 @@
cursor: help;
}
}
@mixin light {
.markdown-body pre {
filter: invert(1) hue-rotate(90deg) brightness(1.3);
}
}
@mixin dark {
.markdown-body pre {
filter: none;
}
}
:root {
@include light();
}
.light {
@include light();
}
.dark {
@include dark();
}
@media (prefers-color-scheme: dark) {
:root {
@include dark();
}
}

View File

@@ -2,15 +2,7 @@ import { showToast } from "./components/ui-lib";
import Locale from "./locales";
export function trimTopic(topic: string) {
const s = topic.split("");
let lastChar = s.at(-1); // 获取 s 的最后一个字符
let pattern = /[,。!?、]/; // 定义匹配中文标点符号的正则表达式
while (lastChar && pattern.test(lastChar!)) {
s.pop();
lastChar = s.at(-1);
}
return s.join("");
return topic.replace(/[,。!?、,.!?]*$/, "");
}
export function copyToClipboard(text: string) {
@@ -28,7 +20,7 @@ export function downloadAs(text: string, filename: string) {
const element = document.createElement("a");
element.setAttribute(
"href",
"data:text/plain;charset=utf-8," + encodeURIComponent(text)
"data:text/plain;charset=utf-8," + encodeURIComponent(text),
);
element.setAttribute("download", filename);
@@ -45,6 +37,10 @@ export function isIOS() {
return /iphone|ipad|ipod/.test(userAgent);
}
export function isMobileScreen() {
return window.innerWidth <= 600;
}
export function selectOrCopy(el: HTMLElement, content: string) {
const currentSelection = window.getSelection();
@@ -61,7 +57,7 @@ export function queryMeta(key: string, defaultValue?: string): string {
let ret: string;
if (document) {
const meta = document.head.querySelector(
`meta[name='${key}']`
`meta[name='${key}']`,
) as HTMLMetaElement;
ret = meta?.content ?? "";
} else {
@@ -72,7 +68,7 @@ export function queryMeta(key: string, defaultValue?: string): string {
}
let currentId: string;
export function getCurrentCommitId() {
export function getCurrentVersion() {
if (currentId) {
return currentId;
}

View File

@@ -25,6 +25,7 @@
"react-markdown": "^8.0.5",
"rehype-katex": "^6.0.2",
"rehype-prism-plus": "^1.5.1",
"remark-breaks": "^3.0.2",
"remark-gfm": "^3.0.1",
"remark-math": "^5.1.1",
"sass": "^1.59.2",
@@ -38,6 +39,7 @@
"@types/react-dom": "^18.0.11",
"@types/react-katex": "^3.0.0",
"@types/spark-md5": "^3.0.2",
"array.prototype.at": "^1.1.1",
"cross-env": "^7.0.3",
"eslint": "^8.36.0",
"eslint-config-next": "13.2.3",

View File

@@ -1,10 +1,14 @@
import fetch from "node-fetch";
import fs from "fs/promises";
const CN_URL =
const RAW_CN_URL =
"https://raw.githubusercontent.com/PlexPt/awesome-chatgpt-prompts-zh/main/prompts-zh.json";
const EN_URL =
const CN_URL =
"https://cdn.jsdelivr.net/gh/PlexPt/awesome-chatgpt-prompts-zh@main/prompts-zh.json";
const RAW_EN_URL =
"https://raw.githubusercontent.com/f/awesome-chatgpt-prompts/main/prompts.csv";
const EN_URL =
"https://cdn.jsdelivr.net/gh/f/awesome-chatgpt-prompts@main/prompts.csv";
const FILE = "./public/prompts.json";
async function fetchCN() {

12850
yarn.lock

File diff suppressed because it is too large Load Diff