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"> <p align="center">
<a href="https://discord.gg/sDfH4MwDWJ"> <a href="https://discord.gg/NGaa9RPCft">
<picture> <picture>
<source media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/110412045/228325485-9d3e618f-a980-44fe-89e9-d6d39164680e.png"> <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"> <img src="https://user-images.githubusercontent.com/110412045/228325485-9d3e618f-a980-44fe-89e9-d6d39164680e.png" height="128">
@ -9,7 +9,7 @@
</p> </p>
<p align="center"> <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"> <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>
<a aria-label="Sponsors" href="#sponsors"> <a aria-label="Sponsors" href="#sponsors">
@ -20,10 +20,7 @@
## All AI Conversations under One Roof. ## ## 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. 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"> ![clone3](https://user-images.githubusercontent.com/110412045/230538752-9b99dc6e-cd02-483a-bff0-6c6e780fa7ae.gif)
<video src="https://user-images.githubusercontent.com/110412045/223754183-8b7f45ce-6517-4bd5-9b39-c624745bf399.mp4" width=400/>
</div>
### Features ### Features

View file

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

View file

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

View file

@ -23,7 +23,6 @@ export default function ExportModel({ open, onOpenChange }) {
const [includeOptions, setIncludeOptions] = useState(true); const [includeOptions, setIncludeOptions] = useState(true);
const [exportBranches, setExportBranches] = useState(false); const [exportBranches, setExportBranches] = useState(false);
const [exportBranchesSupport, setExportBranchesSupport] = useState(false);
const [recursive, setRecursive] = useState(true); const [recursive, setRecursive] = useState(true);
const conversation = useRecoilValue(store.conversation) || {}; 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(() => { useEffect(() => {
setFileName( setFileName(filenamify(String(conversation?.title || 'file')));
filenamify(String(conversation?.title || 'file'))
);
setType('text'); setType('text');
setIncludeOptions(true); setIncludeOptions(true);
setExportBranches(false); setExportBranches(false);
setExportBranchesSupport(false);
setRecursive(true); setRecursive(true);
}, [open]); }, [open]);
const _setType = newType => { const _setType = newType => {
if (newType === 'json' || newType === 'csv' || newType === 'webpage') { const exportBranchesSupport = newType === 'json' || newType === 'csv' || newType === 'webpage';
setExportBranches(true); const exportOptionsSupport = newType !== 'csv' && newType !== 'screenshot';
setExportBranchesSupport(true);
} else { setExportBranches(exportBranchesSupport);
setExportBranches(false); setIncludeOptions(exportOptionsSupport);
setExportBranchesSupport(false);
}
setType(newType); 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 // return an object or an array based on branches and recursive option
// messageId is used to get siblindIdx from recoil snapshot // messageId is used to get siblindIdx from recoil snapshot
const buildMessageTree = async ({ messageId, message, messages, branches = false, recursive = false }) => { const buildMessageTree = async ({ messageId, message, messages, branches = false, recursive = false }) => {
@ -320,57 +323,54 @@ export default function ExportModel({ open, onOpenChange }) {
</div> </div>
</div> </div>
<div className="grid w-full gap-6 sm:grid-cols-2"> <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="col-span-1 flex flex-col items-start justify-start gap-2">
<div className="grid w-full items-center gap-2">
<Label
htmlFor="includeOptions"
className="text-left text-sm font-medium"
>
Include endpoint options
</Label>
<div className="flex h-[40px] w-full items-center space-x-3">
<Checkbox
id="includeOptions"
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}
/>
<label
htmlFor="includeOptions"
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 dark:text-gray-50"
>
Enabled
</label>
</div>
</div>
</div>
) : null}
{type !== 'screenshot' ? (
<div className="grid w-full items-center gap-2"> <div className="grid w-full items-center gap-2">
<Label <Label
htmlFor="exportBranches" htmlFor="includeOptions"
className="text-left text-sm font-medium" className="text-left text-sm font-medium"
> >
Export all message branches Include endpoint options
</Label> </Label>
<div className="flex h-[40px] w-full items-center space-x-3"> <div className="flex h-[40px] w-full items-center space-x-3">
<Checkbox <Checkbox
id="exportBranches" id="includeOptions"
disabled={!exportBranchesSupport} disabled={!exportOptionsSupport}
checked={exportBranches} 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" 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={setExportBranches} onCheckedChange={setIncludeOptions}
/> />
<label <label
htmlFor="exportBranches" htmlFor="includeOptions"
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 dark:text-gray-50" className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 dark:text-gray-50"
> >
{exportBranchesSupport ? 'Enabled' : 'Not Supported'} {exportOptionsSupport ? 'Enabled' : 'Not Supported'}
</label> </label>
</div> </div>
</div> </div>
) : null} </div>
<div className="grid w-full items-center gap-2">
<Label
htmlFor="exportBranches"
className="text-left text-sm font-medium"
>
Export all message branches
</Label>
<div className="flex h-[40px] w-full items-center space-x-3">
<Checkbox
id="exportBranches"
disabled={!exportBranchesSupport}
checked={exportBranches}
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={setExportBranches}
/>
<label
htmlFor="exportBranches"
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 dark:text-gray-50"
>
{exportBranchesSupport ? 'Enabled' : 'Not Supported'}
</label>
</div>
</div>
{type === 'json' ? ( {type === 'json' ? (
<div className="grid w-full items-center gap-2"> <div className="grid w-full items-center gap-2">
<Label <Label

View file

@ -4,6 +4,7 @@ import { Listbox } from '@headlessui/react';
import { cn } from '~/utils/'; import { cn } from '~/utils/';
function Dropdown({ value, onChange, options, className, containerClassName }) { function Dropdown({ value, onChange, options, className, containerClassName }) {
const currentOption = options.find(element => element === value || element?.value === value) ?? value;
return ( return (
<div className={cn('flex items-center justify-center gap-2', containerClassName)}> <div className={cn('flex items-center justify-center gap-2', containerClassName)}>
<div className="relative w-full"> <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="inline-flex w-full truncate">
<span className="flex h-6 items-center gap-1 truncate text-sm text-black dark:text-white"> <span className="flex h-6 items-center gap-1 truncate text-sm text-black dark:text-white">
{value} {currentOption?.display ?? value}
</span> </span>
</span> </span>
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2"> <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) => ( {options.map((item, i) => (
<Listbox.Option <Listbox.Option
key={i} 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" 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="flex items-center gap-1.5 truncate">
<span <span
className={cn( className={cn(
'flex h-6 items-center gap-1 text-gray-800 dark:text-gray-100', '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> </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"> <span className="absolute inset-y-0 right-0 flex items-center pr-3 text-gray-800 dark:text-gray-100">
<CheckMark /> <CheckMark />
</span> </span>

View file

@ -1,14 +1,37 @@
import React, { createContext, useRef, useContext, useCallback } from 'react'; import React, { createContext, useRef, useContext, useCallback } from 'react';
import { useScreenshot as useScreenshot_ } from 'use-react-screenshot'; import html2canvas from 'html2canvas';
const ScreenshotContext = createContext({}); const ScreenshotContext = createContext({});
export const useScreenshot = () => { export const useScreenshot = () => {
const { ref } = useContext(ScreenshotContext); 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 = () => { const captureScreenshot = () => {
return takeScreenshot(ref.current); return takeScreenShot(ref.current);
}; };
return { screenshotTargetRef: ref, captureScreenshot }; return { screenshotTargetRef: ref, captureScreenshot };