27 Commits

Author SHA1 Message Date
Heiko Joerg Schick 023a8a202b Fixed chunked encoding not sent in realtime with revers proxy software 2023-12-07 21:15:15 +01:00
Heiko Joerg Schick 82b77b0baa Updated Alpine version 2023-11-08 09:52:55 +01:00
Heiko Joerg Schick f65aca0221 Added GPT-4-Turbo 2023-11-08 09:50:56 +01:00
schihei fd8b183f51 Merge branch 'feature/export-chat-to-screenshot' into 'wip/h3132'
Added functionality to export chats to screenshot

See merge request schihei/chatbot-ui!4
2023-08-13 08:34:41 +00:00
Heiko Joerg Schick db4375ca8a Added functionality to export chats to screenshot 2023-08-13 10:33:24 +02:00
Heiko Joerg Schick accab8e0cd Merge branch 'wip/h3132' of ssh://gitlab.h3132.de:30002/schihei/chatbot-ui into wip/h3132 2023-08-12 23:26:12 +02:00
Heiko Joerg Schick 6c39256008 Minor bugfixes 2023-08-12 23:25:56 +02:00
schihei e48fda0a2a Merge branch 'feature/export-chat-to-markdown' into 'wip/h3132'
Added functionallity to export chat to markdown

See merge request schihei/chatbot-ui!3
2023-08-12 21:18:52 +00:00
Heiko Joerg Schick 1f94effb43 Added functionallity to export chat to markdown 2023-08-12 22:50:15 +02:00
schihei b14c052dcd Merge branch 'feature/GPT-3.5-turbo-16k-model' into 'wip/h3132'
Added GPT-3.5-turbo-16k model

See merge request schihei/chatbot-ui!1
2023-08-12 20:41:57 +00:00
schihei c067490a78 Merge branch 'feature/export-chat-to-pdf' into 'wip/h3132'
Added functionallity to export chat to PDF

