mirror of
https://github.com/NovaOSS/nova-betterchat.git
synced 2024-11-25 19:33:59 +01:00
Merge branch 'main' of github.com:ztjhz/ChatGPTFreeApp
This commit is contained in:
commit
01b6fc7479
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
|
@ -1,2 +1,2 @@
|
||||||
github: [ztjhz]
|
github: [ztjhz]
|
||||||
ko_fi: freechatgpt
|
ko_fi: betterchatgpt
|
||||||
|
|
|
@ -98,11 +98,11 @@ Better ChatGPT 已经包含了大量的功能。您可以使用以下功能:
|
||||||
|
|
||||||
如果您想支持我们的团队,请考虑通过以下方法之一赞助我们。每一份贡献,无论多小,都有助于我们维护和改善我们的服务。
|
如果您想支持我们的团队,请考虑通过以下方法之一赞助我们。每一份贡献,无论多小,都有助于我们维护和改善我们的服务。
|
||||||
|
|
||||||
| 付款方式 | 链接 |
|
| 付款方式 | 链接 |
|
||||||
| -------------- | -------------------------------------------------------------------------------------- |
|
| -------------- | ---------------------------------------------------------------------------------------- |
|
||||||
| 支付宝 (Ayaka) | <img src="https://ayaka14732.github.io/sponsor/alipay.jpg" width=150 /> |
|
| 支付宝 (Ayaka) | <img src="https://ayaka14732.github.io/sponsor/alipay.jpg" width=150 /> |
|
||||||
| 微信 (Ayaka) | <img src="https://ayaka14732.github.io/sponsor/wechat.png" width=150 /> |
|
| 微信 (Ayaka) | <img src="https://ayaka14732.github.io/sponsor/wechat.png" width=150 /> |
|
||||||
| KoFi | [![support](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/freechatgpt) |
|
| KoFi | [![support](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/betterchatgpt) |
|
||||||
|
|
||||||
感谢您成为我们社区的一员,我们期待着在未来为您提供更好的服务。
|
感谢您成为我们社区的一员,我们期待着在未来为您提供更好的服务。
|
||||||
|
|
||||||
|
|
10
README.md
10
README.md
|
@ -105,11 +105,11 @@ If you have enjoyed using our app, we kindly ask you to give this project a ⭐
|
||||||
|
|
||||||
If you would like to support the team, consider sponsoring us through one of the methods below. Every contribution, no matter how small, helps us to maintain and improve our service.
|
If you would like to support the team, consider sponsoring us through one of the methods below. Every contribution, no matter how small, helps us to maintain and improve our service.
|
||||||
|
|
||||||
| Payment Method | Link |
|
| Payment Method | Link |
|
||||||
| -------------- | -------------------------------------------------------------------------------------- |
|
| -------------- | ---------------------------------------------------------------------------------------- |
|
||||||
| KoFi | [![support](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/freechatgpt) |
|
| KoFi | [![support](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/betterchatgpt) |
|
||||||
| Alipay (Ayaka) | <img src="https://ayaka14732.github.io/sponsor/alipay.jpg" width=150 /> |
|
| Alipay (Ayaka) | <img src="https://ayaka14732.github.io/sponsor/alipay.jpg" width=150 /> |
|
||||||
| Wechat (Ayaka) | <img src="https://ayaka14732.github.io/sponsor/wechat.png" width=150 /> |
|
| Wechat (Ayaka) | <img src="https://ayaka14732.github.io/sponsor/wechat.png" width=150 /> |
|
||||||
|
|
||||||
Thank you for being a part of our community, and we look forward to serving you better in the future.
|
Thank you for being a part of our community, and we look forward to serving you better in the future.
|
||||||
|
|
||||||
|
|
|
@ -10,11 +10,11 @@
|
||||||
<meta name="twitter:image" content="https://bettergpt.chat/preview.jpg" />
|
<meta name="twitter:image" content="https://bettergpt.chat/preview.jpg" />
|
||||||
<meta
|
<meta
|
||||||
name="description"
|
name="description"
|
||||||
content="Access unrestricted ChatGPT from anywhere in the world, completely free of charge!"
|
content="Play and chat smarter with BetterChatGPT - an amazing open-source web app with a better UI for exploring OpenAI's ChatGPT API! "
|
||||||
/>
|
/>
|
||||||
<meta
|
<meta
|
||||||
name="twitter:description"
|
name="twitter:description"
|
||||||
content="Access unrestricted ChatGPT from anywhere in the world, completely free of charge!"
|
content="Play and chat smarter with BetterChatGPT - an amazing open-source web app with a better UI for exploring OpenAI's ChatGPT API! "
|
||||||
/>
|
/>
|
||||||
<meta name="twitter:title" content="Better ChatGPT" />
|
<meta name="twitter:title" content="Better ChatGPT" />
|
||||||
<meta name="twitter:card" content="summary_large_image" />
|
<meta name="twitter:card" content="summary_large_image" />
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"name": "chatgpt-free-app",
|
"name": "better-chatgpt",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|
|
@ -78,7 +78,7 @@ const AboutMenu = () => {
|
||||||
<p>{t('support.paragraph3', { ns: 'about' })}</p>
|
<p>{t('support.paragraph3', { ns: 'about' })}</p>
|
||||||
|
|
||||||
<div className='flex flex-col items-center gap-4 my-4'>
|
<div className='flex flex-col items-center gap-4 my-4'>
|
||||||
<a href='https://ko-fi.com/freechatgpt' target='_blank'>
|
<a href='https://ko-fi.com/betterchatgpt' target='_blank'>
|
||||||
<img
|
<img
|
||||||
src='/kofi.svg'
|
src='/kofi.svg'
|
||||||
alt='Support us through the Ko-fi platform.'
|
alt='Support us through the Ko-fi platform.'
|
||||||
|
|
|
@ -19,12 +19,14 @@ import TickIcon from '@icon/TickIcon';
|
||||||
import CrossIcon from '@icon/CrossIcon';
|
import CrossIcon from '@icon/CrossIcon';
|
||||||
import RefreshIcon from '@icon/RefreshIcon';
|
import RefreshIcon from '@icon/RefreshIcon';
|
||||||
import DownChevronArrow from '@icon/DownChevronArrow';
|
import DownChevronArrow from '@icon/DownChevronArrow';
|
||||||
|
import CopyIcon from '@icon/CopyIcon';
|
||||||
|
|
||||||
import useSubmit from '@hooks/useSubmit';
|
import useSubmit from '@hooks/useSubmit';
|
||||||
|
|
||||||
import { ChatInterface } from '@type/chat';
|
import { ChatInterface } from '@type/chat';
|
||||||
|
|
||||||
import PopupModal from '@components/PopupModal';
|
import PopupModal from '@components/PopupModal';
|
||||||
|
import TokenCount from '@components/TokenCount';
|
||||||
import CommandPrompt from './CommandPrompt';
|
import CommandPrompt from './CommandPrompt';
|
||||||
import CodeBlock from './CodeBlock';
|
import CodeBlock from './CodeBlock';
|
||||||
import { codeLanguageSubset } from '@constants/chat';
|
import { codeLanguageSubset } from '@constants/chat';
|
||||||
|
@ -126,6 +128,10 @@ const ContentView = React.memo(
|
||||||
handleSubmit();
|
handleSubmit();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleCopy = () => {
|
||||||
|
navigator.clipboard.writeText(content);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className='markdown prose w-full break-words dark:prose-invert dark share-gpt-message'>
|
<div className='markdown prose w-full break-words dark:prose-invert dark share-gpt-message'>
|
||||||
|
@ -157,14 +163,17 @@ const ContentView = React.memo(
|
||||||
<div className='flex justify-end gap-2 w-full mt-2'>
|
<div className='flex justify-end gap-2 w-full mt-2'>
|
||||||
{isDelete || (
|
{isDelete || (
|
||||||
<>
|
<>
|
||||||
{role === 'assistant' && messageIndex === lastMessageIndex && (
|
{!useStore.getState().generating &&
|
||||||
<RefreshButton onClick={handleRefresh} />
|
role === 'assistant' &&
|
||||||
)}
|
messageIndex === lastMessageIndex && (
|
||||||
|
<RefreshButton onClick={handleRefresh} />
|
||||||
|
)}
|
||||||
{messageIndex !== 0 && <UpButton onClick={handleMoveUp} />}
|
{messageIndex !== 0 && <UpButton onClick={handleMoveUp} />}
|
||||||
{messageIndex !== lastMessageIndex && (
|
{messageIndex !== lastMessageIndex && (
|
||||||
<DownButton onClick={handleMoveDown} />
|
<DownButton onClick={handleMoveDown} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<CopyButton onClick={handleCopy} />
|
||||||
<EditButton setIsEdit={setIsEdit} />
|
<EditButton setIsEdit={setIsEdit} />
|
||||||
<DeleteButton setIsDelete={setIsDelete} />
|
<DeleteButton setIsDelete={setIsDelete} />
|
||||||
</>
|
</>
|
||||||
|
@ -277,6 +286,7 @@ const UpButton = ({
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const RefreshButton = ({
|
const RefreshButton = ({
|
||||||
onClick,
|
onClick,
|
||||||
}: {
|
}: {
|
||||||
|
@ -284,6 +294,28 @@ const RefreshButton = ({
|
||||||
}) => {
|
}) => {
|
||||||
return <MessageButton icon={<RefreshIcon />} onClick={onClick} />;
|
return <MessageButton icon={<RefreshIcon />} onClick={onClick} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const CopyButton = ({
|
||||||
|
onClick,
|
||||||
|
}: {
|
||||||
|
onClick: React.MouseEventHandler<HTMLButtonElement>;
|
||||||
|
}) => {
|
||||||
|
const [isCopied, setIsCopied] = useState<boolean>(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MessageButton
|
||||||
|
icon={isCopied ? <TickIcon /> : <CopyIcon />}
|
||||||
|
onClick={(e) => {
|
||||||
|
onClick(e);
|
||||||
|
setIsCopied(true);
|
||||||
|
window.setTimeout(() => {
|
||||||
|
setIsCopied(false);
|
||||||
|
}, 3000);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const EditView = ({
|
const EditView = ({
|
||||||
content,
|
content,
|
||||||
setIsEdit,
|
setIsEdit,
|
||||||
|
@ -481,6 +513,7 @@ const EditViewButtons = React.memo(
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
{sticky && <TokenCount />}
|
||||||
<CommandPrompt _setContent={_setContent} />
|
<CommandPrompt _setContent={_setContent} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
29
src/components/TokenCount/TokenCount.tsx
Normal file
29
src/components/TokenCount/TokenCount.tsx
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import useStore from '@store/store';
|
||||||
|
import { shallow } from 'zustand/shallow';
|
||||||
|
|
||||||
|
import { countMessagesToken } from '@utils/messageUtils';
|
||||||
|
|
||||||
|
const TokenCount = React.memo(() => {
|
||||||
|
const [tokenCount, setTokenCount] = useState<number>(0);
|
||||||
|
const generating = useStore((state) => state.generating);
|
||||||
|
const messages = useStore(
|
||||||
|
(state) =>
|
||||||
|
state.chats ? state.chats[state.currentChatIndex].messages : [],
|
||||||
|
shallow
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!generating) setTokenCount(countMessagesToken(messages));
|
||||||
|
}, [messages, generating]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='absolute top-[-16px] right-0'>
|
||||||
|
<div className='text-xs italic text-gray-900 dark:text-gray-300'>
|
||||||
|
Tokens: {tokenCount}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default TokenCount;
|
1
src/components/TokenCount/index.ts
Normal file
1
src/components/TokenCount/index.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export { default } from './TokenCount';
|
36
src/main.css
36
src/main.css
|
@ -27,6 +27,42 @@
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.markdown table {
|
||||||
|
--tw-border-spacing-x: 0px;
|
||||||
|
--tw-border-spacing-y: 0px;
|
||||||
|
border-collapse: separate;
|
||||||
|
border-spacing: var(--tw-border-spacing-x) var(--tw-border-spacing-y);
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.markdown th {
|
||||||
|
background-color: rgba(236, 236, 241, 0.2);
|
||||||
|
border-bottom-width: 1px;
|
||||||
|
border-left-width: 1px;
|
||||||
|
border-top-width: 1px;
|
||||||
|
padding: 0.25rem 0.75rem;
|
||||||
|
}
|
||||||
|
.markdown th:first-child {
|
||||||
|
border-top-left-radius: 0.375rem;
|
||||||
|
}
|
||||||
|
.markdown th:last-child {
|
||||||
|
border-right-width: 1px;
|
||||||
|
border-top-right-radius: 0.375rem;
|
||||||
|
}
|
||||||
|
.markdown td {
|
||||||
|
border-bottom-width: 1px;
|
||||||
|
border-left-width: 1px;
|
||||||
|
padding: 0.25rem 0.75rem;
|
||||||
|
}
|
||||||
|
.markdown td:last-child {
|
||||||
|
border-right-width: 1px;
|
||||||
|
}
|
||||||
|
.markdown tbody tr:last-child td:first-child {
|
||||||
|
border-bottom-left-radius: 0.375rem;
|
||||||
|
}
|
||||||
|
.markdown tbody tr:last-child td:last-child {
|
||||||
|
border-bottom-right-radius: 0.375rem;
|
||||||
|
}
|
||||||
|
|
||||||
img {
|
img {
|
||||||
@apply inline-block;
|
@apply inline-block;
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,3 +17,10 @@ export const limitMessageTokens = (
|
||||||
|
|
||||||
return limitedMessages;
|
return limitedMessages;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const countMessagesToken = (messages: MessageInterface[]) => {
|
||||||
|
return messages.reduce(
|
||||||
|
(tokenCount, message) => (tokenCount += countTokens(message.content)),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
Loading…
Reference in a new issue