mirror of
https://github.com/chathub-dev/chathub.git
synced 2025-09-26 20:31:18 +08:00
Add layout switch in all-in-one page
This commit is contained in:
@@ -72,7 +72,7 @@
|
||||
"i18next-browser-languagedetector": "^7.0.1",
|
||||
"immer": "^9.0.19",
|
||||
"inter-ui": "^3.19.3",
|
||||
"jotai": "^2.1.0",
|
||||
"jotai": "^2.2.1",
|
||||
"jotai-immer": "^0.2.0",
|
||||
"js-base64": "^3.7.5",
|
||||
"langchain": "^0.0.84",
|
||||
@@ -95,6 +95,7 @@
|
||||
"react-spinners": "^0.13.8",
|
||||
"react-textarea-autosize": "^8.4.1",
|
||||
"react-viewport-list": "^7.1.1",
|
||||
"react-wrap-balancer": "^1.0.0",
|
||||
"rehype-highlight": "^6.0.0",
|
||||
"rehype-katex": "^6.0.3",
|
||||
"rehype-sanitize": "^5.0.1",
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { incrTokenUsage } from '~services/storage'
|
||||
import { incrTokenUsage } from '~services/storage/token-usage'
|
||||
import { ChatMessage } from './consts'
|
||||
|
||||
import GPT3Tokenizer from 'gpt3-tokenizer'
|
||||
|
@@ -1,5 +1,4 @@
|
||||
import cx from 'classnames'
|
||||
import { useSetAtom } from 'jotai'
|
||||
import { FC, useCallback, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import clearIcon from '~/assets/icons/clear.svg'
|
||||
@@ -8,7 +7,6 @@ import shareIcon from '~/assets/icons/share.svg'
|
||||
import { CHATBOTS } from '~app/consts'
|
||||
import { ConversationContext, ConversationContextValue } from '~app/context'
|
||||
import { trackEvent } from '~app/plausible'
|
||||
import { multiPanelBotsAtom } from '~app/state'
|
||||
import { ChatMessageModel } from '~types'
|
||||
import { BotId, BotInstance } from '../../bots'
|
||||
import Button from '../Button'
|
||||
@@ -28,7 +26,7 @@ interface Props {
|
||||
generating: boolean
|
||||
stopGenerating: () => void
|
||||
mode?: 'full' | 'compact'
|
||||
index?: number
|
||||
onSwitchBot?: (botId: BotId) => void
|
||||
}
|
||||
|
||||
const ConversationPanel: FC<Props> = (props) => {
|
||||
@@ -38,7 +36,6 @@ const ConversationPanel: FC<Props> = (props) => {
|
||||
const marginClass = 'mx-5'
|
||||
const [showHistory, setShowHistory] = useState(false)
|
||||
const [showShareDialog, setShowShareDialog] = useState(false)
|
||||
const setCompareBots = useSetAtom(multiPanelBotsAtom)
|
||||
|
||||
const context: ConversationContextValue = useMemo(() => {
|
||||
return {
|
||||
@@ -69,21 +66,6 @@ const ConversationPanel: FC<Props> = (props) => {
|
||||
trackEvent('open_share_dialog', { botId: props.botId })
|
||||
}, [props.botId])
|
||||
|
||||
const onSwitchBot = useCallback(
|
||||
(botId: BotId) => {
|
||||
if (props.index === undefined) {
|
||||
return
|
||||
}
|
||||
trackEvent('switch_bot', { botId })
|
||||
setCompareBots((bots) => {
|
||||
const newBots = [...bots]
|
||||
newBots[props.index!] = botId
|
||||
return newBots
|
||||
})
|
||||
},
|
||||
[props.index, setCompareBots],
|
||||
)
|
||||
|
||||
return (
|
||||
<ConversationContext.Provider value={context}>
|
||||
<div className={cx('flex flex-col overflow-hidden bg-primary-background h-full rounded-[20px]')}>
|
||||
@@ -98,7 +80,9 @@ const ConversationPanel: FC<Props> = (props) => {
|
||||
<Tooltip content={props.bot.name || botInfo.name}>
|
||||
<span className="font-semibold text-primary-text text-sm cursor-default">{botInfo.name}</span>
|
||||
</Tooltip>
|
||||
{mode === 'compact' && <SwitchBotDropdown excludeBotId={props.botId} onChange={onSwitchBot} />}
|
||||
{mode === 'compact' && props.onSwitchBot && (
|
||||
<SwitchBotDropdown selectedBotId={props.botId} onChange={props.onSwitchBot} />
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-row items-center gap-3">
|
||||
<Tooltip content={t('Share conversation')}>
|
||||
|
30
src/app/components/Chat/LayoutSwitch.tsx
Normal file
30
src/app/components/Chat/LayoutSwitch.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import cx from 'classnames'
|
||||
import { FC } from 'react'
|
||||
import layoutFourIcon from '~assets/icons/layout-four.svg'
|
||||
import layoutThreeIcon from '~assets/icons/layout-three.svg'
|
||||
import layoutTwoIcon from '~assets/icons/layout-two.svg'
|
||||
|
||||
const Item: FC<{ icon: string; active: boolean; onClick: () => void }> = (props) => {
|
||||
return (
|
||||
<a className={cx(!!props.active && 'bg-[#00000014] dark:bg-[#ffffff26] rounded-[6px]')} onClick={props.onClick}>
|
||||
<img src={props.icon} className="w-8 h-8 cursor-pointer" />
|
||||
</a>
|
||||
)
|
||||
}
|
||||
|
||||
interface Props {
|
||||
layout: number
|
||||
onChange: (layout: number) => void
|
||||
}
|
||||
|
||||
const LayoutSwitch: FC<Props> = (props) => {
|
||||
return (
|
||||
<div className="flex flex-row items-center gap-2 bg-primary-background rounded-[15px] px-4">
|
||||
<Item icon={layoutTwoIcon} active={props.layout === 2} onClick={() => props.onChange(2)} />
|
||||
<Item icon={layoutThreeIcon} active={props.layout === 3} onClick={() => props.onChange(3)} />
|
||||
<Item icon={layoutFourIcon} active={props.layout === 4} onClick={() => props.onChange(4)} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default LayoutSwitch
|
@@ -12,7 +12,7 @@ function Layout() {
|
||||
style={{ backgroundColor: followArcTheme ? 'var(--arc-palette-foregroundPrimary)' : themeColor }}
|
||||
>
|
||||
<Sidebar />
|
||||
<div className="p-[15px] h-full overflow-hidden">
|
||||
<div className="px-[15px] py-3 h-full overflow-hidden">
|
||||
<Outlet />
|
||||
</div>
|
||||
</main>
|
||||
|
33
src/app/components/PremiumFeatureModal.tsx
Normal file
33
src/app/components/PremiumFeatureModal.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import { Link } from '@tanstack/react-router'
|
||||
import { FC } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Button from './Button'
|
||||
import Dialog from './Dialog'
|
||||
|
||||
interface Props {
|
||||
open: boolean
|
||||
setOpen: (open: boolean) => void
|
||||
}
|
||||
|
||||
const PremiumFeatureModal: FC<Props> = (props) => {
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<Dialog
|
||||
title={`🔒 ${t('Premium Feature')}`}
|
||||
open={props.open}
|
||||
onClose={() => props.setOpen(false)}
|
||||
className="rounded-2xl w-[500px]"
|
||||
>
|
||||
<div className="flex flex-col items-center gap-4 py-5">
|
||||
<p className="font-semibold text-primary-text text-center w-[70%]">
|
||||
{t('Upgrade to premium to chat with more than two bots at once')}
|
||||
</p>
|
||||
<Link to="/premium" onClick={() => props.setOpen(false)} className="focus-visible:outline-none">
|
||||
<Button color="primary" text={t('Upgrade')} />
|
||||
</Link>
|
||||
</div>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
export default PremiumFeatureModal
|
@@ -3,6 +3,8 @@ import cx from 'classnames'
|
||||
import { useAtom } from 'jotai'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { BsLayoutSplit, BsLayoutThreeColumns } from 'react-icons/bs'
|
||||
import { RxViewGrid } from 'react-icons/rx'
|
||||
import allInOneIcon from '~/assets/all-in-one.svg'
|
||||
import collapseIcon from '~/assets/icons/collapse.svg'
|
||||
import feedbackIcon from '~/assets/icons/feedback.svg'
|
||||
|
@@ -5,7 +5,7 @@ import { BotId } from '~app/bots'
|
||||
import { useEnabledBots } from '~app/hooks/use-enabled-bots'
|
||||
|
||||
interface Props {
|
||||
excludeBotId: BotId
|
||||
selectedBotId: BotId
|
||||
onChange: (botId: BotId) => void
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ const SwitchBotDropdown: FC<Props> = (props) => {
|
||||
>
|
||||
<Menu.Items className="absolute left-0 z-10 mt-2 rounded-md bg-secondary shadow-lg focus:outline-none">
|
||||
{enabledBots.map(({ botId, bot }) => {
|
||||
if (botId === props.excludeBotId) {
|
||||
if (botId === props.selectedBotId) {
|
||||
return null
|
||||
}
|
||||
return (
|
||||
|
@@ -79,6 +79,8 @@ const resources = {
|
||||
Premium: '付费会员',
|
||||
Chatbots: '聊天机器人',
|
||||
'Manage order and devices': '管理订单与设备',
|
||||
'Upgrade to premium to chat with more than two bots at once': '升级会员,同时和两个以上的机器人聊天',
|
||||
Upgrade: '升级',
|
||||
},
|
||||
},
|
||||
de: {
|
||||
|
@@ -1,24 +1,50 @@
|
||||
import cx from 'classnames'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { useAtomValue, useSetAtom } from 'jotai'
|
||||
import { atomWithStorage } from 'jotai/utils'
|
||||
import { uniqBy } from 'lodash-es'
|
||||
import { FC, Suspense, useCallback, useMemo } from 'react'
|
||||
import { FC, Suspense, useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Button from '~app/components/Button'
|
||||
import ChatMessageInput from '~app/components/Chat/ChatMessageInput'
|
||||
import LayoutSwitch from '~app/components/Chat/LayoutSwitch'
|
||||
import PremiumFeatureModal from '~app/components/PremiumFeatureModal'
|
||||
import { useChat } from '~app/hooks/use-chat'
|
||||
import { useUserConfig } from '~app/hooks/use-user-config'
|
||||
import { usePremium } from '~app/hooks/use-premium'
|
||||
import { trackEvent } from '~app/plausible'
|
||||
import { multiPanelBotsAtom } from '~app/state'
|
||||
import { MultiPanelLayout } from '~services/user-config'
|
||||
import { getAllInOneLayout, setAllInOneLayout } from '~services/storage/all-in-one-layout'
|
||||
import { BotId } from '../bots'
|
||||
import ConversationPanel from '../components/Chat/ConversationPanel'
|
||||
|
||||
const GeneralChatPanel: FC<{ chats: ReturnType<typeof useChat>[] }> = ({ chats }) => {
|
||||
type OnLayoutChange = (layout: number) => void
|
||||
|
||||
const twoPanelBotsAtom = atomWithStorage<BotId[]>('multiPanelBots:2', ['chatgpt', 'bing'])
|
||||
const threePanelBotsAtom = atomWithStorage<BotId[]>('multiPanelBots:3', ['chatgpt', 'bing', 'bard'])
|
||||
const fourPanelBotsAtom = atomWithStorage<BotId[]>('multiPanelBots:4', ['chatgpt', 'bing', 'claude', 'bard'])
|
||||
|
||||
const GeneralChatPanel: FC<{
|
||||
chats: ReturnType<typeof useChat>[]
|
||||
botsAtom: typeof twoPanelBotsAtom
|
||||
onLayoutChange: OnLayoutChange
|
||||
}> = ({ chats, botsAtom, onLayoutChange }) => {
|
||||
const { t } = useTranslation()
|
||||
const generating = useMemo(() => chats.some((c) => c.generating), [chats])
|
||||
const setBots = useSetAtom(botsAtom)
|
||||
const [premiumModalOpen, setPremiumModalOpen] = useState(false)
|
||||
const premiumState = usePremium()
|
||||
const disabled = useMemo(() => !premiumState.isLoading && !premiumState.activated, [premiumState])
|
||||
|
||||
useEffect(() => {
|
||||
if (disabled && chats.length > 2) {
|
||||
setPremiumModalOpen(true)
|
||||
}
|
||||
}, [chats.length, disabled])
|
||||
|
||||
const onUserSendMessage = useCallback(
|
||||
(input: string, botId?: BotId) => {
|
||||
if (disabled && chats.length > 2) {
|
||||
setPremiumModalOpen(true)
|
||||
return
|
||||
}
|
||||
if (botId) {
|
||||
const chat = chats.find((c) => c.botId === botId)
|
||||
chat?.sendMessage(input)
|
||||
@@ -27,7 +53,19 @@ const GeneralChatPanel: FC<{ chats: ReturnType<typeof useChat>[] }> = ({ chats }
|
||||
}
|
||||
trackEvent('send_messages', { count: chats.length })
|
||||
},
|
||||
[chats],
|
||||
[chats, disabled],
|
||||
)
|
||||
|
||||
const onSwitchBot = useCallback(
|
||||
(botId: BotId, index: number) => {
|
||||
trackEvent('switch_bot', { botId, panel: chats.length })
|
||||
setBots((bots) => {
|
||||
const newBots = [...bots]
|
||||
newBots[index] = botId
|
||||
return newBots
|
||||
})
|
||||
},
|
||||
[chats.length, setBots],
|
||||
)
|
||||
|
||||
return (
|
||||
@@ -49,58 +87,68 @@ const GeneralChatPanel: FC<{ chats: ReturnType<typeof useChat>[] }> = ({ chats }
|
||||
stopGenerating={chat.stopGenerating}
|
||||
mode="compact"
|
||||
resetConversation={chat.resetConversation}
|
||||
index={index}
|
||||
onSwitchBot={(botId) => onSwitchBot(botId, index)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<ChatMessageInput
|
||||
mode="full"
|
||||
className="rounded-[20px] bg-primary-background px-4 py-2"
|
||||
disabled={generating}
|
||||
onSubmit={onUserSendMessage}
|
||||
actionButton={!generating && <Button text={t('Send')} color="primary" type="submit" />}
|
||||
autoFocus={true}
|
||||
/>
|
||||
<div className="flex flex-row gap-3">
|
||||
<LayoutSwitch layout={chats.length} onChange={onLayoutChange} />
|
||||
<ChatMessageInput
|
||||
mode="full"
|
||||
className="rounded-[15px] bg-primary-background px-4 py-2 grow"
|
||||
disabled={generating}
|
||||
onSubmit={onUserSendMessage}
|
||||
actionButton={!generating && <Button text={t('Send')} color="primary" type="submit" />}
|
||||
autoFocus={true}
|
||||
/>
|
||||
</div>
|
||||
<PremiumFeatureModal open={premiumModalOpen} setOpen={setPremiumModalOpen} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const TwoBotChatPanel: FC = () => {
|
||||
const multiPanelBotIds = useAtomValue(multiPanelBotsAtom)
|
||||
const TwoBotChatPanel: FC<{ onLayoutChange: OnLayoutChange }> = (props) => {
|
||||
const multiPanelBotIds = useAtomValue(twoPanelBotsAtom)
|
||||
const chat1 = useChat(multiPanelBotIds[0])
|
||||
const chat2 = useChat(multiPanelBotIds[1])
|
||||
const chats = useMemo(() => [chat1, chat2], [chat1, chat2])
|
||||
return <GeneralChatPanel chats={chats} />
|
||||
return <GeneralChatPanel chats={chats} botsAtom={twoPanelBotsAtom} onLayoutChange={props.onLayoutChange} />
|
||||
}
|
||||
|
||||
const ThreeBotChatPanel: FC = () => {
|
||||
const multiPanelBotIds = useAtomValue(multiPanelBotsAtom)
|
||||
const ThreeBotChatPanel: FC<{ onLayoutChange: OnLayoutChange }> = (props) => {
|
||||
const multiPanelBotIds = useAtomValue(threePanelBotsAtom)
|
||||
const chat1 = useChat(multiPanelBotIds[0])
|
||||
const chat2 = useChat(multiPanelBotIds[1])
|
||||
const chat3 = useChat(multiPanelBotIds[2])
|
||||
const chats = useMemo(() => [chat1, chat2, chat3], [chat1, chat2, chat3])
|
||||
return <GeneralChatPanel chats={chats} />
|
||||
return <GeneralChatPanel chats={chats} botsAtom={threePanelBotsAtom} onLayoutChange={props.onLayoutChange} />
|
||||
}
|
||||
|
||||
const FourBotChatPanel: FC = () => {
|
||||
const multiPanelBotIds = useAtomValue(multiPanelBotsAtom)
|
||||
const FourBotChatPanel: FC<{ onLayoutChange: OnLayoutChange }> = (props) => {
|
||||
const multiPanelBotIds = useAtomValue(fourPanelBotsAtom)
|
||||
const chat1 = useChat(multiPanelBotIds[0])
|
||||
const chat2 = useChat(multiPanelBotIds[1])
|
||||
const chat3 = useChat(multiPanelBotIds[2])
|
||||
const chat4 = useChat(multiPanelBotIds[3])
|
||||
const chats = useMemo(() => [chat1, chat2, chat3, chat4], [chat1, chat2, chat3, chat4])
|
||||
return <GeneralChatPanel chats={chats} />
|
||||
return <GeneralChatPanel chats={chats} botsAtom={fourPanelBotsAtom} onLayoutChange={props.onLayoutChange} />
|
||||
}
|
||||
|
||||
const MultiBotChatPanel: FC = () => {
|
||||
const { multiPanelLayout } = useUserConfig()
|
||||
if (multiPanelLayout === MultiPanelLayout.Four) {
|
||||
return <FourBotChatPanel />
|
||||
const [layout, setLayout] = useState(() => getAllInOneLayout())
|
||||
|
||||
const onLayoutChange = useCallback((layout: number) => {
|
||||
setLayout(layout)
|
||||
setAllInOneLayout(layout)
|
||||
}, [])
|
||||
|
||||
if (layout === 4) {
|
||||
return <FourBotChatPanel onLayoutChange={onLayoutChange} />
|
||||
}
|
||||
if (multiPanelLayout === MultiPanelLayout.Three) {
|
||||
return <ThreeBotChatPanel />
|
||||
if (layout === 3) {
|
||||
return <ThreeBotChatPanel onLayoutChange={onLayoutChange} />
|
||||
}
|
||||
return <TwoBotChatPanel />
|
||||
return <TwoBotChatPanel onLayoutChange={onLayoutChange} />
|
||||
}
|
||||
|
||||
const MultiBotChatPanelPage: FC = () => {
|
||||
|
@@ -44,7 +44,7 @@ function SidePanelPage() {
|
||||
<div className="flex flex-row items-center gap-2">
|
||||
<img src={botInfo.avatar} className="w-4 h-4 object-contain rounded-full" />
|
||||
<span className="font-semibold text-primary-text text-xs">{botInfo.name}</span>
|
||||
<SwitchBotDropdown excludeBotId={botId} onChange={setBotId} />
|
||||
<SwitchBotDropdown selectedBotId={botId} onChange={setBotId} />
|
||||
</div>
|
||||
<div className="flex flex-row items-center gap-3">
|
||||
<img
|
||||
|
@@ -2,9 +2,9 @@ import { createHashHistory, ReactRouter, RootRoute, Route, useParams } from '@ta
|
||||
import { BotId } from './bots'
|
||||
import Layout from './components/Layout'
|
||||
import MultiBotChatPanel from './pages/MultiBotChatPanel'
|
||||
import PremiumPage from './pages/PremiumPage'
|
||||
import SettingPage from './pages/SettingPage'
|
||||
import SingleBotChatPanel from './pages/SingleBotChatPanel'
|
||||
import PremiumPage from './pages/PremiumPage'
|
||||
|
||||
const rootRoute = new RootRoute()
|
||||
|
||||
|
@@ -22,8 +22,7 @@ export const chatFamily = atomFamily(
|
||||
(a, b) => a.botId === b.botId && a.page === b.page,
|
||||
)
|
||||
|
||||
export const multiPanelBotsAtom = atomWithStorage<BotId[]>('multiPanelBots', ['chatgpt', 'bing', 'claude', 'bard'])
|
||||
export const licenseKeyAtom = atomWithStorage('licenseKey', '')
|
||||
export const licenseKeyAtom = atomWithStorage('licenseKey', '', undefined, { unstable_getOnInit: true })
|
||||
export const sidebarCollapsedAtom = atomWithStorage('sidebarCollapsed', false)
|
||||
export const themeColorAtom = atomWithStorage('themeColor', getDefaultThemeColor())
|
||||
export const followArcThemeAtom = atomWithStorage('followArcTheme', false)
|
||||
|
5
src/assets/icons/layout-four.svg
Normal file
5
src/assets/icons/layout-four.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="5" y="7" width="22" height="18" rx="3" stroke="#BDBDBD" stroke-width="2"/>
|
||||
<line x1="16" y1="7" x2="16" y2="25" stroke="#BDBDBD" stroke-width="2"/>
|
||||
<line x1="27" y1="16" x2="5" y2="16" stroke="#BDBDBD" stroke-width="2"/>
|
||||
</svg>
|
After Width: | Height: | Size: 333 B |
5
src/assets/icons/layout-three.svg
Normal file
5
src/assets/icons/layout-three.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="5" y="7" width="22" height="18" rx="3" stroke="#BDBDBD" stroke-width="2"/>
|
||||
<line x1="12" y1="7" x2="12" y2="25" stroke="#BDBDBD" stroke-width="2"/>
|
||||
<line x1="20" y1="6" x2="20" y2="24" stroke="#BDBDBD" stroke-width="2"/>
|
||||
</svg>
|
After Width: | Height: | Size: 333 B |
4
src/assets/icons/layout-two.svg
Normal file
4
src/assets/icons/layout-two.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="5" y="7" width="22" height="18" rx="3" stroke="#BDBDBD" stroke-width="2"/>
|
||||
<line x1="16" y1="7" x2="16" y2="25" stroke="#BDBDBD" stroke-width="2"/>
|
||||
</svg>
|
After Width: | Height: | Size: 260 B |
10
src/services/storage/all-in-one-layout.ts
Normal file
10
src/services/storage/all-in-one-layout.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
function getAllInOneLayout() {
|
||||
const v = localStorage.getItem('allInOneLayout') || ''
|
||||
return parseInt(v) || 2
|
||||
}
|
||||
|
||||
function setAllInOneLayout(v: number) {
|
||||
localStorage.setItem('allInOneLayout', v.toString())
|
||||
}
|
||||
|
||||
export { getAllInOneLayout, setAllInOneLayout }
|
15
yarn.lock
15
yarn.lock
@@ -2092,7 +2092,7 @@
|
||||
|
||||
"@tanstack/react-router@^0.0.1-beta.83":
|
||||
version "0.0.1-beta.83"
|
||||
resolved "https://registry.yarnpkg.com/@tanstack/react-router/-/react-router-0.0.1-beta.83.tgz#280fdcc77e755743d7709d1c823e044a3a7c4bc4"
|
||||
resolved "https://registry.npmmirror.com/@tanstack/react-router/-/react-router-0.0.1-beta.83.tgz#280fdcc77e755743d7709d1c823e044a3a7c4bc4"
|
||||
integrity sha512-FcezDPKxXu7uP8tjTQpKBXkweiql053O7D3hRBg1kWuU3LyouHFGFALLr2xvWkue4WWSpa1mBzrINldYygDKLw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.16.7"
|
||||
@@ -4265,10 +4265,10 @@ jotai-immer@^0.2.0:
|
||||
resolved "https://registry.yarnpkg.com/jotai-immer/-/jotai-immer-0.2.0.tgz#9bfa1a5a7911c5b222d22a8388a801c05bc06c65"
|
||||
integrity sha512-hahK8EPiROS9RoNWmX/Z8rY9WkAijspX4BZ1O7umpcwI4kPNkbcCpu/PhiQ8FMcpEcF6KmbpbMpSSj/GFmo8NA==
|
||||
|
||||
jotai@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/jotai/-/jotai-2.1.0.tgz#b1a9525345518453802e4a64d99e2800598bab76"
|
||||
integrity sha512-fR82PtHAmEQrc/daMEYGc4EteW96/b6wodtDSCzLvoJA/6y4YG70er4hh2f8CYwYjqwQ0eZUModGfG4DmwkTyQ==
|
||||
jotai@^2.2.1:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.npmmirror.com/jotai/-/jotai-2.2.1.tgz#0a95b88c5f3ea4fd656b5f79af6f84e895f84f5a"
|
||||
integrity sha512-Gz4tpbRQy9OiFgBwF9F7TieDn0UTE3C0IFSDuxHjOIvgn2tACH30UKz6p/wIlfoZROXSTCIxEvYEa7Y25WM+8g==
|
||||
|
||||
js-base64@^3.7.5:
|
||||
version "3.7.5"
|
||||
@@ -5881,6 +5881,11 @@ react-viewport-list@^7.1.1:
|
||||
resolved "https://registry.yarnpkg.com/react-viewport-list/-/react-viewport-list-7.1.1.tgz#2f36ad1db8124e8a960bc006b95228b696cc81d6"
|
||||
integrity sha512-O3gxykg3DgpcyYH+/X2kFwWFaZjckJ0FK3UBb4vFhZe+CsGLcX8OVJb0VSYI+IupEuQ9pl8dvJak8JRkIuvNjw==
|
||||
|
||||
react-wrap-balancer@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.npmmirror.com/react-wrap-balancer/-/react-wrap-balancer-1.0.0.tgz#f45f8af7cf68d9d54d16a8cf271047b75d1431d7"
|
||||
integrity sha512-yjDH+I8WGyDfh95gKhX/6ckfSBAltwQkxiYxtLPlyIRQNUVSjvz1uHR6Hpy+zHyOkJQw6GEC5RPglA41QXvzyw==
|
||||
|
||||
react@^18.2.0:
|
||||
version "18.2.0"
|
||||
resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"
|
||||
|
Reference in New Issue
Block a user