See merge request schihei/chatbot-ui!2
2023-08-12 20:40:35 +00:00
Heiko Joerg Schick 6a49f664db Added functionallity to export chat to PDF 2023-08-12 22:26:24 +02:00
Heiko Joerg Schick 10733fab7d Added GPT-3.5-turbo-16k model 2023-08-12 22:13:34 +02:00
Anthony Puppo fa3f6e93bb Add blinking typing cursing (#619) 2023-04-20 08:33:28 -06:00
kyyhkynen f849a2e924 add Finnish translations (#618) 2023-04-20 08:28:29 -06:00
Mckay Wrigley 063ce50319 force update 2023-04-19 15:00:11 -06:00
Mckay Wrigley 718dc6432a FIX IMPORT 2023-04-19 14:51:35 -06:00
Mckay Wrigley ab9f108a57 fix prompt selection 2023-04-19 14:37:18 -06:00
Mckay Wrigley c7be45587f readme update 2023-04-19 08:45:50 -06:00
Anthony Puppo c959264f93 Performance (#602) 2023-04-19 08:39:19 -06:00
TKS 24068ea5aa Create Security.md (#607)
basic secuirty policy as the repo didn't have one yet, update as needed.
2023-04-19 08:35:18 -06:00
Shemar Lindie dcb2810d46 fix: optimize mobile layout (#606)
- conserve space in new convo view
- fix overlapping edit and delete chat message icons
- fix overlapping copy icon
2023-04-19 08:34:27 -06:00
ryanhex53 2208e4dfc7 more chinese translation. (#609) 2023-04-19 08:32:29 -06:00
Mckay Wrigley 3685378ab8 change empty msg 2023-04-19 08:31:12 -06:00
Mckay Wrigley 971cde5310 sort folders alphabetically 2023-04-18 16:05:44 -06:00
Mckay Wrigley 5725024d80 import/export add instead of replace (#601)
* change search language

* add to import/export

* fix log
2023-04-18 16:00:20 -06:00
borborborja b964188d0b add catalan language (#598) 2023-04-18 09:10:47 -06:00
30 changed files with 400 additions and 104 deletions
+2 -2
View File
@@ -1,5 +1,5 @@
# ---- Base Node ----
FROM node:19-alpine AS base
FROM node:20-alpine AS base
WORKDIR /app
COPY package*.json ./
@@ -13,7 +13,7 @@ COPY . .
RUN npm run build
# ---- Production ----
FROM node:19-alpine AS production
FROM node:20-alpine AS production
WORKDIR /app
COPY --from=dependencies /app/node_modules ./node_modules
COPY --from=build /app/.next ./.next
+4 -16
View File
@@ -1,6 +1,6 @@
# Chatbot UI
Chatbot UI is an advanced chatbot kit for OpenAI's chat models built on top of [Chatbot UI Lite](https://github.com/mckaywrigley/chatbot-ui-lite) using Next.js, TypeScript, and Tailwind CSS.
Chatbot UI is an open source chat UI for AI models.
See a [demo](https://twitter.com/mckaywrigley/status/1640380021423603713?s=46&t=AowqkodyK6B4JccSOxSPew).
@@ -14,17 +14,8 @@ Expect frequent improvements.
**Next up:**
- [ ] Import/Export prompts
- [ ] "Bots"
- [ ] Sharing
## Modifications
Modify the chat interface in `components/Chat`.
Modify the sidebar interface in `components/Sidebar`.
Modify the system prompt in `utils/server/index.ts`.
- [ ] "Bots"
## Deploy
@@ -34,10 +25,6 @@ Host your own live version of Chatbot UI with Vercel.
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fmckaywrigley%2Fchatbot-ui)
**Replit**
Fork Chatbot UI on Replit [here](https://replit.com/@MckayWrigley/chatbot-ui-pro?v=1).
**Docker**
Build locally:
@@ -108,10 +95,11 @@ When deploying the application, the following environment variables can be set:
| GOOGLE_CSE_ID | | See [Custom Search JSON API documentation][GCSE] |
If you do not provide an OpenAI API key with `OPENAI_API_KEY`, users will have to provide their own key.
If you don't have an OpenAI API key, you can get one [here](https://platform.openai.com/account/api-keys).
## Contact
If you have any questions, feel free to reach out to me on [Twitter](https://twitter.com/mckaywrigley).
If you have any questions, feel free to reach out to Mckay on [Twitter](https://twitter.com/mckaywrigley).
[GCSE]: https://developers.google.com/custom-search/v1/overview
+53
View File
@@ -0,0 +1,53 @@
# Security Policy
This security policy outlines the process for reporting vulnerabilities and secrets found within this GitHub repository. It is essential that all contributors and users adhere to this policy in order to maintain a secure and stable environment.
## Reporting a Vulnerability
If you discover a vulnerability within the code, dependencies, or any other component of this repository, please follow these steps:
1. **Do not disclose the vulnerability publicly.** Publicly disclosing a vulnerability may put the project at risk and could potentially harm other users.
2. **Contact the repository maintainer(s) privately.** Send a private message or email to the maintainer(s) with a detailed description of the vulnerability. Include the following information:
- The affected component(s)
- Steps to reproduce the issue
- Potential impact of the vulnerability
- Any possible mitigations or workarounds
3. **Wait for a response from the maintainer(s).** Please be patient, as they may need time to investigate and verify the issue. The maintainer(s) should acknowledge receipt of your report and provide an estimated time frame for addressing the vulnerability.
4. **Cooperate with the maintainer(s).** If requested, provide additional information or assistance to help resolve the issue.
5. **Do not disclose the vulnerability until the maintainer(s) have addressed it.** Once the issue has been resolved, the maintainer(s) may choose to publicly disclose the vulnerability and credit you for the discovery.
## Reporting Secrets
If you discover any secrets, such as API keys or passwords, within the repository, follow these steps:
1. **Do not share the secret or use it for unauthorized purposes.** Misusing a secret could have severe consequences for the project and its users.
2. **Contact the repository maintainer(s) privately.** Notify them of the discovered secret, its location, and any potential risks associated with it.
3. **Wait for a response and further instructions.**
## Responsible Disclosure
We encourage responsible disclosure of vulnerabilities and secrets. If you follow the steps outlined in this policy, we will work with you to understand and address the issue. We will not take legal action against individuals who discover and report vulnerabilities or secrets in accordance with this policy.
## Patching and Updates
We are committed to maintaining the security of our project. When vulnerabilities are reported and confirmed, we will:
1. Work diligently to develop and apply a patch or implement a mitigation strategy.
2. Keep the reporter informed about the progress of the fix.
3. Update the repository with the necessary patches and document the changes in the release notes or changelog.
4. Credit the reporter for the discovery, if they wish to be acknowledged.
## Contributing to Security
We welcome contributions that help improve the security of our project. If you have suggestions or want to contribute code to address security issues, please follow the standard contribution guidelines for this repository. When submitting a pull request related to security, please mention that it addresses a security issue and provide any necessary context.
By adhering to this security policy, you contribute to the overall security and stability of the project. Thank you for your cooperation and responsible handling of vulnerabilities and secrets.
+107 -5
View File
@@ -1,4 +1,10 @@
import { IconClearAll, IconSettings } from '@tabler/icons-react';
import {
IconClearAll,
IconSettings,
IconMarkdown,
IconPdf,
IconScreenshot,
} from '@tabler/icons-react';
import {
MutableRefObject,
memo,
@@ -28,11 +34,16 @@ import HomeContext from '@/pages/api/home/home.context';
import Spinner from '../Spinner';
import { ChatInput } from './ChatInput';
import { ChatLoader } from './ChatLoader';
import { ChatMessage } from './ChatMessage';
import { ErrorMessageDiv } from './ErrorMessageDiv';
import { ModelSelect } from './ModelSelect';
import { SystemPrompt } from './SystemPrompt';
import { TemperatureSlider } from './Temperature';
import { MemoizedChatMessage } from './MemoizedChatMessage';
import {jsPDF} from "jspdf";
import html2canvas from "html2canvas";
import { toPng } from 'html-to-image';
interface Props {
stopConversationRef: MutableRefObject<boolean>;
@@ -300,6 +311,79 @@ export const Chat = memo(({ stopConversationRef }: Props) => {
}
};
const onMarkdown = () => {
if (!selectedConversation){
return '';
}
let markdownContent = '';
selectedConversation.messages.forEach(obj => {
markdownContent += `## ${obj.role === "user" ? t('You') : t("AI")}\n\n${obj.content}\n\n`;
});
const date = new Date().toLocaleString("default", { year: "numeric", month: "long", day: "numeric" })
const time = new Date().toLocaleTimeString("default", {hour12: true, hour: "numeric", minute: "numeric"})
markdownContent += `---\n`
markdownContent += `${t("Exported on")} ` + date + ` ${t("at")} ` + time + ".";
const markdownFile = new Blob([markdownContent], { type: 'text/markdown' });
const downloadLink = document.createElement('a');
downloadLink.href = URL.createObjectURL(markdownFile);
downloadLink.download = `${selectedConversation?.name || 'conversation'}.md`;
downloadLink.click();
}
const onPdf = () => {
if (chatContainerRef.current === null) {
return;
}
else {
chatContainerRef.current.classList.remove('max-h-full')
html2canvas(chatContainerRef.current).then((canvas) => {
if (chatContainerRef.current) {
chatContainerRef.current.classList.add('max-h-full')
}
const imgData = canvas.toDataURL('image/png');
const orientation = canvas.width > canvas.height ? "l" : "p";
const pixelRatio = window.devicePixelRatio > 2 ? window.devicePixelRatio : 2
const pdf = new jsPDF(
orientation,
"pt",
[canvas.width / pixelRatio, canvas.height / pixelRatio],
true,
);
var pdfWidth = pdf.internal.pageSize.getWidth();
var pdfHeight = pdf.internal.pageSize.getHeight();
pdf.addImage(imgData, "PNG", 0, 0, pdfWidth, pdfHeight, "", "FAST");
const title = `${selectedConversation?.name || 'conversation'}.pdf`
pdf.save(title)
})
}
};
const onScreenshot = () => {
if (chatContainerRef.current === null) {
return;
}
chatContainerRef.current.classList.remove('max-h-full');
toPng(chatContainerRef.current, { cacheBust: true })
.then((dataUrl) => {
const link = document.createElement('a');
link.download = `${selectedConversation?.name || 'conversation'}.png`;
link.href = dataUrl;
link.click();
if (chatContainerRef.current) {
chatContainerRef.current.classList.add('max-h-full');
}
})
.catch((err) => {
console.log(err);
});
};
const scrollDown = () => {
if (autoScrollEnabled) {
messagesEndRef.current?.scrollIntoView(true);
@@ -398,7 +482,7 @@ export const Chat = memo(({ stopConversationRef }: Props) => {
>
{selectedConversation?.messages.length === 0 ? (
<>
<div className="mx-auto flex w-[350px] flex-col space-y-10 pt-12 sm:w-[600px]">
<div className="mx-auto flex flex-col space-y-5 md:space-y-10 px-3 pt-5 md:pt-12 sm:max-w-[600px]">
<div className="text-center text-3xl font-semibold text-gray-800 dark:text-gray-100">
{models.length === 0 ? (
<div>
@@ -425,7 +509,7 @@ export const Chat = memo(({ stopConversationRef }: Props) => {
/>
<TemperatureSlider
label="Temperature"
label={t('Temperature')}
onChangeTemperature={(temperature) =>
handleUpdateConversation(selectedConversation, {
key: 'temperature',
@@ -454,6 +538,24 @@ export const Chat = memo(({ stopConversationRef }: Props) => {
>
<IconClearAll size={18} />
</button>
<button
className="ml-2 cursor-pointer hover:opacity-50"
onClick={onMarkdown}
>
<IconMarkdown size={18} />
</button>
<button
className="ml-2 cursor-pointer hover:opacity-50"
onClick={onPdf}
>
<IconPdf size={18} />
</button>
<button
className="ml-2 cursor-pointer hover:opacity-50"
onClick={onScreenshot}
>
<IconScreenshot size={18} />
</button>
</div>
{showSettings && (
<div className="flex flex-col space-y-10 md:mx-auto md:max-w-xl md:gap-6 md:py-3 md:pt-6 lg:max-w-2xl lg:px-0 xl:max-w-3xl">
@@ -464,7 +566,7 @@ export const Chat = memo(({ stopConversationRef }: Props) => {
)}
{selectedConversation?.messages.map((message, index) => (
<ChatMessage
<MemoizedChatMessage
key={index}
message={message}
messageIndex={index}
+2 -2
View File
@@ -43,7 +43,7 @@ export const ChatInput = ({
onScrollDownClick,
stopConversationRef,
textareaRef,
showScrollDownButton
showScrollDownButton,
}: Props) => {
const { t } = useTranslation('chat');
@@ -371,7 +371,7 @@ export const ChatInput = ({
{isModalVisible && (
<VariableModal
prompt={prompts[activePromptIndex]}
prompt={filteredPrompts[activePromptIndex]}
variables={variables}
onSubmit={handleSubmit}
onClose={() => setIsModalVisible(false)}
+1 -2
View File
@@ -1,5 +1,4 @@
import { IconRobot } from '@tabler/icons-react';
import { IconDots } from '@tabler/icons-react';
import { FC } from 'react';
interface Props { }
@@ -14,7 +13,7 @@ export const ChatLoader: FC<Props> = () => {
<div className="min-w-[40px] items-end">
<IconRobot size={30} />
</div>
<IconDots className="animate-pulse" />
<span className="animate-pulse cursor-default mt-1"></span>
</div>
</div>
);
+40 -46
View File
@@ -23,7 +23,7 @@ import rehypeMathjax from 'rehype-mathjax';
import remarkGfm from 'remark-gfm';
import remarkMath from 'remark-math';
interface Props {
export interface Props {
message: Message;
messageIndex: number;
onEdit?: (editedMessage: Message) => void
@@ -33,7 +33,7 @@ export const ChatMessage: FC<Props> = memo(({ message, messageIndex, onEdit }) =
const { t } = useTranslation('chat');
const {
state: { selectedConversation, conversations, currentMessage },
state: { selectedConversation, conversations, currentMessage, messageIsStreaming },
dispatch: homeDispatch,
} = useContext(HomeContext);
@@ -126,14 +126,14 @@ export const ChatMessage: FC<Props> = memo(({ message, messageIndex, onEdit }) =
return (
<div
className={`group px-4 ${
className={`group md:px-4 ${
message.role === 'assistant'
? 'border-b border-black/10 bg-gray-50 text-gray-800 dark:border-gray-900/50 dark:bg-[#444654] dark:text-gray-100'
: 'border-b border-black/10 bg-white text-gray-800 dark:border-gray-900/50 dark:bg-[#343541] dark:text-gray-100'
}`}
style={{ overflowWrap: 'anywhere' }}
>
<div className="relative m-auto flex gap-4 p-4 text-base md:max-w-2xl md:gap-6 md:py-6 lg:max-w-2xl lg:px-0 xl:max-w-3xl">
<div className="relative m-auto flex p-4 text-base md:max-w-2xl md:gap-6 md:py-6 lg:max-w-2xl lg:px-0 xl:max-w-3xl">
<div className="min-w-[40px] text-right font-bold">
{message.role === 'assistant' ? (
<IconRobot size={30} />
@@ -185,68 +185,44 @@ export const ChatMessage: FC<Props> = memo(({ message, messageIndex, onEdit }) =
</div>
</div>
) : (
<div className="prose whitespace-pre-wrap dark:prose-invert">
<div className="prose whitespace-pre-wrap dark:prose-invert flex-1">
{message.content}
</div>
)}
{(window.innerWidth < 640 || !isEditing) && (
<>
{!isEditing && (
<div className="md:-mr-8 ml-1 md:ml-0 flex flex-col md:flex-row gap-4 md:gap-1 items-center md:items-start justify-end md:justify-start">
<button
className={`absolute translate-x-[1000px] text-gray-500 hover:text-gray-700 focus:translate-x-0 group-hover:translate-x-0 dark:text-gray-400 dark:hover:text-gray-300 ${
window.innerWidth < 640
? 'bottom-1 right-3'
: 'right-6 top-[26px]'
}
`}
className="invisible group-hover:visible focus:visible text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300"
onClick={toggleEditing}
>
<IconEdit size={20} />
</button>
<button
className={`absolute translate-x-[1000px] text-gray-500 hover:text-gray-700 focus:translate-x-0 group-hover:translate-x-0 dark:text-gray-400 dark:hover:text-gray-300 ${
window.innerWidth < 640
? 'bottom-1 right-3'
: 'right-0 top-[26px]'
}
`}
className="invisible group-hover:visible focus:visible text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300"
onClick={handleDeleteMessage}
>
<IconTrash size={20} />
</button>
</>
</div>
)}
</div>
) : (
<>
<div
className={`absolute ${
window.innerWidth < 640
? 'bottom-1 right-3'
: 'right-0 top-[26px] m-0'
}`}
>
{messagedCopied ? (
<IconCheck
size={20}
className="text-green-500 dark:text-green-400"
/>
) : (
<button
className="translate-x-[1000px] text-gray-500 hover:text-gray-700 focus:translate-x-0 group-hover:translate-x-0 dark:text-gray-400 dark:hover:text-gray-300"
onClick={copyOnClick}
>
<IconCopy size={20} />
</button>
)}
</div>
<div className="flex flex-row">
<MemoizedReactMarkdown
className="prose dark:prose-invert"
className="prose dark:prose-invert flex-1"
remarkPlugins={[remarkGfm, remarkMath]}
rehypePlugins={[rehypeMathjax]}
components={{
code({ node, inline, className, children, ...props }) {
if (children.length) {
if (children[0] == '▍') {
return <span className="animate-pulse cursor-default mt-1"></span>
}
children[0] = (children[0] as string).replace("`▍`", "▍")
}
const match = /language-(\w+)/.exec(className || '');
return !inline ? (
@@ -285,9 +261,27 @@ export const ChatMessage: FC<Props> = memo(({ message, messageIndex, onEdit }) =
},
}}
>
{message.content}
{`${message.content}${
messageIsStreaming && messageIndex == (selectedConversation?.messages.length ?? 0) - 1 ? '`▍`' : ''
}`}
</MemoizedReactMarkdown>
</>
<div className="md:-mr-8 ml-1 md:ml-0 flex flex-col md:flex-row gap-4 md:gap-1 items-center md:items-start justify-end md:justify-start">
{messagedCopied ? (
<IconCheck
size={20}
className="text-green-500 dark:text-green-400"
/>
) : (
<button
className="invisible group-hover:visible focus:visible text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300"
onClick={copyOnClick}
>
<IconCopy size={20} />
</button>
)}
</div>
</div>
)}
</div>
</div>
+9
View File
@@ -0,0 +1,9 @@
import { FC, memo } from "react";
import { ChatMessage, Props } from "./ChatMessage";
export const MemoizedChatMessage: FC<Props> = memo(
ChatMessage,
(prevProps, nextProps) => (
prevProps.message.content === nextProps.message.content
)
);
+3 -3
View File
@@ -52,13 +52,13 @@ export const TemperatureSlider: FC<Props> = ({
onChange={handleChange}
/>
<ul className="w mt-2 pb-8 flex justify-between px-[24px] text-neutral-900 dark:text-neutral-100">
<li className="relative flex justify-center">
<li className="flex justify-center">
<span className="absolute">{t('Precise')}</span>
</li>
<li className="relative flex justify-center">
<li className="flex justify-center">
<span className="absolute">{t('Neutral')}</span>
</li>
<li className="relative flex justify-center">
<li className="flex justify-center">
<span className="absolute">{t('Creative')}</span>
</li>
</ul>
+4 -2
View File
@@ -107,6 +107,8 @@ export const Chatbar = () => {
});
homeDispatch({ field: 'folders', value: folders });
homeDispatch({ field: 'prompts', value: prompts });
window.location.reload();
};
const handleClearConversations = () => {
@@ -115,7 +117,7 @@ export const Chatbar = () => {
field: 'selectedConversation',
value: {
id: uuidv4(),
name: 'New conversation',
name: t('New Conversation'),
messages: [],
model: OpenAIModels[defaultModelId],
prompt: DEFAULT_SYSTEM_PROMPT,
@@ -157,7 +159,7 @@ export const Chatbar = () => {
field: 'selectedConversation',
value: {
id: uuidv4(),
name: 'New conversation',
name: t('New Conversation'),
messages: [],
model: OpenAIModels[defaultModelId],
prompt: DEFAULT_SYSTEM_PROMPT,
@@ -49,6 +49,7 @@ export const ChatFolders = ({ searchTerm }: Props) => {
<div className="flex w-full flex-col pt-2">
{folders
.filter((folder) => folder.type === 'chat')
.sort((a, b) => a.name.localeCompare(b.name))
.map((folder, index) => (
<Folder
key={index}
@@ -1,9 +1,4 @@
import {
IconFileExport,
IconMoon,
IconSettings,
IconSun,
} from '@tabler/icons-react';
import { IconFileExport, IconSettings } from '@tabler/icons-react';
import { useContext, useState } from 'react';
import { useTranslation } from 'next-i18next';
@@ -38,7 +33,6 @@ export const ChatbarSettings = () => {
handleClearConversations,
handleImportConversations,
handleExportData,
handleApiKeyChange,
} = useContext(ChatbarContext);
@@ -1,4 +1,9 @@
import { FC, memo } from 'react';
import ReactMarkdown, { Options } from 'react-markdown';
export const MemoizedReactMarkdown: FC<Options> = memo(ReactMarkdown);
export const MemoizedReactMarkdown: FC<Options> = memo(
ReactMarkdown,
(prevProps, nextProps) => (
prevProps.children === nextProps.children
)
);
@@ -49,6 +49,7 @@ export const PromptFolders = () => {
<div className="flex w-full flex-col pt-2">
{folders
.filter((folder) => folder.type === 'prompt')
.sort((a, b) => a.name.localeCompare(b.name))
.map((folder, index) => (
<Folder
key={index}
+2 -2
View File
@@ -79,7 +79,7 @@ const Sidebar = <T,>({
</button>
</div>
<Search
placeholder={t('Search prompts...') || ''}
placeholder={t('Search...') || ''}
searchTerm={searchTerm}
onSearch={handleSearchTerm}
/>
@@ -105,7 +105,7 @@ const Sidebar = <T,>({
<div className="mt-8 select-none text-center text-white opacity-50">
<IconMistOff className="mx-auto mb-3" />
<span className="text-[14px] leading-normal">
{t('No prompts.')}
{t('No data.')}
</span>
</div>
)}
+2
View File
@@ -22,6 +22,8 @@ module.exports = {
"zh",
"ar",
"tr",
"ca",
"fi",
],
},
localePath:
+3
View File
@@ -15,7 +15,10 @@
"@dqbd/tiktoken": "^1.0.2",
"@tabler/icons-react": "^2.9.0",
"eventsource-parser": "^0.1.0",
"html2canvas": "^1.4.1",
"html-to-image": "^1.11.11",
"i18next": "^22.4.13",
"jspdf": "^2.5.1",
"next": "13.2.4",
"next-i18next": "^13.2.2",
"openai": "^3.2.1",
+7 -1
View File
@@ -54,7 +54,13 @@ const handler = async (req: Request): Promise<Response> => {
const stream = await OpenAIStream(model, promptToSend, temperatureToUse, key, messagesToSend);
return new Response(stream);
var resp = new Response(stream);
// let proxy services like nginx or argo tunnel know about pass the chunk immediately
// similar to nginx option `proxy_buffering off;`
resp.headers.set('Content-Type', 'text/event-stream');
return resp;
} catch (error) {
console.error(error);
if (error instanceof OpenAIError) {
+2 -2
View File
@@ -183,7 +183,7 @@ const Home = ({
const newConversation: Conversation = {
id: uuidv4(),
name: `${t('New Conversation')}`,
name: t('New Conversation'),
messages: [],
model: lastConversation?.model || {
id: OpenAIModels[defaultModelId].id,
@@ -331,7 +331,7 @@ const Home = ({
field: 'selectedConversation',
value: {
id: uuidv4(),
name: 'New conversation',
name: t('New Conversation'),
messages: [],
model: OpenAIModels[defaultModelId],
prompt: DEFAULT_SYSTEM_PROMPT,
+35
View File
@@ -0,0 +1,35 @@
{
"OpenAI API Key Required": "OpenAI API-avain tarvitaan",
"Please set your OpenAI API key in the bottom left of the sidebar.": "Ole hyvä ja aseta OpenAI API-avaimesi sivupalkin vasemmassa alareunassa.",
"Stop Generating": "Lopeta generointi",
"Prompt limit is {{maxLength}} characters": "Promptin maksimipituus on {{maxLength}} merkkiä",
"System Prompt": "System prompt",
"You are ChatGPT, a large language model trained by OpenAI. Follow the user's instructions carefully. Respond using markdown.": "Sinä olet ChatGPT, OpenAI:n opettama laaja kielimalli. Seuraa käyttäjän ohjeita tarkasti. Vastaa käyttäen markdownia.",
"Enter a prompt": "Syötä prompti",
"Regenerate response": "Luo vastaus uudelleen",
"Sorry, there was an error.": "Valitettavasti tapahtui virhe.",
"Model": "Malli",
"Conversation": "Keskustelu",
"OR": "TAI",
"Loading...": "Ladataan...",
"Type a message...": "Kirjoita viesti...",
"Type a message or type \"/\" to select a prompt...": "Kirjoita viesti tai \"/\" valitaksesi promptin...",
"Error fetching models.": "Virhe malleja haettaessa.",
"AI": "AI",
"You": "Sinä",
"Cancel": "Peruuta",
"Save & Submit": "Tallenna & Lähetä",
"Make sure your OpenAI API key is set in the bottom left of the sidebar.": "Varmista että OpenAI API-avaimesi on asetettu sivupalkin vasemmassa alareunassa.",
"If you completed this step, OpenAI may be experiencing issues.": "Jos olet suorittanut tämän kohdan, OpenAI:lla saattaa olla tällä hetkellä ongelmia.",
"click if using a .env.local file": "klikkaa jos käytät .env.local-tiedostoa",
"Message limit is {{maxLength}} characters. You have entered {{valueLength}} characters.": "Viestin maksimipituus on {{maxLength}} merkkiä. Olet käyttänyt {{valueLength}} merkkiä.",
"Please enter a message": "Ole hyvä ja syötä viesti",
"Chatbot UI is an advanced chatbot kit for OpenAI's chat models aiming to mimic ChatGPT's interface and functionality.": "Chatbot UI on OpenAI:n keskustelumalleille tarkoitettu käyttöliittymä joka pyrkii jäljittelemään ChatGPT:n käyttöliittymää ja toiminnallisuuksia.",
"Are you sure you want to clear all messages?": "Haluatko varmasti poistaa kaikki viestit?",
"Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic.": "Suuremmat arvot kuten 0.8 tekevät vastauksista satunnaisempia, kun taas pienemmät arvot kuten 0.2 tekevät niistä keskittyneempiä ja johdonmukaisempia.",
"Precise": "Tarkka",
"Neutral": "Neutraali",
"Creative": "Luova",
"View Account Usage": "Näytä tilin käyttötilastot",
"Search...": "Hae..."
}
+1
View File
@@ -0,0 +1 @@
{}
+5
View File
@@ -0,0 +1,5 @@
{
"Copy code": "Kopioi koodi",
"Copied!": "Kopioitu!",
"Enter file name": "Syötä tiedostonimi"
}
+12
View File
@@ -0,0 +1,12 @@
{
"New prompt": "Uusi prompt",
"New folder": "Uusi kansio",
"No prompts.": "Ei prompteja.",
"Search prompts...": "Hae prompteista...",
"Name": "Nimi",
"Description": "Kuvaus",
"A description for your prompt.": "Promptisi kuvaus.",
"Prompt": "Prompt",
"Prompt content. Use {{}} to denote a variable. Ex: {{name}} is a {{adjective}} {{noun}}": "Promptin sisältö. Käytä {{}} muuttujien merkitsemiseen. Esim: {{nimi}} on {{adjektiivi}} {{substantiivi}}",
"Save": "Tallenna"
}
+7
View File
@@ -0,0 +1,7 @@
{
"Settings": "Asetukset",
"Theme": "Teema",
"Dark mode": "Tumma teema",
"Light mode": "Vaalea teema",
"Save": "Tallenna"
}
+13
View File
@@ -0,0 +1,13 @@
{
"New folder": "Uusi kansio",
"New chat": "Uusi keskustelu",
"No conversations.": "Ei keskusteluja.",
"Search conversations...": "Hae keskusteluista...",
"OpenAI API Key": "OpenAI API-avain",
"Import data": "Tuo keskusteluita",
"Are you sure?": "Oletko varma?",
"Clear conversations": "Tyhjennä keskustelut",
"Export data": "Vie keskustelut",
"Settings": "Asetukset",
"Plugin Keys": "Plugin-avaimet"
}
+7 -1
View File
@@ -4,6 +4,7 @@
"If you don't have an OpenAI API key, you can get one here: ": "如果你没有 OpenAI API 密钥,你可以在此获取:",
"Stop Generating": "停止生成",
"Prompt limit is {{maxLength}} characters": "提示字数限制为 {{maxLength}} 个字符",
"New Conversation": "新的聊天",
"System Prompt": "系统提示",
"You are ChatGPT, a large language model trained by OpenAI. Follow the user's instructions carefully. Respond using markdown.": "你是 ChatGPT,一个由 OpenAI 训练的大型语言模型。请仔细遵循用户的指示。使用 Markdown 格式进行回应。",
"Enter a prompt": "输入一个提示",
@@ -26,5 +27,10 @@
"Please enter a message": "请输入一条消息",
"Chatbot UI is an advanced chatbot kit for OpenAI's chat models aiming to mimic ChatGPT's interface and functionality.": "Chatbot UI 是一个高级聊天机器人工具包,旨在模仿 OpenAI 聊天模型的 ChatGPT 界面和功能。",
"Are you sure you want to clear all messages?": "你确定要清除所有的消息吗?",
"Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic.": "较高的数值(例如0.8)会使输出更随机,而较低的数值(例如0.2)会使输出更加聚焦和确定性更强。"
"Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic.": "较高的数值(例如0.8)会使输出更随机,而较低的数值(例如0.2)会使输出更加聚焦和确定性更强。",
"View Account Usage": "查阅账户用量",
"Temperature": "生成温度",
"Precise": "保守",
"Neutral": "中立",
"Creative": "随性"
}
+4 -1
View File
@@ -1,4 +1,7 @@
{
"Settings": "设置",
"Theme": "主题",
"Dark mode": "深色模式",
"Light mode": "浅色模式"
"Light mode": "浅色模式",
"Save": "保存"
}
+4 -1
View File
@@ -1,11 +1,14 @@
{
"New folder": "新建文件夹",
"New chat": "新建聊天",
"New Conversation": "新的聊天",
"No conversations.": "无对话",
"Search conversations...": "搜索对话...",
"OpenAI API Key": "OpenAI API 密钥",
"Import data": "导入对话",
"Are you sure?": "确定吗?",
"Clear conversations": "清空对话",
"Export data": "导出对话"
"Settings": "设置",
"Export data": "导出对话",
"Plugin Keys": "插件密钥"
}
+14
View File
@@ -10,8 +10,10 @@ export interface OpenAIModel {
export enum OpenAIModelID {
GPT_3_5 = 'gpt-3.5-turbo',
GPT_3_5_AZ = 'gpt-35-turbo',
GPT_3_5_16K = 'gpt-3.5-turbo-16k',
GPT_4 = 'gpt-4',
GPT_4_32K = 'gpt-4-32k',
GPT_4_TURBO = 'gpt-4-1106-preview'
}
// in case the `DEFAULT_MODEL` environment variable is not set or set to an unsupported model
@@ -30,6 +32,12 @@ export const OpenAIModels: Record<OpenAIModelID, OpenAIModel> = {
maxLength: 12000,
tokenLimit: 4000,
},
[OpenAIModelID.GPT_3_5_16K]: {
id: OpenAIModelID.GPT_3_5_16K,
name: 'GPT-3.5-16K',
maxLength: 96000,
tokenLimit: 32000,
},
[OpenAIModelID.GPT_4]: {
id: OpenAIModelID.GPT_4,
name: 'GPT-4',
@@ -42,4 +50,10 @@ export const OpenAIModels: Record<OpenAIModelID, OpenAIModel> = {
maxLength: 96000,
tokenLimit: 32000,
},
[OpenAIModelID.GPT_4_TURBO]: {
id: OpenAIModelID.GPT_4_TURBO,
name: 'GPT-4-TURBO',
maxLength: 380000,
tokenLimit: 128000,
},
};
+48 -10
View File
@@ -1,3 +1,4 @@
import { Conversation } from '@/types/chat';
import {
ExportFormatV1,
ExportFormatV2,
@@ -6,6 +7,8 @@ import {
LatestExportFormat,
SupportedExportFormats,
} from '@/types/export';
import { FolderInterface } from '@/types/folder';
import { Prompt } from '@/types/prompt';
import { cleanConversationHistory } from './clean';
@@ -109,18 +112,53 @@ export const exportData = () => {
export const importData = (
data: SupportedExportFormats,
): LatestExportFormat => {
const cleanedData = cleanData(data);
const { history, folders, prompts } = cleanedData;
const { history, folders, prompts } = cleanData(data);
const conversations = history;
localStorage.setItem('conversationHistory', JSON.stringify(conversations));
localStorage.setItem(
'selectedConversation',
JSON.stringify(conversations[conversations.length - 1]),
const oldConversations = localStorage.getItem('conversationHistory');
const oldConversationsParsed = oldConversations
? JSON.parse(oldConversations)
: [];
const newHistory: Conversation[] = [
...oldConversationsParsed,
...history,
].filter(
(conversation, index, self) =>
index === self.findIndex((c) => c.id === conversation.id),
);
localStorage.setItem('conversationHistory', JSON.stringify(newHistory));
if (newHistory.length > 0) {
localStorage.setItem(
'selectedConversation',
JSON.stringify(newHistory[newHistory.length - 1]),
);
} else {
localStorage.removeItem('selectedConversation');
}
localStorage.setItem('folders', JSON.stringify(folders));
localStorage.setItem('prompts', JSON.stringify(prompts));
const oldFolders = localStorage.getItem('folders');
const oldFoldersParsed = oldFolders ? JSON.parse(oldFolders) : [];
const newFolders: FolderInterface[] = [
...oldFoldersParsed,
...folders,
].filter(
(folder, index, self) =>
index === self.findIndex((f) => f.id === folder.id),
);
localStorage.setItem('folders', JSON.stringify(newFolders));
return cleanedData;
const oldPrompts = localStorage.getItem('prompts');
const oldPromptsParsed = oldPrompts ? JSON.parse(oldPrompts) : [];
const newPrompts: Prompt[] = [...oldPromptsParsed, ...prompts].filter(
(prompt, index, self) =>
index === self.findIndex((p) => p.id === prompt.id),
);
localStorage.setItem('prompts', JSON.stringify(newPrompts));
return {
version: 4,
history: newHistory,
folders: newFolders,
prompts: newPrompts,
};
};