@ -91,6 +91,7 @@
|
|||||||
"tinycolor2": "1.6.0",
|
"tinycolor2": "1.6.0",
|
||||||
"unsplash-js": "7.0.18",
|
"unsplash-js": "7.0.18",
|
||||||
"use-debounce": "9.0.4",
|
"use-debounce": "9.0.4",
|
||||||
|
"zod-validation-error": "3.0.3",
|
||||||
"zustand": "4.5.0"
|
"zustand": "4.5.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -18,7 +18,7 @@ import {
|
|||||||
|
|
||||||
export interface ContextMenuProps<T extends HTMLElement> {
|
export interface ContextMenuProps<T extends HTMLElement> {
|
||||||
onOpen?: () => void
|
onOpen?: () => void
|
||||||
renderMenu: () => JSX.Element | null
|
renderMenu: ({ onClose }: { onClose: () => void }) => JSX.Element | null
|
||||||
children: (
|
children: (
|
||||||
ref: MutableRefObject<T | null>,
|
ref: MutableRefObject<T | null>,
|
||||||
isOpened: boolean
|
isOpened: boolean
|
||||||
@ -101,7 +101,7 @@ export function ContextMenu<T extends HTMLElement = HTMLElement>(
|
|||||||
}}
|
}}
|
||||||
{...props.menuButtonProps}
|
{...props.menuButtonProps}
|
||||||
/>
|
/>
|
||||||
{props.renderMenu()}
|
{props.renderMenu({ onClose: onCloseHandler })}
|
||||||
</Menu>
|
</Menu>
|
||||||
</Portal>
|
</Portal>
|
||||||
)}
|
)}
|
||||||
|
@ -662,3 +662,12 @@ export const UnlinkIcon = (props: IconProps) => (
|
|||||||
<line x1="19" x2="22" y1="16" y2="16" />
|
<line x1="19" x2="22" y1="16" y2="16" />
|
||||||
</Icon>
|
</Icon>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
export const RepeatIcon = (props: IconProps) => (
|
||||||
|
<Icon viewBox="0 0 24 24" {...featherIconsBaseProps} {...props}>
|
||||||
|
<path d="m2 9 3-3 3 3" />
|
||||||
|
<path d="M13 18H7a2 2 0 0 1-2-2V6" />
|
||||||
|
<path d="m22 15-3 3-3-3" />
|
||||||
|
<path d="M11 6h6a2 2 0 0 1 2 2v10" />
|
||||||
|
</Icon>
|
||||||
|
)
|
||||||
|
@ -0,0 +1,95 @@
|
|||||||
|
import {
|
||||||
|
MenuList,
|
||||||
|
MenuItem,
|
||||||
|
useDisclosure,
|
||||||
|
Menu,
|
||||||
|
MenuButton,
|
||||||
|
HStack,
|
||||||
|
Text,
|
||||||
|
} from '@chakra-ui/react'
|
||||||
|
import { RepeatIcon, ChevronRightIcon } from '@/components/icons'
|
||||||
|
import { useDebouncedCallback } from 'use-debounce'
|
||||||
|
import { useForgedBlock } from '@/features/forge/hooks/useForgedBlock'
|
||||||
|
import { ForgedBlockIcon } from '@/features/forge/ForgedBlockIcon'
|
||||||
|
import { ForgedBlock } from '@typebot.io/forge-schemas'
|
||||||
|
import { TurnableIntoParam } from '@typebot.io/forge'
|
||||||
|
import { ZodObject } from 'zod'
|
||||||
|
import { BlockV6 } from '@typebot.io/schemas'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
block: BlockV6
|
||||||
|
onTurnIntoClick: (
|
||||||
|
params: TurnableIntoParam,
|
||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
blockSchema: ZodObject<any>
|
||||||
|
) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ForgedBlockTurnIntoMenu = ({ block, onTurnIntoClick }: Props) => {
|
||||||
|
const { actionDef } = useForgedBlock(
|
||||||
|
block.type,
|
||||||
|
'options' in block ? block.options?.action : undefined
|
||||||
|
)
|
||||||
|
const { onClose, onOpen, isOpen } = useDisclosure()
|
||||||
|
const debounceSubMenuClose = useDebouncedCallback(onClose, 200)
|
||||||
|
|
||||||
|
const handleMouseEnter = () => {
|
||||||
|
debounceSubMenuClose.cancel()
|
||||||
|
onOpen()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!actionDef ||
|
||||||
|
!actionDef?.turnableInto ||
|
||||||
|
actionDef?.turnableInto.length === 0
|
||||||
|
)
|
||||||
|
return null
|
||||||
|
return (
|
||||||
|
<Menu isOpen={isOpen} placement="right" offset={[0, 0]} onClose={onClose}>
|
||||||
|
<MenuButton
|
||||||
|
as={MenuItem}
|
||||||
|
onClick={onOpen}
|
||||||
|
onMouseEnter={handleMouseEnter}
|
||||||
|
onMouseLeave={debounceSubMenuClose}
|
||||||
|
icon={<RepeatIcon />}
|
||||||
|
>
|
||||||
|
<HStack justifyContent="space-between">
|
||||||
|
<Text>Turn into</Text>
|
||||||
|
<ChevronRightIcon />
|
||||||
|
</HStack>
|
||||||
|
</MenuButton>
|
||||||
|
<MenuList
|
||||||
|
onMouseEnter={handleMouseEnter}
|
||||||
|
onMouseLeave={debounceSubMenuClose}
|
||||||
|
>
|
||||||
|
{actionDef.turnableInto.map((params) => (
|
||||||
|
<TurnIntoMenuItem
|
||||||
|
key={params.blockType}
|
||||||
|
blockType={params.blockType}
|
||||||
|
onClick={(blockSchema) => onTurnIntoClick(params, blockSchema)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</MenuList>
|
||||||
|
</Menu>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const TurnIntoMenuItem = ({
|
||||||
|
blockType,
|
||||||
|
onClick,
|
||||||
|
}: {
|
||||||
|
blockType: ForgedBlock['type']
|
||||||
|
onClick: (blockSchema: ZodObject<any>) => void
|
||||||
|
}) => {
|
||||||
|
const { blockDef, blockSchema } = useForgedBlock(blockType)
|
||||||
|
|
||||||
|
if (!blockDef || !blockSchema) return null
|
||||||
|
return (
|
||||||
|
<MenuItem
|
||||||
|
icon={<ForgedBlockIcon type={blockType} />}
|
||||||
|
onClick={() => onClick(blockSchema)}
|
||||||
|
>
|
||||||
|
{blockDef.name}
|
||||||
|
</MenuItem>
|
||||||
|
)
|
||||||
|
}
|
@ -1,12 +1,9 @@
|
|||||||
import { useMemo } from 'react'
|
import { useMemo } from 'react'
|
||||||
import { forgedBlockSchemas, forgedBlocks } from '@typebot.io/forge-schemas'
|
import { forgedBlockSchemas, forgedBlocks } from '@typebot.io/forge-schemas'
|
||||||
import { enabledBlocks } from '@typebot.io/forge-repository'
|
import { enabledBlocks } from '@typebot.io/forge-repository'
|
||||||
import { BlockWithOptions } from '@typebot.io/schemas'
|
import { BlockV6 } from '@typebot.io/schemas'
|
||||||
|
|
||||||
export const useForgedBlock = (
|
export const useForgedBlock = (blockType: BlockV6['type'], action?: string) =>
|
||||||
blockType: BlockWithOptions['type'],
|
|
||||||
action?: string
|
|
||||||
) =>
|
|
||||||
useMemo(() => {
|
useMemo(() => {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
if ((enabledBlocks as any).includes(blockType) === false) return {}
|
if ((enabledBlocks as any).includes(blockType) === false) return {}
|
||||||
|
@ -45,6 +45,10 @@ import { SettingsModal } from './SettingsModal'
|
|||||||
import { TElement } from '@udecode/plate-common'
|
import { TElement } from '@udecode/plate-common'
|
||||||
import { LogicBlockType } from '@typebot.io/schemas/features/blocks/logic/constants'
|
import { LogicBlockType } from '@typebot.io/schemas/features/blocks/logic/constants'
|
||||||
import { useGroupsStore } from '@/features/graph/hooks/useGroupsStore'
|
import { useGroupsStore } from '@/features/graph/hooks/useGroupsStore'
|
||||||
|
import { TurnableIntoParam } from '@typebot.io/forge'
|
||||||
|
import { ZodError, ZodObject } from 'zod'
|
||||||
|
import { toast } from 'sonner'
|
||||||
|
import { fromZodError } from 'zod-validation-error'
|
||||||
|
|
||||||
export const BlockNode = ({
|
export const BlockNode = ({
|
||||||
block,
|
block,
|
||||||
@ -186,6 +190,42 @@ export const BlockNode = ({
|
|||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
const convertBlock = (
|
||||||
|
turnIntoParams: TurnableIntoParam,
|
||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
targetBlockSchema: ZodObject<any>
|
||||||
|
) => {
|
||||||
|
if (!('options' in block) || !block.options) return
|
||||||
|
|
||||||
|
const convertedBlockOptions = turnIntoParams.customMapping
|
||||||
|
? turnIntoParams.customMapping(block.options)
|
||||||
|
: block.options
|
||||||
|
try {
|
||||||
|
updateBlock(
|
||||||
|
indices,
|
||||||
|
targetBlockSchema.parse({
|
||||||
|
...block,
|
||||||
|
type: turnIntoParams.blockType,
|
||||||
|
options: {
|
||||||
|
...convertedBlockOptions,
|
||||||
|
credentialsId: undefined,
|
||||||
|
},
|
||||||
|
} as Block)
|
||||||
|
)
|
||||||
|
setOpenedBlockId(block.id)
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof ZodError) {
|
||||||
|
const validationError = fromZodError(error)
|
||||||
|
console.error(validationError)
|
||||||
|
toast.error('Could not convert block', {
|
||||||
|
description: validationError.toString(),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
toast.error('An error occured while converting the block')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const hasIcomingEdge = typebot?.edges.some((edge) => {
|
const hasIcomingEdge = typebot?.edges.some((edge) => {
|
||||||
return edge.to.blockId === block.id
|
return edge.to.blockId === block.id
|
||||||
})
|
})
|
||||||
@ -198,7 +238,16 @@ export const BlockNode = ({
|
|||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<ContextMenu<HTMLDivElement>
|
<ContextMenu<HTMLDivElement>
|
||||||
renderMenu={() => <BlockNodeContextMenu indices={indices} />}
|
renderMenu={({ onClose }) => (
|
||||||
|
<BlockNodeContextMenu
|
||||||
|
indices={indices}
|
||||||
|
block={block}
|
||||||
|
onTurnIntoClick={(params, schema) => {
|
||||||
|
convertBlock(params, schema)
|
||||||
|
onClose()
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
{(ref, isContextMenuOpened) => (
|
{(ref, isContextMenuOpened) => (
|
||||||
<Popover
|
<Popover
|
||||||
|
@ -1,11 +1,24 @@
|
|||||||
import { MenuList, MenuItem } from '@chakra-ui/react'
|
import { MenuList, MenuItem } from '@chakra-ui/react'
|
||||||
import { CopyIcon, TrashIcon } from '@/components/icons'
|
import { CopyIcon, TrashIcon } from '@/components/icons'
|
||||||
import { useTypebot } from '@/features/editor/providers/TypebotProvider'
|
import { useTypebot } from '@/features/editor/providers/TypebotProvider'
|
||||||
import { BlockIndices } from '@typebot.io/schemas'
|
import { BlockIndices, BlockV6 } from '@typebot.io/schemas'
|
||||||
import { useTranslate } from '@tolgee/react'
|
import { useTranslate } from '@tolgee/react'
|
||||||
|
import { ForgedBlockTurnIntoMenu } from '@/features/forge/components/ForgedBlockTurnIntoMenu'
|
||||||
|
import { TurnableIntoParam } from '@typebot.io/forge'
|
||||||
|
import { ZodObject } from 'zod'
|
||||||
|
|
||||||
type Props = { indices: BlockIndices }
|
type Props = {
|
||||||
export const BlockNodeContextMenu = ({ indices }: Props) => {
|
indices: BlockIndices
|
||||||
|
block: BlockV6
|
||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
onTurnIntoClick: (params: TurnableIntoParam, schema: ZodObject<any>) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export const BlockNodeContextMenu = ({
|
||||||
|
indices,
|
||||||
|
block,
|
||||||
|
onTurnIntoClick,
|
||||||
|
}: Props) => {
|
||||||
const { t } = useTranslate()
|
const { t } = useTranslate()
|
||||||
const { deleteBlock, duplicateBlock } = useTypebot()
|
const { deleteBlock, duplicateBlock } = useTypebot()
|
||||||
|
|
||||||
@ -15,6 +28,10 @@ export const BlockNodeContextMenu = ({ indices }: Props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<MenuList>
|
<MenuList>
|
||||||
|
<ForgedBlockTurnIntoMenu
|
||||||
|
block={block}
|
||||||
|
onTurnIntoClick={onTurnIntoClick}
|
||||||
|
/>
|
||||||
<MenuItem icon={<CopyIcon />} onClick={handleDuplicateClick}>
|
<MenuItem icon={<CopyIcon />} onClick={handleDuplicateClick}>
|
||||||
{t('duplicate')}
|
{t('duplicate')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
@ -8,6 +8,7 @@ import { ChatNodeResponse } from '../types'
|
|||||||
export const sendMessage = createAction({
|
export const sendMessage = createAction({
|
||||||
auth,
|
auth,
|
||||||
name: 'Send Message',
|
name: 'Send Message',
|
||||||
|
turnableInto: undefined,
|
||||||
options: option.object({
|
options: option.object({
|
||||||
botId: option.string.layout({
|
botId: option.string.layout({
|
||||||
label: 'Bot ID',
|
label: 'Bot ID',
|
||||||
|
@ -67,6 +67,15 @@ export const createChatCompletion = createAction({
|
|||||||
name: 'Create chat completion',
|
name: 'Create chat completion',
|
||||||
auth,
|
auth,
|
||||||
options,
|
options,
|
||||||
|
turnableInto: [
|
||||||
|
{
|
||||||
|
blockType: 'openai',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
blockType: 'together-ai',
|
||||||
|
},
|
||||||
|
{ blockType: 'open-router' },
|
||||||
|
],
|
||||||
getSetVariableIds: (options) =>
|
getSetVariableIds: (options) =>
|
||||||
options.responseMapping?.map((res) => res.variableId).filter(isDefined) ??
|
options.responseMapping?.map((res) => res.variableId).filter(isDefined) ??
|
||||||
[],
|
[],
|
||||||
|
@ -9,7 +9,7 @@ export const auth = {
|
|||||||
isRequired: true,
|
isRequired: true,
|
||||||
inputType: 'password',
|
inputType: 'password',
|
||||||
helperText:
|
helperText:
|
||||||
'You can generate an API key [here](https://console.mistral.ai/user/api-keys/).',
|
'You can generate an API key [here](https://console.mistral.ai/api-keys).',
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
} satisfies AuthDefinition
|
} satisfies AuthDefinition
|
||||||
|
@ -12,6 +12,15 @@ import { ModelsResponse } from '../types'
|
|||||||
export const createChatCompletion = createAction({
|
export const createChatCompletion = createAction({
|
||||||
name: 'Create chat completion',
|
name: 'Create chat completion',
|
||||||
auth,
|
auth,
|
||||||
|
turnableInto: [
|
||||||
|
{
|
||||||
|
blockType: 'openai',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
blockType: 'together-ai',
|
||||||
|
},
|
||||||
|
{ blockType: 'mistral' },
|
||||||
|
],
|
||||||
options: parseChatCompletionOptions({
|
options: parseChatCompletionOptions({
|
||||||
modelFetchId: 'fetchModels',
|
modelFetchId: 'fetchModels',
|
||||||
}),
|
}),
|
||||||
|
@ -19,6 +19,15 @@ export const createChatCompletion = createAction({
|
|||||||
modelFetchId: 'fetchModels',
|
modelFetchId: 'fetchModels',
|
||||||
}),
|
}),
|
||||||
getSetVariableIds: getChatCompletionSetVarIds,
|
getSetVariableIds: getChatCompletionSetVarIds,
|
||||||
|
turnableInto: [
|
||||||
|
{
|
||||||
|
blockType: 'open-router',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
blockType: 'together-ai',
|
||||||
|
},
|
||||||
|
{ blockType: 'mistral' },
|
||||||
|
],
|
||||||
fetchers: [
|
fetchers: [
|
||||||
{
|
{
|
||||||
id: 'fetchModels',
|
id: 'fetchModels',
|
||||||
|
@ -14,6 +14,15 @@ export const createChatCompletion = createAction({
|
|||||||
modelHelperText:
|
modelHelperText:
|
||||||
'You can find the list of all the models available [here](https://docs.together.ai/docs/inference-models#chat-models). Copy the model string for API.',
|
'You can find the list of all the models available [here](https://docs.together.ai/docs/inference-models#chat-models). Copy the model string for API.',
|
||||||
}),
|
}),
|
||||||
|
turnableInto: [
|
||||||
|
{
|
||||||
|
blockType: 'openai',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
blockType: 'open-router',
|
||||||
|
},
|
||||||
|
{ blockType: 'mistral' },
|
||||||
|
],
|
||||||
getSetVariableIds: getChatCompletionSetVarIds,
|
getSetVariableIds: getChatCompletionSetVarIds,
|
||||||
run: {
|
run: {
|
||||||
server: (params) =>
|
server: (params) =>
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@typebot.io/tsconfig": "workspace:*",
|
"@typebot.io/tsconfig": "workspace:*",
|
||||||
"@types/react": "18.2.15"
|
"@types/react": "18.2.15",
|
||||||
|
"@typebot.io/forge-repository": "workspace:*"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { SVGProps } from 'react'
|
import { SVGProps } from 'react'
|
||||||
import { z } from './zod'
|
import { z } from './zod'
|
||||||
import { ZodRawShape } from 'zod'
|
import { ZodRawShape } from 'zod'
|
||||||
|
import { enabledBlocks } from '@typebot.io/forge-repository'
|
||||||
|
|
||||||
export type VariableStore = {
|
export type VariableStore = {
|
||||||
get: (variableId: string) => string | (string | null)[] | null | undefined
|
get: (variableId: string) => string | (string | null)[] | null | undefined
|
||||||
@ -32,6 +33,14 @@ export type FunctionToExecute = {
|
|||||||
|
|
||||||
export type ReadOnlyVariableStore = Omit<VariableStore, 'set'>
|
export type ReadOnlyVariableStore = Omit<VariableStore, 'set'>
|
||||||
|
|
||||||
|
export type TurnableIntoParam<T = {}> = {
|
||||||
|
blockType: (typeof enabledBlocks)[number]
|
||||||
|
/**
|
||||||
|
* If defined will be used to convert the existing block options into the new block options.
|
||||||
|
*/
|
||||||
|
customMapping?: (options: T) => any
|
||||||
|
}
|
||||||
|
|
||||||
export type ActionDefinition<
|
export type ActionDefinition<
|
||||||
A extends AuthDefinition,
|
A extends AuthDefinition,
|
||||||
BaseOptions extends z.ZodObject<ZodRawShape> = z.ZodObject<{}>,
|
BaseOptions extends z.ZodObject<ZodRawShape> = z.ZodObject<{}>,
|
||||||
@ -40,6 +49,7 @@ export type ActionDefinition<
|
|||||||
name: string
|
name: string
|
||||||
fetchers?: FetcherDefinition<A, z.infer<BaseOptions> & z.infer<Options>>[]
|
fetchers?: FetcherDefinition<A, z.infer<BaseOptions> & z.infer<Options>>[]
|
||||||
options?: Options
|
options?: Options
|
||||||
|
turnableInto?: TurnableIntoParam<z.infer<Options>>[]
|
||||||
getSetVariableIds?: (options: z.infer<Options>) => string[]
|
getSetVariableIds?: (options: z.infer<Options>) => string[]
|
||||||
run?: {
|
run?: {
|
||||||
server?: (params: {
|
server?: (params: {
|
||||||
|
15
pnpm-lock.yaml
generated
15
pnpm-lock.yaml
generated
@ -263,6 +263,9 @@ importers:
|
|||||||
use-debounce:
|
use-debounce:
|
||||||
specifier: 9.0.4
|
specifier: 9.0.4
|
||||||
version: 9.0.4(react@18.2.0)
|
version: 9.0.4(react@18.2.0)
|
||||||
|
zod-validation-error:
|
||||||
|
specifier: 3.0.3
|
||||||
|
version: 3.0.3(zod@3.22.4)
|
||||||
zustand:
|
zustand:
|
||||||
specifier: 4.5.0
|
specifier: 4.5.0
|
||||||
version: 4.5.0(@types/react@18.2.15)(immer@10.0.2)(react@18.2.0)
|
version: 4.5.0(@types/react@18.2.15)(immer@10.0.2)(react@18.2.0)
|
||||||
@ -1414,6 +1417,9 @@ importers:
|
|||||||
specifier: 3.22.4
|
specifier: 3.22.4
|
||||||
version: 3.22.4
|
version: 3.22.4
|
||||||
devDependencies:
|
devDependencies:
|
||||||
|
'@typebot.io/forge-repository':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../repository
|
||||||
'@typebot.io/tsconfig':
|
'@typebot.io/tsconfig':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../../tsconfig
|
version: link:../../tsconfig
|
||||||
@ -22787,6 +22793,15 @@ packages:
|
|||||||
zod: 3.22.4
|
zod: 3.22.4
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/zod-validation-error@3.0.3(zod@3.22.4):
|
||||||
|
resolution: {integrity: sha512-cETTrcMq3Ze58vhdR0zD37uJm/694I6mAxcf/ei5bl89cC++fBNxrC2z8lkFze/8hVMPwrbtrwXHR2LB50fpHw==}
|
||||||
|
engines: {node: '>=18.0.0'}
|
||||||
|
peerDependencies:
|
||||||
|
zod: ^3.18.0
|
||||||
|
dependencies:
|
||||||
|
zod: 3.22.4
|
||||||
|
dev: false
|
||||||
|
|
||||||
/zod@3.22.4:
|
/zod@3.22.4:
|
||||||
resolution: {integrity: sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==}
|
resolution: {integrity: sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user