Compare commits

..

46 Commits
v1.7.2 ... 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
GOWxx
0c9add7988 feat: support history message count to zero 2023-03-30 18:50:58 +08:00
25 changed files with 359 additions and 137 deletions

View File

@@ -28,7 +28,7 @@ jobs:
images: yidadaa/chatgpt-next-web
tags: |
type=raw,value=latest
type=semver,pattern={{version}}
type=ref,event=tag
-
name: Set up QEMU
@@ -43,10 +43,10 @@ jobs:
uses: docker/build-push-action@v4
with:
context: .
platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64/v8
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 install --frozen-lockfile --network-timeout 100000; \
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

@@ -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 在某些区域被污染了,绑定自定义域名即可直连。
@@ -191,4 +201,4 @@ docker run -d -p 3000:3000 -e OPENAI_API_KEY="" -e CODE="" yidadaa/chatgpt-next-
## 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";
@@ -102,7 +108,7 @@ export function ChatList() {
state.currentSessionIndex,
state.selectSession,
state.removeSession,
]
],
);
return (
@@ -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) ||
@@ -196,7 +202,7 @@ export function Chat(props: {
setPromptHints(promptStore.search(text));
},
100,
{ leading: true, trailing: true }
{ leading: true, trailing: true },
);
const onPromptSelect = (prompt: Prompt) => {
@@ -210,7 +216,7 @@ export function Chat(props: {
if (!dom) return;
const paddingBottomNum: number = parseInt(
window.getComputedStyle(dom).paddingBottom,
10
10,
);
dom.scrollTop = dom.scrollHeight - dom.offsetHeight + paddingBottomNum;
};
@@ -250,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();
@@ -284,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[])
@@ -300,7 +304,7 @@ export function Chat(props: {
preview: true,
},
]
: []
: [],
)
.concat(
userInput.length > 0
@@ -312,14 +316,24 @@ export function Chat(props: {
preview: true,
},
]
: []
: [],
);
// auto scroll
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",
});
@@ -340,7 +354,7 @@ export function Chat(props: {
const newTopic = prompt(Locale.Chat.Rename, session.topic);
if (newTopic && newTopic !== session.topic) {
chatStore.updateCurrentSession(
(session) => (session.topic = newTopic!)
(session) => (session.topic = newTopic!),
);
}
}}
@@ -439,7 +453,10 @@ export function Chat(props: {
className="markdown-body"
style={{ fontSize: `${fontSize}px` }}
onContextMenu={(e) => onRightClick(e, message)}
onDoubleClickCapture={() => setUserInput(message.content)}
onDoubleClickCapture={() => {
if (!isMobileScreen()) return;
setUserInput(message.content);
}}
>
<Markdown content={message.content} />
</div>
@@ -456,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>
@@ -471,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}
/>
@@ -586,7 +603,7 @@ export function Home() {
state.newSession,
state.currentSessionIndex,
state.removeSession,
]
],
);
const loading = !useHasHydrated();
const [showSideBar, setShowSideBar] = useState(true);
@@ -604,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

@@ -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) {
@@ -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();
@@ -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

@@ -23,9 +23,9 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-markdown": "^8.0.5",
"remark-breaks": "^3.0.2",
"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",
@@ -39,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() {

View File

@@ -1570,6 +1570,16 @@ array-union@^2.1.0:
resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d"
integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==
array.prototype.at@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/array.prototype.at/-/array.prototype.at-1.1.1.tgz#6deda3cd3c704afa16361387ea344e0b8d8831b5"
integrity sha512-n/wYNLJy/fVEU9EGPt2ww920hy1XX3XB2yTREFy1QsxctBgQV/tZIwg1G8jVxELna4pLCzg/xvvS/DDXtI4NNg==
dependencies:
call-bind "^1.0.2"
define-properties "^1.1.4"
es-abstract "^1.20.4"
es-shim-unscopables "^1.0.0"
array.prototype.flat@^1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz#ffc6576a7ca3efc2f46a143b9d1dda9b4b3cf5e2"