mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-21 19:00:13 +01:00
Merge branch 'main' into dano/react-query-typescript
This commit is contained in:
commit
5ea8f75f70
6 changed files with 131 additions and 110 deletions
|
|
@ -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">
|

|
||||||
<video src="https://user-images.githubusercontent.com/110412045/223754183-8b7f45ce-6517-4bd5-9b39-c624745bf399.mp4" width=400/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -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"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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 };
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue