mirror of
https://github.com/NovaOSS/nova-betterchat.git
synced 2024-11-25 17: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]
|
||||
ko_fi: freechatgpt
|
||||
ko_fi: betterchatgpt
|
||||
|
|
|
@ -99,10 +99,10 @@ Better ChatGPT 已经包含了大量的功能。您可以使用以下功能:
|
|||
如果您想支持我们的团队,请考虑通过以下方法之一赞助我们。每一份贡献,无论多小,都有助于我们维护和改善我们的服务。
|
||||
|
||||
| 付款方式 | 链接 |
|
||||
| -------------- | -------------------------------------------------------------------------------------- |
|
||||
| -------------- | ---------------------------------------------------------------------------------------- |
|
||||
| 支付宝 (Ayaka) | <img src="https://ayaka14732.github.io/sponsor/alipay.jpg" 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) |
|
||||
|
||||
感谢您成为我们社区的一员,我们期待着在未来为您提供更好的服务。
|
||||
|
||||
|
|
|
@ -106,8 +106,8 @@ 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.
|
||||
|
||||
| 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 /> |
|
||||
| Wechat (Ayaka) | <img src="https://ayaka14732.github.io/sponsor/wechat.png" width=150 /> |
|
||||
|
||||
|
|
|
@ -10,11 +10,11 @@
|
|||
<meta name="twitter:image" content="https://bettergpt.chat/preview.jpg" />
|
||||
<meta
|
||||
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
|
||||
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:card" content="summary_large_image" />
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"name": "chatgpt-free-app",
|
||||
"name": "better-chatgpt",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
|
|
|
@ -78,7 +78,7 @@ const AboutMenu = () => {
|
|||
<p>{t('support.paragraph3', { ns: 'about' })}</p>
|
||||
|
||||
<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
|
||||
src='/kofi.svg'
|
||||
alt='Support us through the Ko-fi platform.'
|
||||
|
|
|
@ -19,12 +19,14 @@ import TickIcon from '@icon/TickIcon';
|
|||
import CrossIcon from '@icon/CrossIcon';
|
||||
import RefreshIcon from '@icon/RefreshIcon';
|
||||
import DownChevronArrow from '@icon/DownChevronArrow';
|
||||
import CopyIcon from '@icon/CopyIcon';
|
||||
|
||||
import useSubmit from '@hooks/useSubmit';
|
||||
|
||||
import { ChatInterface } from '@type/chat';
|
||||
|
||||
import PopupModal from '@components/PopupModal';
|
||||
import TokenCount from '@components/TokenCount';
|
||||
import CommandPrompt from './CommandPrompt';
|
||||
import CodeBlock from './CodeBlock';
|
||||
import { codeLanguageSubset } from '@constants/chat';
|
||||
|
@ -126,6 +128,10 @@ const ContentView = React.memo(
|
|||
handleSubmit();
|
||||
};
|
||||
|
||||
const handleCopy = () => {
|
||||
navigator.clipboard.writeText(content);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className='markdown prose w-full break-words dark:prose-invert dark share-gpt-message'>
|
||||
|
@ -157,7 +163,9 @@ const ContentView = React.memo(
|
|||
<div className='flex justify-end gap-2 w-full mt-2'>
|
||||
{isDelete || (
|
||||
<>
|
||||
{role === 'assistant' && messageIndex === lastMessageIndex && (
|
||||
{!useStore.getState().generating &&
|
||||
role === 'assistant' &&
|
||||
messageIndex === lastMessageIndex && (
|
||||
<RefreshButton onClick={handleRefresh} />
|
||||
)}
|
||||
{messageIndex !== 0 && <UpButton onClick={handleMoveUp} />}
|
||||
|
@ -165,6 +173,7 @@ const ContentView = React.memo(
|
|||
<DownButton onClick={handleMoveDown} />
|
||||
)}
|
||||
|
||||
<CopyButton onClick={handleCopy} />
|
||||
<EditButton setIsEdit={setIsEdit} />
|
||||
<DeleteButton setIsDelete={setIsDelete} />
|
||||
</>
|
||||
|
@ -277,6 +286,7 @@ const UpButton = ({
|
|||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const RefreshButton = ({
|
||||
onClick,
|
||||
}: {
|
||||
|
@ -284,6 +294,28 @@ const RefreshButton = ({
|
|||
}) => {
|
||||
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 = ({
|
||||
content,
|
||||
setIsEdit,
|
||||
|
@ -481,6 +513,7 @@ const EditViewButtons = React.memo(
|
|||
</button>
|
||||
)}
|
||||
</div>
|
||||
{sticky && <TokenCount />}
|
||||
<CommandPrompt _setContent={_setContent} />
|
||||
</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%;
|
||||
}
|
||||
|
||||
.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 {
|
||||
@apply inline-block;
|
||||
}
|
||||
|
|
|
@ -17,3 +17,10 @@ export const limitMessageTokens = (
|
|||
|
||||
return limitedMessages;
|
||||
};
|
||||
|
||||
export const countMessagesToken = (messages: MessageInterface[]) => {
|
||||
return messages.reduce(
|
||||
(tokenCount, message) => (tokenCount += countTokens(message.content)),
|
||||
0
|
||||
);
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue