resolve conflict
This commit is contained in:
commit
add9f9d9cb
|
@ -1,6 +1,7 @@
|
||||||
name: Publish Docker image
|
name: Publish Docker image
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
release:
|
release:
|
||||||
types: [published]
|
types: [published]
|
||||||
|
|
||||||
|
@ -9,25 +10,43 @@ jobs:
|
||||||
name: Push Docker image to Docker Hub
|
name: Push Docker image to Docker Hub
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Check out the repo
|
-
|
||||||
|
name: Check out the repo
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
|
-
|
||||||
- name: Log in to Docker Hub
|
name: Log in to Docker Hub
|
||||||
uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9
|
uses: docker/login-action@v2
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKER_USERNAME }}
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
|
|
||||||
- name: Extract metadata (tags, labels) for Docker
|
-
|
||||||
|
name: Extract metadata (tags, labels) for Docker
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38
|
uses: docker/metadata-action@v4
|
||||||
with:
|
with:
|
||||||
images: yidadaa/chatgpt-next-web
|
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:
|
with:
|
||||||
context: .
|
context: .
|
||||||
|
platforms: linux/amd64
|
||||||
push: true
|
push: true
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
cache-from: type=gha
|
||||||
|
cache-to: type=gha,mode=max
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
# .github/workflows/sync.yml
|
||||||
|
name: Sync Fork
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: "0 8 * * *" # 每天0点触发
|
||||||
|
jobs:
|
||||||
|
repo-sync:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: TG908/fork-sync@v1.1
|
||||||
|
with:
|
||||||
|
github_token: ${{ secrets.GITHUB_TOKEN }} # 这个 token action 会默认配置, 这里只需这样写就行
|
||||||
|
owner: Yidadaa # fork 上游项目 owner
|
||||||
|
head: main # fork 上游项目需要同步的分支
|
||||||
|
base: main # 需要同步到本项目的目标分支
|
|
@ -6,13 +6,9 @@ RUN apk add --no-cache libc6-compat
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY package.json yarn.lock* package-lock.json* ./
|
COPY package.json yarn.lock ./
|
||||||
|
|
||||||
RUN \
|
RUN yarn install
|
||||||
if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
|
|
||||||
elif [ -f package-lock.json ]; then npm ci; \
|
|
||||||
else echo "Lockfile not found." && exit 1; \
|
|
||||||
fi
|
|
||||||
|
|
||||||
FROM base AS builder
|
FROM base AS builder
|
||||||
|
|
||||||
|
|
29
README.md
29
README.md
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
One-Click to deploy your own ChatGPT web UI.
|
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)
|
||||||
|
|
||||||
[](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)
|
[](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,7 +36,17 @@ One-Click to deploy your own ChatGPT web UI.
|
||||||
- Automatically compresses chat history to support long conversations while also saving your tokens
|
- Automatically compresses chat history to support long conversations while also saving your tokens
|
||||||
- One-click export all chat history with full Markdown support
|
- 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);
|
1. 准备好你的 [OpenAI API Key](https://platform.openai.com/account/api-keys);
|
||||||
2. 点击右侧按钮开始部署:
|
2. 点击右侧按钮开始部署:
|
||||||
|
@ -169,15 +179,12 @@ docker run -d -p 3000:3000 -e OPENAI_API_KEY="" -e CODE="" yidadaa/chatgpt-next-
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## 说明 Attention
|
|
||||||
|
|
||||||
本项目的演示地址所用的 OpenAI 账户的免费额度将于 2023-04-01 过期,届时将无法通过演示地址在线体验。
|
## 捐赠 Donate USDT
|
||||||
|
> BNB Smart Chain (BEP 20)
|
||||||
如果你想贡献出自己的 API Key,可以通过作者主页的邮箱发送给作者,并标注过期时间。
|
```
|
||||||
|
0x67cD02c7EB62641De576a1fA3EdB32eA0c3ffD89
|
||||||
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.
|
|
||||||
|
|
||||||
## 鸣谢 Special Thanks
|
## 鸣谢 Special Thanks
|
||||||
|
|
||||||
|
@ -194,4 +201,4 @@ If you would like to contribute your API key, you can email it to the author and
|
||||||
|
|
||||||
## LICENSE
|
## 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)
|
||||||
|
|
|
@ -26,13 +26,13 @@
|
||||||
@media only screen and (min-width: 600px) {
|
@media only screen and (min-width: 600px) {
|
||||||
.tight-container {
|
.tight-container {
|
||||||
--window-width: 100vw;
|
--window-width: 100vw;
|
||||||
--window-height: 100vh;
|
--window-height: var(--full-height);
|
||||||
--window-content-width: calc(100% - var(--sidebar-width));
|
--window-content-width: calc(100% - var(--sidebar-width));
|
||||||
|
|
||||||
@include container();
|
@include container();
|
||||||
|
|
||||||
max-width: 100vw;
|
max-width: 100vw;
|
||||||
max-height: 100vh;
|
max-height: var(--full-height);
|
||||||
|
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
}
|
}
|
||||||
|
@ -91,7 +91,7 @@
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: -100%;
|
left: -100%;
|
||||||
z-index: 999;
|
z-index: 999;
|
||||||
height: 100vh;
|
height: var(--full-height);
|
||||||
transition: all ease 0.3s;
|
transition: all ease 0.3s;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
@ -306,7 +306,6 @@
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
margin-bottom: 100px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-body-title {
|
.chat-body-title {
|
||||||
|
@ -430,9 +429,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-input-panel {
|
.chat-input-panel {
|
||||||
position: absolute;
|
|
||||||
bottom: 0px;
|
|
||||||
display: flex;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|
|
@ -24,8 +24,14 @@ import LeftIcon from "../icons/left.svg";
|
||||||
import RightIcon from "../icons/right.svg";
|
import RightIcon from "../icons/right.svg";
|
||||||
|
|
||||||
import { Message, SubmitKey, useChatStore, ChatSession } from "../store";
|
import { Message, SubmitKey, useChatStore, ChatSession } from "../store";
|
||||||
import { showModal } from "./ui-lib";
|
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 Locale from "../locales";
|
||||||
|
|
||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
|
@ -274,6 +280,7 @@ export function Chat(props: {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
chatStore.onUserInput(userInput).then(() => setIsLoading(false));
|
chatStore.onUserInput(userInput).then(() => setIsLoading(false));
|
||||||
setUserInput("");
|
setUserInput("");
|
||||||
|
setPromptHints([]);
|
||||||
inputRef.current?.focus();
|
inputRef.current?.focus();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -318,9 +325,7 @@ export function Chat(props: {
|
||||||
|
|
||||||
// for auto-scroll
|
// for auto-scroll
|
||||||
const latestMessageRef = useRef<HTMLDivElement>(null);
|
const latestMessageRef = useRef<HTMLDivElement>(null);
|
||||||
|
const [autoScroll, setAutoScroll] = useState(true);
|
||||||
// wont scroll while hovering messages
|
|
||||||
const [autoScroll, setAutoScroll] = useState(false);
|
|
||||||
|
|
||||||
// preview messages
|
// preview messages
|
||||||
const messages = (session.messages as RenderMessage[])
|
const messages = (session.messages as RenderMessage[])
|
||||||
|
@ -353,7 +358,17 @@ export function Chat(props: {
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const dom = latestMessageRef.current;
|
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({
|
dom.scrollIntoView({
|
||||||
block: "end",
|
block: "end",
|
||||||
});
|
});
|
||||||
|
@ -473,6 +488,7 @@ export function Chat(props: {
|
||||||
className="markdown-body"
|
className="markdown-body"
|
||||||
style={{ fontSize: `${fontSize}px` }}
|
style={{ fontSize: `${fontSize}px` }}
|
||||||
onContextMenu={(e) => onRightClick(e, message)}
|
onContextMenu={(e) => onRightClick(e, message)}
|
||||||
|
onDoubleClickCapture={() => setUserInput(message.content)}
|
||||||
>
|
>
|
||||||
<Markdown content={message.content} />
|
<Markdown content={message.content} />
|
||||||
</div>
|
</div>
|
||||||
|
@ -508,7 +524,7 @@ export function Chat(props: {
|
||||||
onFocus={() => setAutoScroll(true)}
|
onFocus={() => setAutoScroll(true)}
|
||||||
onBlur={() => {
|
onBlur={() => {
|
||||||
setAutoScroll(false);
|
setAutoScroll(false);
|
||||||
setTimeout(() => setPromptHints([]), 100);
|
setTimeout(() => setPromptHints([]), 500);
|
||||||
}}
|
}}
|
||||||
autoFocus={!props?.sidebarShowing}
|
autoFocus={!props?.sidebarShowing}
|
||||||
/>
|
/>
|
||||||
|
@ -641,7 +657,9 @@ export function Home() {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`${
|
className={`${
|
||||||
config.tightBorder ? styles["tight-container"] : styles.container
|
config.tightBorder && !isMobileScreen()
|
||||||
|
? styles["tight-container"]
|
||||||
|
: styles.container
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import ReactMarkdown from "react-markdown";
|
import ReactMarkdown from "react-markdown";
|
||||||
import "katex/dist/katex.min.css";
|
import "katex/dist/katex.min.css";
|
||||||
import RemarkMath from "remark-math";
|
import RemarkMath from "remark-math";
|
||||||
|
import RemarkBreaks from "remark-breaks";
|
||||||
import RehypeKatex from "rehype-katex";
|
import RehypeKatex from "rehype-katex";
|
||||||
import RemarkGfm from "remark-gfm";
|
import RemarkGfm from "remark-gfm";
|
||||||
import RehypePrsim from "rehype-prism-plus";
|
import RehypePrsim from "rehype-prism-plus";
|
||||||
|
@ -29,7 +30,7 @@ export function PreCode(props: { children: any }) {
|
||||||
export function Markdown(props: { content: string }) {
|
export function Markdown(props: { content: string }) {
|
||||||
return (
|
return (
|
||||||
<ReactMarkdown
|
<ReactMarkdown
|
||||||
remarkPlugins={[RemarkMath, RemarkGfm]}
|
remarkPlugins={[RemarkMath, RemarkGfm, RemarkBreaks]}
|
||||||
rehypePlugins={[RehypeKatex, [RehypePrsim, { ignoreMissing: true }]]}
|
rehypePlugins={[RehypeKatex, [RehypePrsim, { ignoreMissing: true }]]}
|
||||||
components={{
|
components={{
|
||||||
pre: PreCode,
|
pre: PreCode,
|
||||||
|
|
|
@ -23,7 +23,7 @@ import {
|
||||||
import { Avatar, PromptHints } from "./home";
|
import { Avatar, PromptHints } from "./home";
|
||||||
|
|
||||||
import Locale, { AllLangs, changeLang, getLang } from "../locales";
|
import Locale, { AllLangs, changeLang, getLang } from "../locales";
|
||||||
import { getCurrentCommitId } from "../utils";
|
import { getCurrentVersion } from "../utils";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { UPDATE_URL } from "../constant";
|
import { UPDATE_URL } from "../constant";
|
||||||
import { SearchService, usePromptStore } from "../store/prompt";
|
import { SearchService, usePromptStore } from "../store/prompt";
|
||||||
|
@ -60,7 +60,7 @@ export function Settings(props: { closeSettings: () => void }) {
|
||||||
|
|
||||||
const updateStore = useUpdateStore();
|
const updateStore = useUpdateStore();
|
||||||
const [checkingUpdate, setCheckingUpdate] = useState(false);
|
const [checkingUpdate, setCheckingUpdate] = useState(false);
|
||||||
const currentId = getCurrentCommitId();
|
const currentId = getCurrentVersion();
|
||||||
const remoteId = updateStore.remoteId;
|
const remoteId = updateStore.remoteId;
|
||||||
const hasNewVersion = currentId !== remoteId;
|
const hasNewVersion = currentId !== remoteId;
|
||||||
|
|
||||||
|
@ -267,19 +267,17 @@ export function Settings(props: { closeSettings: () => void }) {
|
||||||
></input>
|
></input>
|
||||||
</SettingItem>
|
</SettingItem>
|
||||||
|
|
||||||
<div className="no-mobile">
|
<SettingItem title={Locale.Settings.TightBorder}>
|
||||||
<SettingItem title={Locale.Settings.TightBorder}>
|
<input
|
||||||
<input
|
type="checkbox"
|
||||||
type="checkbox"
|
checked={config.tightBorder}
|
||||||
checked={config.tightBorder}
|
onChange={(e) =>
|
||||||
onChange={(e) =>
|
updateConfig(
|
||||||
updateConfig(
|
(config) => (config.tightBorder = e.currentTarget.checked),
|
||||||
(config) => (config.tightBorder = e.currentTarget.checked),
|
)
|
||||||
)
|
}
|
||||||
}
|
></input>
|
||||||
></input>
|
</SettingItem>
|
||||||
</SettingItem>
|
|
||||||
</div>
|
|
||||||
</List>
|
</List>
|
||||||
<List>
|
<List>
|
||||||
<SettingItem
|
<SettingItem
|
||||||
|
@ -375,7 +373,7 @@ export function Settings(props: { closeSettings: () => void }) {
|
||||||
type="range"
|
type="range"
|
||||||
title={config.historyMessageCount.toString()}
|
title={config.historyMessageCount.toString()}
|
||||||
value={config.historyMessageCount}
|
value={config.historyMessageCount}
|
||||||
min="2"
|
min="0"
|
||||||
max="25"
|
max="25"
|
||||||
step="2"
|
step="2"
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
|
|
|
@ -3,3 +3,4 @@ export const REPO = "ChatGPT-Next-Web";
|
||||||
export const REPO_URL = `https://github.com/${OWNER}/${REPO}`;
|
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 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_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`;
|
||||||
|
|
|
@ -8,11 +8,12 @@ import { ACCESS_CODES, IS_IN_DOCKER } from "./api/access";
|
||||||
let COMMIT_ID: string | undefined;
|
let COMMIT_ID: string | undefined;
|
||||||
try {
|
try {
|
||||||
COMMIT_ID = process
|
COMMIT_ID = process
|
||||||
|
// .execSync("git describe --tags --abbrev=0")
|
||||||
.execSync("git rev-parse --short HEAD")
|
.execSync("git rev-parse --short HEAD")
|
||||||
.toString()
|
.toString()
|
||||||
.trim();
|
.trim();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("No git or not from git repo.")
|
console.error("No git or not from git repo.");
|
||||||
}
|
}
|
||||||
|
|
||||||
export const metadata = {
|
export const metadata = {
|
||||||
|
@ -22,13 +23,13 @@ export const metadata = {
|
||||||
title: "ChatGPT Next Web",
|
title: "ChatGPT Next Web",
|
||||||
statusBarStyle: "black-translucent",
|
statusBarStyle: "black-translucent",
|
||||||
},
|
},
|
||||||
themeColor: "#fafafa"
|
themeColor: "#fafafa",
|
||||||
};
|
};
|
||||||
|
|
||||||
function Meta() {
|
function Meta() {
|
||||||
const metas = {
|
const metas = {
|
||||||
version: COMMIT_ID ?? "unknown",
|
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 (
|
return (
|
||||||
|
|
|
@ -57,6 +57,7 @@ const cn = {
|
||||||
cn: "简体中文",
|
cn: "简体中文",
|
||||||
en: "English",
|
en: "English",
|
||||||
tw: "繁體中文",
|
tw: "繁體中文",
|
||||||
|
es: "Español",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Avatar: "头像",
|
Avatar: "头像",
|
||||||
|
|
|
@ -54,11 +54,12 @@ const en: LocaleType = {
|
||||||
Close: "Close",
|
Close: "Close",
|
||||||
},
|
},
|
||||||
Lang: {
|
Lang: {
|
||||||
Name: "语言",
|
Name: "Language",
|
||||||
Options: {
|
Options: {
|
||||||
cn: "简体中文",
|
cn: "简体中文",
|
||||||
en: "English",
|
en: "English",
|
||||||
tw: "繁體中文",
|
tw: "繁體中文",
|
||||||
|
es: "Español",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Avatar: "Avatar",
|
Avatar: "Avatar",
|
||||||
|
|
|
@ -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;
|
|
@ -1,10 +1,11 @@
|
||||||
import CN from "./cn";
|
import CN from "./cn";
|
||||||
import EN from "./en";
|
import EN from "./en";
|
||||||
import TW from "./tw";
|
import TW from "./tw";
|
||||||
|
import ES from "./es";
|
||||||
|
|
||||||
export type { LocaleType } from "./cn";
|
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];
|
type Lang = (typeof AllLangs)[number];
|
||||||
|
|
||||||
const LANG_KEY = "lang";
|
const LANG_KEY = "lang";
|
||||||
|
@ -44,6 +45,8 @@ export function getLang(): Lang {
|
||||||
return "cn";
|
return "cn";
|
||||||
} else if (lang.includes("tw")) {
|
} else if (lang.includes("tw")) {
|
||||||
return "tw";
|
return "tw";
|
||||||
|
} else if (lang.includes("es")) {
|
||||||
|
return "es";
|
||||||
} else {
|
} else {
|
||||||
return "en";
|
return "en";
|
||||||
}
|
}
|
||||||
|
@ -54,4 +57,4 @@ export function changeLang(lang: Lang) {
|
||||||
location.reload();
|
location.reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
export default { en: EN, cn: CN, tw: TW }[getLang()];
|
export default { en: EN, cn: CN, tw: TW, es: ES }[getLang()];
|
||||||
|
|
|
@ -53,11 +53,12 @@ const tw: LocaleType = {
|
||||||
Close: "關閉",
|
Close: "關閉",
|
||||||
},
|
},
|
||||||
Lang: {
|
Lang: {
|
||||||
Name: "語言",
|
Name: "Language",
|
||||||
Options: {
|
Options: {
|
||||||
cn: "简体中文",
|
cn: "简体中文",
|
||||||
en: "English",
|
en: "English",
|
||||||
tw: "繁體中文",
|
tw: "繁體中文",
|
||||||
|
es: "Español",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Avatar: "大頭貼",
|
Avatar: "大頭貼",
|
||||||
|
|
|
@ -69,10 +69,9 @@ export async function requestChat(messages: Message[]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function requestUsage() {
|
export async function requestUsage() {
|
||||||
const res = await requestOpenaiClient("dashboard/billing/credit_grants")(
|
const res = await requestOpenaiClient(
|
||||||
null,
|
"dashboard/billing/credit_grants?_vercel_no_cache=1",
|
||||||
"GET",
|
)(null, "GET");
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = (await res.json()) as {
|
const response = (await res.json()) as {
|
||||||
|
|
|
@ -89,7 +89,9 @@ export function isValidNumber(x: number, min: number, max: number) {
|
||||||
return typeof x === "number" && x <= max && x >= min;
|
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: {
|
const validator: {
|
||||||
[k in keyof ModelConfig]: (x: ModelConfig[keyof ModelConfig]) => boolean;
|
[k in keyof ModelConfig]: (x: ModelConfig[keyof ModelConfig]) => boolean;
|
||||||
} = {
|
} = {
|
||||||
|
@ -103,7 +105,7 @@ export function filterConfig(config: ModelConfig): Partial<ModelConfig> {
|
||||||
return isValidNumber(x as number, -2, 2);
|
return isValidNumber(x as number, -2, 2);
|
||||||
},
|
},
|
||||||
temperature(x) {
|
temperature(x) {
|
||||||
return isValidNumber(x as number, 0, 1);
|
return isValidNumber(x as number, 0, 2);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
import { persist } from "zustand/middleware";
|
import { persist } from "zustand/middleware";
|
||||||
import { FETCH_COMMIT_URL } from "../constant";
|
import { FETCH_COMMIT_URL, FETCH_TAG_URL } from "../constant";
|
||||||
import { getCurrentCommitId } from "../utils";
|
import { getCurrentVersion } from "../utils";
|
||||||
|
|
||||||
export interface UpdateStore {
|
export interface UpdateStore {
|
||||||
lastUpdate: number;
|
lastUpdate: number;
|
||||||
|
@ -19,16 +19,17 @@ export const useUpdateStore = create<UpdateStore>()(
|
||||||
remoteId: "",
|
remoteId: "",
|
||||||
|
|
||||||
async getLatestCommitId(force = false) {
|
async getLatestCommitId(force = false) {
|
||||||
const overOneHour = Date.now() - get().lastUpdate > 3600 * 1000;
|
const overTenMins = Date.now() - get().lastUpdate > 10 * 60 * 1000;
|
||||||
const shouldFetch = force || overOneHour;
|
const shouldFetch = force || overTenMins;
|
||||||
if (!shouldFetch) {
|
if (!shouldFetch) {
|
||||||
return getCurrentCommitId();
|
return getCurrentVersion();
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
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 data = await (await fetch(FETCH_COMMIT_URL)).json();
|
||||||
const sha = data[0].sha as string;
|
const remoteId = (data[0].sha as string).substring(0, 7);
|
||||||
const remoteId = sha.substring(0, 7);
|
|
||||||
set(() => ({
|
set(() => ({
|
||||||
lastUpdate: Date.now(),
|
lastUpdate: Date.now(),
|
||||||
remoteId,
|
remoteId,
|
||||||
|
@ -37,13 +38,13 @@ export const useUpdateStore = create<UpdateStore>()(
|
||||||
return remoteId;
|
return remoteId;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("[Fetch Upstream Commit Id]", error);
|
console.error("[Fetch Upstream Commit Id]", error);
|
||||||
return getCurrentCommitId();
|
return getCurrentVersion();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
name: UPDATE_KEY,
|
name: UPDATE_KEY,
|
||||||
version: 1,
|
version: 1,
|
||||||
}
|
},
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -53,14 +53,19 @@
|
||||||
--sidebar-width: 380px;
|
--sidebar-width: 380px;
|
||||||
--sidebar-collapse-width: 8vh;
|
--sidebar-collapse-width: 8vh;
|
||||||
--window-content-width: calc(100% - var(--sidebar-width));
|
--window-content-width: calc(100% - var(--sidebar-width));
|
||||||
|
<<<<<<< HEAD
|
||||||
--window-content-width-collapse: calc(100% - var(--sidebar-collapse-width));
|
--window-content-width-collapse: calc(100% - var(--sidebar-collapse-width));
|
||||||
--message-max-width: 90%;
|
--message-max-width: 90%;
|
||||||
|
=======
|
||||||
|
--message-max-width: 80%;
|
||||||
|
--full-height: 100%;
|
||||||
|
>>>>>>> upstream/main
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: 600px) {
|
@media only screen and (max-width: 600px) {
|
||||||
:root {
|
:root {
|
||||||
--window-width: 100vw;
|
--window-width: 100vw;
|
||||||
--window-height: 100vh;
|
--window-height: var(--full-height);
|
||||||
--sidebar-width: 100vw;
|
--sidebar-width: 100vw;
|
||||||
--window-content-width: var(--window-width);
|
--window-content-width: var(--window-width);
|
||||||
--message-max-width: 100%;
|
--message-max-width: 100%;
|
||||||
|
@ -76,13 +81,16 @@
|
||||||
@include dark;
|
@include dark;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
html {
|
||||||
|
height: var(--full-height);
|
||||||
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
background-color: var(--gray);
|
background-color: var(--gray);
|
||||||
color: var(--black);
|
color: var(--black);
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
height: 100vh;
|
height: var(--full-height);
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
@ -121,6 +129,11 @@ select {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
background-color: var(--white);
|
background-color: var(--white);
|
||||||
color: var(--black);
|
color: var(--black);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type="checkbox"] {
|
input[type="checkbox"] {
|
||||||
|
@ -198,7 +211,7 @@ div.math {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
height: 100vh;
|
height: var(--full-height);
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
background-color: rgba($color: #000000, $alpha: 0.5);
|
background-color: rgba($color: #000000, $alpha: 0.5);
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
@ -120,33 +120,3 @@
|
||||||
cursor: help;
|
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
12
app/utils.ts
12
app/utils.ts
|
@ -4,7 +4,7 @@ import Locale from "./locales";
|
||||||
export function trimTopic(topic: string) {
|
export function trimTopic(topic: string) {
|
||||||
const s = topic.split("");
|
const s = topic.split("");
|
||||||
let lastChar = s.at(-1); // 获取 s 的最后一个字符
|
let lastChar = s.at(-1); // 获取 s 的最后一个字符
|
||||||
let pattern = /[,。!?、]/; // 定义匹配中文标点符号的正则表达式
|
let pattern = /[,。!?、,.!?]/; // 定义匹配中文和英文标点符号的正则表达式
|
||||||
while (lastChar && pattern.test(lastChar!)) {
|
while (lastChar && pattern.test(lastChar!)) {
|
||||||
s.pop();
|
s.pop();
|
||||||
lastChar = s.at(-1);
|
lastChar = s.at(-1);
|
||||||
|
@ -28,7 +28,7 @@ export function downloadAs(text: string, filename: string) {
|
||||||
const element = document.createElement("a");
|
const element = document.createElement("a");
|
||||||
element.setAttribute(
|
element.setAttribute(
|
||||||
"href",
|
"href",
|
||||||
"data:text/plain;charset=utf-8," + encodeURIComponent(text)
|
"data:text/plain;charset=utf-8," + encodeURIComponent(text),
|
||||||
);
|
);
|
||||||
element.setAttribute("download", filename);
|
element.setAttribute("download", filename);
|
||||||
|
|
||||||
|
@ -45,6 +45,10 @@ export function isIOS() {
|
||||||
return /iphone|ipad|ipod/.test(userAgent);
|
return /iphone|ipad|ipod/.test(userAgent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isMobileScreen() {
|
||||||
|
return window.innerWidth <= 600;
|
||||||
|
}
|
||||||
|
|
||||||
export function selectOrCopy(el: HTMLElement, content: string) {
|
export function selectOrCopy(el: HTMLElement, content: string) {
|
||||||
const currentSelection = window.getSelection();
|
const currentSelection = window.getSelection();
|
||||||
|
|
||||||
|
@ -61,7 +65,7 @@ export function queryMeta(key: string, defaultValue?: string): string {
|
||||||
let ret: string;
|
let ret: string;
|
||||||
if (document) {
|
if (document) {
|
||||||
const meta = document.head.querySelector(
|
const meta = document.head.querySelector(
|
||||||
`meta[name='${key}']`
|
`meta[name='${key}']`,
|
||||||
) as HTMLMetaElement;
|
) as HTMLMetaElement;
|
||||||
ret = meta?.content ?? "";
|
ret = meta?.content ?? "";
|
||||||
} else {
|
} else {
|
||||||
|
@ -72,7 +76,7 @@ export function queryMeta(key: string, defaultValue?: string): string {
|
||||||
}
|
}
|
||||||
|
|
||||||
let currentId: string;
|
let currentId: string;
|
||||||
export function getCurrentCommitId() {
|
export function getCurrentVersion() {
|
||||||
if (currentId) {
|
if (currentId) {
|
||||||
return currentId;
|
return currentId;
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-markdown": "^8.0.5",
|
"react-markdown": "^8.0.5",
|
||||||
|
"remark-breaks": "^3.0.2",
|
||||||
"rehype-katex": "^6.0.2",
|
"rehype-katex": "^6.0.2",
|
||||||
"rehype-prism-plus": "^1.5.1",
|
"rehype-prism-plus": "^1.5.1",
|
||||||
"remark-gfm": "^3.0.1",
|
"remark-gfm": "^3.0.1",
|
||||||
|
|
|
@ -1,10 +1,14 @@
|
||||||
import fetch from "node-fetch";
|
import fetch from "node-fetch";
|
||||||
import fs from "fs/promises";
|
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";
|
"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";
|
"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";
|
const FILE = "./public/prompts.json";
|
||||||
|
|
||||||
async function fetchCN() {
|
async function fetchCN() {
|
||||||
|
|
Loading…
Reference in New Issue