Merge branch 'main' into dano/react-query-typescript

This commit is contained in:
Danny Avila 2023-04-07 10:19:13 -04:00 committed by GitHub
commit 5ea8f75f70
6 changed files with 131 additions and 110 deletions

View file

@ -1,5 +1,5 @@
<p align="center">
<a href="https://discord.gg/sDfH4MwDWJ">
<a href="https://discord.gg/NGaa9RPCft">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/110412045/228325485-9d3e618f-a980-44fe-89e9-d6d39164680e.png">
<img src="https://user-images.githubusercontent.com/110412045/228325485-9d3e618f-a980-44fe-89e9-d6d39164680e.png" height="128">
@ -9,7 +9,7 @@
</p>
<p align="center">
<a aria-label="Join the community on Discord" href="https://discord.gg/sDfH4MwDWJ">
<a aria-label="Join the community on Discord" href="https://discord.gg/NGaa9RPCft">
<img alt="" src="https://img.shields.io/badge/Join%20the%20community-blueviolet.svg?style=for-the-badge&logo=DISCORD&labelColor=000000&logoWidth=20">
</a>
<a aria-label="Sponsors" href="#sponsors">
@ -20,10 +20,7 @@
## All AI Conversations under One Roof. ##
Assistant AIs are the future and OpenAI revolutionized this movement with ChatGPT. While numerous UIs exist, this app commemorates the original styling of ChatGPT, with the ability to integrate any current/future AI models, while integrating and improving upon original client features, such as conversation/message search and prompt templates (currently WIP). Through this clone, you can avoid ChatGPT Plus in favor of free or pay-per-call APIs. I will soon deploy a demo of this app. Feel free to contribute, clone, or fork. Currently dockerized.
<div align="center">
<video src="https://user-images.githubusercontent.com/110412045/223754183-8b7f45ce-6517-4bd5-9b39-c624745bf399.mp4" width=400/>
</div>
![clone3](https://user-images.githubusercontent.com/110412045/230538752-9b99dc6e-cd02-483a-bff0-6c6e780fa7ae.gif)
### Features

View file

@ -35,21 +35,21 @@ router.post('/', async (req, res) => {
let endpointOption = {};
if (req.body?.jailbreak)
endpointOption = {
jailbreak: req.body?.jailbreak ?? false,
jailbreakConversationId: req.body?.jailbreakConversationId ?? null,
systemMessage: req.body?.systemMessage ?? null,
context: req.body?.context ?? null,
toneStyle: req.body?.toneStyle ?? 'fast'
jailbreak: req.body?.jailbreak || false,
jailbreakConversationId: req.body?.jailbreakConversationId || null,
systemMessage: req.body?.systemMessage || null,
context: req.body?.context || null,
toneStyle: req.body?.toneStyle || 'fast'
};
else
endpointOption = {
jailbreak: req.body?.jailbreak ?? false,
systemMessage: req.body?.systemMessage ?? null,
context: req.body?.context ?? null,
conversationSignature: req.body?.conversationSignature ?? null,
clientId: req.body?.clientId ?? null,
invocationId: req.body?.invocationId ?? null,
toneStyle: req.body?.toneStyle ?? 'fast'
jailbreak: req.body?.jailbreak || false,
systemMessage: req.body?.systemMessage || null,
context: req.body?.context || null,
conversationSignature: req.body?.conversationSignature || null,
clientId: req.body?.clientId || null,
invocationId: req.body?.invocationId || null,
toneStyle: req.body?.toneStyle || 'fast'
};
console.log('ask log', {
@ -122,23 +122,31 @@ const ask = async ({
console.log('BING RESPONSE', response);
const newConversationId = endpointOption?.jailbreak
? response.jailbreakConversationId
: response.conversationId || conversationId;
const newUserMassageId = response.parentMessageId || userMessageId;
const newResponseMessageId = response.parentMessageId || response.details.requestId || userMessageId;
// STEP1 generate response message
response.text = response.response || response.details.spokenText || '**Bing refused to answer.**';
let responseMessage = {
conversationId: newConversationId,
messageId: newResponseMessageId,
parentMessageId: overrideParentMessageId || newUserMassageId,
sender: endpointOption?.jailbreak ? 'Sydney' : 'BingAI',
text: await handleText(response, true),
suggestions: response.details.suggestedResponses && response.details.suggestedResponses.map(s => s.text)
suggestions:
response.details.suggestedResponses && response.details.suggestedResponses.map(s => s.text),
jailbreak: endpointOption?.jailbreak
};
// // response.text = await handleText(response, true);
// response.suggestions =
// response.details.suggestedResponses && response.details.suggestedResponses.map(s => s.text);
if (endpointOption?.jailbreak) {
responseMessage.conversationId = response.jailbreakConversationId;
responseMessage.messageId = response.messageId || response.details.messageId;
responseMessage.parentMessageId = overrideParentMessageId || response.parentMessageId || userMessageId;
responseMessage.sender = 'Sydney';
} else {
responseMessage.conversationId = response.conversationId;
responseMessage.messageId = response.messageId || response.details.messageId;
responseMessage.parentMessageId =
overrideParentMessageId || response.parentMessageId || response.details.requestId || userMessageId;
responseMessage.sender = 'BingAI';
}
await saveMessage(responseMessage);
@ -151,22 +159,14 @@ const ask = async ({
// Attition: the api will also create new conversationId while using invalid userMessage.parentMessageId,
// but in this situation, don't change the conversationId, but create new convo.
let conversationUpdate = { conversationId: newConversationId, endpoint: 'bingAI' };
if (conversationId != newConversationId)
if (isNewConversation) {
// change the conversationId to new one
let conversationUpdate = { conversationId, endpoint: 'bingAI' };
if (conversationId != responseMessage.conversationId && isNewConversation)
conversationUpdate = {
...conversationUpdate,
conversationId: conversationId,
newConversationId: newConversationId
newConversationId: responseMessage.conversationId || conversationId
};
} else {
// create new conversation
conversationUpdate = {
...conversationUpdate,
...endpointOption
};
}
conversationId = responseMessage.conversationId || conversationId;
if (endpointOption?.jailbreak) {
conversationUpdate.jailbreak = true;
@ -179,16 +179,17 @@ const ask = async ({
}
await saveConvo(req?.session?.user?.username, conversationUpdate);
conversationId = newConversationId;
// STEP3 update the user message
userMessage.conversationId = newConversationId;
userMessage.messageId = newUserMassageId;
userMessage.conversationId = conversationId;
userMessage.messageId = responseMessage.parentMessageId;
// If response has parentMessageId, the fake userMessage.messageId should be updated to the real one.
if (!overrideParentMessageId)
await saveMessage({ ...userMessage, messageId: userMessageId, newMessageId: newUserMassageId });
userMessageId = newUserMassageId;
if (!overrideParentMessageId) {
const oldUserMessageId = userMessageId;
await saveMessage({ ...userMessage, messageId: oldUserMessageId, newMessageId: userMessage.messageId });
}
userMessageId = userMessage.messageId;
sendMessage(res, {
title: await getConvoTitle(req?.session?.user?.username, conversationId),

View file

@ -67,7 +67,6 @@
"tailwindcss-animate": "^1.0.5",
"tailwindcss-radix": "^2.8.0",
"url": "^0.11.0",
"use-react-screenshot": "github:danny-avila/use-react-screenshot#master",
"uuidv4": "^6.2.13",
"@tanstack/react-query": "^4.28.0"
},

View file

@ -23,7 +23,6 @@ export default function ExportModel({ open, onOpenChange }) {
const [includeOptions, setIncludeOptions] = useState(true);
const [exportBranches, setExportBranches] = useState(false);
const [exportBranchesSupport, setExportBranchesSupport] = useState(false);
const [recursive, setRecursive] = useState(true);
const conversation = useRecoilValue(store.conversation) || {};
@ -37,30 +36,34 @@ export default function ExportModel({ open, onOpenChange }) {
[]
);
const typeOptions = ['text', 'markdown', 'csv', 'json', 'screenshot']; //,, 'webpage'];
const typeOptions = [
{ value: 'text', display: 'text (.txt)' },
{ value: 'markdown', display: 'markdown (.md)' },
{ value: 'csv', display: 'csv (.csv)' },
{ value: 'json', display: 'json (.json)' },
{ value: 'screenshot', display: 'screenshot (.png)' }
]; //,, 'webpage'];
useEffect(() => {
setFileName(
filenamify(String(conversation?.title || 'file'))
);
setFileName(filenamify(String(conversation?.title || 'file')));
setType('text');
setIncludeOptions(true);
setExportBranches(false);
setExportBranchesSupport(false);
setRecursive(true);
}, [open]);
const _setType = newType => {
if (newType === 'json' || newType === 'csv' || newType === 'webpage') {
setExportBranches(true);
setExportBranchesSupport(true);
} else {
setExportBranches(false);
setExportBranchesSupport(false);
}
const exportBranchesSupport = newType === 'json' || newType === 'csv' || newType === 'webpage';
const exportOptionsSupport = newType !== 'csv' && newType !== 'screenshot';
setExportBranches(exportBranchesSupport);
setIncludeOptions(exportOptionsSupport);
setType(newType);
};
const exportBranchesSupport = type === 'json' || type === 'csv' || type === 'webpage';
const exportOptionsSupport = type !== 'csv' && type !== 'screenshot';
// return an object or an array based on branches and recursive option
// messageId is used to get siblindIdx from recoil snapshot
const buildMessageTree = async ({ messageId, message, messages, branches = false, recursive = false }) => {
@ -320,7 +323,6 @@ export default function ExportModel({ open, onOpenChange }) {
</div>
</div>
<div className="grid w-full gap-6 sm:grid-cols-2">
{type !== 'csv' && type !== 'screenshot' ? (
<div className="col-span-1 flex flex-col items-start justify-start gap-2">
<div className="grid w-full items-center gap-2">
<Label
@ -332,6 +334,7 @@ export default function ExportModel({ open, onOpenChange }) {
<div className="flex h-[40px] w-full items-center space-x-3">
<Checkbox
id="includeOptions"
disabled={!exportOptionsSupport}
checked={includeOptions}
className="focus:ring-opacity-20 dark:border-gray-500 dark:bg-gray-700 dark:text-gray-50 dark:focus:ring-gray-600 dark:focus:ring-opacity-50 dark:focus:ring-offset-0"
onCheckedChange={setIncludeOptions}
@ -340,13 +343,11 @@ export default function ExportModel({ open, onOpenChange }) {
htmlFor="includeOptions"
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 dark:text-gray-50"
>
Enabled
{exportOptionsSupport ? 'Enabled' : 'Not Supported'}
</label>
</div>
</div>
</div>
) : null}
{type !== 'screenshot' ? (
<div className="grid w-full items-center gap-2">
<Label
htmlFor="exportBranches"
@ -370,7 +371,6 @@ export default function ExportModel({ open, onOpenChange }) {
</label>
</div>
</div>
) : null}
{type === 'json' ? (
<div className="grid w-full items-center gap-2">
<Label

View file

@ -4,6 +4,7 @@ import { Listbox } from '@headlessui/react';
import { cn } from '~/utils/';
function Dropdown({ value, onChange, options, className, containerClassName }) {
const currentOption = options.find(element => element === value || element?.value === value) ?? value;
return (
<div className={cn('flex items-center justify-center gap-2', containerClassName)}>
<div className="relative w-full">
@ -19,7 +20,7 @@ function Dropdown({ value, onChange, options, className, containerClassName }) {
>
<span className="inline-flex w-full truncate">
<span className="flex h-6 items-center gap-1 truncate text-sm text-black dark:text-white">
{value}
{currentOption?.display ?? value}
</span>
</span>
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
@ -43,19 +44,19 @@ function Dropdown({ value, onChange, options, className, containerClassName }) {
{options.map((item, i) => (
<Listbox.Option
key={i}
value={item}
value={item?.value ?? item}
className="group relative flex h-[42px] cursor-pointer select-none items-center overflow-hidden border-b border-black/10 pl-3 pr-9 text-gray-900 last:border-0 hover:bg-[#ECECF1] dark:border-white/20 dark:text-white dark:hover:bg-gray-700"
>
<span className="flex items-center gap-1.5 truncate">
<span
className={cn(
'flex h-6 items-center gap-1 text-gray-800 dark:text-gray-100',
value === item ? 'font-semibold' : ''
value === (item?.value ?? item) ? 'font-semibold' : ''
)}
>
{item}
{item?.display ?? item}
</span>
{value === item && (
{value === (item?.value ?? item) && (
<span className="absolute inset-y-0 right-0 flex items-center pr-3 text-gray-800 dark:text-gray-100">
<CheckMark />
</span>

View file

@ -1,14 +1,37 @@
import React, { createContext, useRef, useContext, useCallback } from 'react';
import { useScreenshot as useScreenshot_ } from 'use-react-screenshot';
import html2canvas from 'html2canvas';
const ScreenshotContext = createContext({});
export const useScreenshot = () => {
const { ref } = useContext(ScreenshotContext);
const [image, takeScreenshot] = useScreenshot_();
const takeScreenShot = node => {
if (!node) {
throw new Error('You should provide correct html node.');
}
return html2canvas(node).then(canvas => {
const croppedCanvas = document.createElement('canvas');
const croppedCanvasContext = croppedCanvas.getContext('2d');
// init data
const cropPositionTop = 0;
const cropPositionLeft = 0;
const cropWidth = canvas.width;
const cropHeight = canvas.height;
croppedCanvas.width = cropWidth;
croppedCanvas.height = cropHeight;
croppedCanvasContext.drawImage(canvas, cropPositionLeft, cropPositionTop);
const base64Image = croppedCanvas.toDataURL('image/png', 1);
return base64Image;
});
};
const captureScreenshot = () => {
return takeScreenshot(ref.current);
return takeScreenShot(ref.current);
};
return { screenshotTargetRef: ref, captureScreenshot };