implement API key alert
This commit is contained in:
@@ -21,7 +21,7 @@ LightRAG WebUI is a React-based web interface for interacting with the LightRAG
|
|||||||
Run the following command to build the project:
|
Run the following command to build the project:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
bun run build
|
bun run build --emptyOutDir
|
||||||
```
|
```
|
||||||
|
|
||||||
This command will bundle the project and output the built files to the `lightrag/api/webui` directory.
|
This command will bundle the project and output the built files to the `lightrag/api/webui` directory.
|
||||||
|
@@ -5,6 +5,7 @@
|
|||||||
"name": "lightrag-webui",
|
"name": "lightrag-webui",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@faker-js/faker": "^9.5.0",
|
"@faker-js/faker": "^9.5.0",
|
||||||
|
"@radix-ui/react-alert-dialog": "^1.1.6",
|
||||||
"@radix-ui/react-checkbox": "^1.1.4",
|
"@radix-ui/react-checkbox": "^1.1.4",
|
||||||
"@radix-ui/react-dialog": "^1.1.6",
|
"@radix-ui/react-dialog": "^1.1.6",
|
||||||
"@radix-ui/react-popover": "^1.1.6",
|
"@radix-ui/react-popover": "^1.1.6",
|
||||||
@@ -220,6 +221,8 @@
|
|||||||
|
|
||||||
"@radix-ui/primitive": ["@radix-ui/primitive@1.1.1", "", {}, "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA=="],
|
"@radix-ui/primitive": ["@radix-ui/primitive@1.1.1", "", {}, "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA=="],
|
||||||
|
|
||||||
|
"@radix-ui/react-alert-dialog": ["@radix-ui/react-alert-dialog@1.1.6", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-dialog": "1.1.6", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-slot": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-p4XnPqgej8sZAAReCAKgz1REYZEBLR8hU9Pg27wFnCWIMc8g1ccCs0FjBcy05V15VTu8pAePw/VDYeOm/uZ6yQ=="],
|
||||||
|
|
||||||
"@radix-ui/react-arrow": ["@radix-ui/react-arrow@1.1.2", "", { "dependencies": { "@radix-ui/react-primitive": "2.0.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-G+KcpzXHq24iH0uGG/pF8LyzpFJYGD4RfLjCIBfGdSLXvjLHST31RUiRVrupIBMvIppMgSzQ6l66iAxl03tdlg=="],
|
"@radix-ui/react-arrow": ["@radix-ui/react-arrow@1.1.2", "", { "dependencies": { "@radix-ui/react-primitive": "2.0.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-G+KcpzXHq24iH0uGG/pF8LyzpFJYGD4RfLjCIBfGdSLXvjLHST31RUiRVrupIBMvIppMgSzQ6l66iAxl03tdlg=="],
|
||||||
|
|
||||||
"@radix-ui/react-checkbox": ["@radix-ui/react-checkbox@1.1.4", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-presence": "1.1.2", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-use-controllable-state": "1.1.0", "@radix-ui/react-use-previous": "1.1.0", "@radix-ui/react-use-size": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-wP0CPAHq+P5I4INKe3hJrIa1WoNqqrejzW+zoU0rOvo1b9gDEJJFl2rYfO1PYJUQCc2H1WZxIJmyv9BS8i5fLw=="],
|
"@radix-ui/react-checkbox": ["@radix-ui/react-checkbox@1.1.4", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-presence": "1.1.2", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-use-controllable-state": "1.1.0", "@radix-ui/react-use-previous": "1.1.0", "@radix-ui/react-use-size": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-wP0CPAHq+P5I4INKe3hJrIa1WoNqqrejzW+zoU0rOvo1b9gDEJJFl2rYfO1PYJUQCc2H1WZxIJmyv9BS8i5fLw=="],
|
||||||
|
@@ -11,6 +11,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@faker-js/faker": "^9.5.0",
|
"@faker-js/faker": "^9.5.0",
|
||||||
|
"@radix-ui/react-alert-dialog": "^1.1.6",
|
||||||
"@radix-ui/react-checkbox": "^1.1.4",
|
"@radix-ui/react-checkbox": "^1.1.4",
|
||||||
"@radix-ui/react-dialog": "^1.1.6",
|
"@radix-ui/react-dialog": "^1.1.6",
|
||||||
"@radix-ui/react-popover": "^1.1.6",
|
"@radix-ui/react-popover": "^1.1.6",
|
||||||
|
@@ -1,13 +1,15 @@
|
|||||||
import { useState, useCallback } from 'react'
|
import { useState, useCallback } from 'react'
|
||||||
import ThemeProvider from '@/components/ThemeProvider'
|
import ThemeProvider from '@/components/graph/ThemeProvider'
|
||||||
import MessageAlert from '@/components/MessageAlert'
|
import MessageAlert from '@/components/MessageAlert'
|
||||||
import StatusIndicator from '@/components/StatusIndicator'
|
import ApiKeyAlert from '@/components/ApiKeyAlert'
|
||||||
|
import StatusIndicator from '@/components/graph/StatusIndicator'
|
||||||
import { healthCheckInterval } from '@/lib/constants'
|
import { healthCheckInterval } from '@/lib/constants'
|
||||||
import { useBackendState } from '@/stores/state'
|
import { useBackendState } from '@/stores/state'
|
||||||
import { useSettingsStore } from '@/stores/settings'
|
import { useSettingsStore } from '@/stores/settings'
|
||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import { Toaster } from 'sonner'
|
import { Toaster } from 'sonner'
|
||||||
import SiteHeader from '@/features/SiteHeader'
|
import SiteHeader from '@/features/SiteHeader'
|
||||||
|
import { InvalidApiKeyError, RequireApiKeError } from '@/api/lightrag'
|
||||||
|
|
||||||
import GraphViewer from '@/features/GraphViewer'
|
import GraphViewer from '@/features/GraphViewer'
|
||||||
import DocumentManager from '@/features/DocumentManager'
|
import DocumentManager from '@/features/DocumentManager'
|
||||||
@@ -20,6 +22,7 @@ function App() {
|
|||||||
const message = useBackendState.use.message()
|
const message = useBackendState.use.message()
|
||||||
const enableHealthCheck = useSettingsStore.use.enableHealthCheck()
|
const enableHealthCheck = useSettingsStore.use.enableHealthCheck()
|
||||||
const [currentTab] = useState(() => useSettingsStore.getState().currentTab)
|
const [currentTab] = useState(() => useSettingsStore.getState().currentTab)
|
||||||
|
const [apiKeyInvalid, setApiKeyInvalid] = useState(false)
|
||||||
|
|
||||||
// Health check
|
// Health check
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -39,6 +42,14 @@ function App() {
|
|||||||
[]
|
[]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (message) {
|
||||||
|
if (message.includes(InvalidApiKeyError) || message.includes(RequireApiKeError)) {
|
||||||
|
setApiKeyInvalid(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [message, setApiKeyInvalid])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ThemeProvider>
|
<ThemeProvider>
|
||||||
<main className="flex h-screen w-screen overflow-x-hidden">
|
<main className="flex h-screen w-screen overflow-x-hidden">
|
||||||
@@ -64,7 +75,8 @@ function App() {
|
|||||||
</div>
|
</div>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
{enableHealthCheck && <StatusIndicator />}
|
{enableHealthCheck && <StatusIndicator />}
|
||||||
{message !== null && <MessageAlert />}
|
{message !== null && !apiKeyInvalid && <MessageAlert />}
|
||||||
|
{apiKeyInvalid && <ApiKeyAlert />}
|
||||||
<Toaster />
|
<Toaster />
|
||||||
</main>
|
</main>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
|
79
lightrag_webui/src/components/ApiKeyAlert.tsx
Normal file
79
lightrag_webui/src/components/ApiKeyAlert.tsx
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
import { useState, useCallback, useEffect } from 'react'
|
||||||
|
import {
|
||||||
|
AlertDialog,
|
||||||
|
AlertDialogContent,
|
||||||
|
AlertDialogDescription,
|
||||||
|
AlertDialogHeader,
|
||||||
|
AlertDialogTitle,
|
||||||
|
AlertDialogTrigger
|
||||||
|
} from '@/components/ui/AlertDialog'
|
||||||
|
import Button from '@/components/ui/Button'
|
||||||
|
import Input from '@/components/ui/Input'
|
||||||
|
import { useSettingsStore } from '@/stores/settings'
|
||||||
|
import { useBackendState } from '@/stores/state'
|
||||||
|
import { InvalidApiKeyError, RequireApiKeError } from '@/api/lightrag'
|
||||||
|
|
||||||
|
import { toast } from 'sonner'
|
||||||
|
|
||||||
|
const ApiKeyAlert = () => {
|
||||||
|
const [opened, setOpened] = useState<boolean>(true)
|
||||||
|
const apiKey = useSettingsStore.use.apiKey()
|
||||||
|
const [tempApiKey, setTempApiKey] = useState<string>('')
|
||||||
|
const message = useBackendState.use.message()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setTempApiKey(apiKey || '')
|
||||||
|
}, [apiKey, opened])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (message) {
|
||||||
|
if (message.includes(InvalidApiKeyError) || message.includes(RequireApiKeError)) {
|
||||||
|
setOpened(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [message, setOpened])
|
||||||
|
|
||||||
|
const setApiKey = useCallback(async () => {
|
||||||
|
useSettingsStore.setState({ apiKey: tempApiKey || null })
|
||||||
|
if (await useBackendState.getState().check()) {
|
||||||
|
setOpened(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
toast.error('API Key is invalid')
|
||||||
|
}, [tempApiKey])
|
||||||
|
|
||||||
|
const handleTempApiKeyChange = useCallback(
|
||||||
|
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setTempApiKey(e.target.value)
|
||||||
|
},
|
||||||
|
[setTempApiKey]
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AlertDialog open={opened} onOpenChange={setOpened}>
|
||||||
|
<AlertDialogTrigger>Open</AlertDialogTrigger>
|
||||||
|
<AlertDialogContent>
|
||||||
|
<AlertDialogHeader>
|
||||||
|
<AlertDialogTitle>API Key is required</AlertDialogTitle>
|
||||||
|
<AlertDialogDescription>Please enter your API key</AlertDialogDescription>
|
||||||
|
</AlertDialogHeader>
|
||||||
|
<form className="flex gap-2" onSubmit={(e) => e.preventDefault()}>
|
||||||
|
<Input
|
||||||
|
type="password"
|
||||||
|
value={tempApiKey}
|
||||||
|
onChange={handleTempApiKeyChange}
|
||||||
|
placeholder="Enter your API key"
|
||||||
|
className="max-h-full w-full min-w-0"
|
||||||
|
autoComplete="off"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button onClick={setApiKey} variant="outline" size="sm">
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ApiKeyAlert
|
@@ -40,7 +40,7 @@ const LabeledCheckBox = ({
|
|||||||
*/
|
*/
|
||||||
export default function Settings() {
|
export default function Settings() {
|
||||||
const [opened, setOpened] = useState<boolean>(false)
|
const [opened, setOpened] = useState<boolean>(false)
|
||||||
const [tempApiKey, setTempApiKey] = useState<string>('') // 用于临时存储输入的API Key
|
const [tempApiKey, setTempApiKey] = useState<string>('')
|
||||||
|
|
||||||
const showPropertyPanel = useSettingsStore.use.showPropertyPanel()
|
const showPropertyPanel = useSettingsStore.use.showPropertyPanel()
|
||||||
const showNodeSearchBar = useSettingsStore.use.showNodeSearchBar()
|
const showNodeSearchBar = useSettingsStore.use.showNodeSearchBar()
|
@@ -2,7 +2,7 @@ import { cn } from '@/lib/utils'
|
|||||||
import { useBackendState } from '@/stores/state'
|
import { useBackendState } from '@/stores/state'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/Popover'
|
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/Popover'
|
||||||
import StatusCard from '@/components/StatusCard'
|
import StatusCard from '@/components/graph/StatusCard'
|
||||||
|
|
||||||
const StatusIndicator = () => {
|
const StatusIndicator = () => {
|
||||||
const health = useBackendState.use.health()
|
const health = useBackendState.use.health()
|
115
lightrag_webui/src/components/ui/AlertDialog.tsx
Normal file
115
lightrag_webui/src/components/ui/AlertDialog.tsx
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
import * as React from 'react'
|
||||||
|
import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog'
|
||||||
|
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
import { buttonVariants } from '@/components/ui/Button'
|
||||||
|
|
||||||
|
const AlertDialog = AlertDialogPrimitive.Root
|
||||||
|
|
||||||
|
const AlertDialogTrigger = AlertDialogPrimitive.Trigger
|
||||||
|
|
||||||
|
const AlertDialogPortal = AlertDialogPrimitive.Portal
|
||||||
|
|
||||||
|
const AlertDialogOverlay = React.forwardRef<
|
||||||
|
React.ComponentRef<typeof AlertDialogPrimitive.Overlay>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<AlertDialogPrimitive.Overlay
|
||||||
|
className={cn(
|
||||||
|
'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/80',
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
ref={ref}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName
|
||||||
|
|
||||||
|
const AlertDialogContent = React.forwardRef<
|
||||||
|
React.ComponentRef<typeof AlertDialogPrimitive.Content>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<AlertDialogPortal>
|
||||||
|
<AlertDialogOverlay />
|
||||||
|
<AlertDialogPrimitive.Content
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-top-[48%] fixed top-[50%] left-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border p-6 shadow-lg duration-200 sm:rounded-lg',
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</AlertDialogPortal>
|
||||||
|
))
|
||||||
|
AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName
|
||||||
|
|
||||||
|
const AlertDialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
||||||
|
<div className={cn('flex flex-col space-y-2 text-center sm:text-left', className)} {...props} />
|
||||||
|
)
|
||||||
|
AlertDialogHeader.displayName = 'AlertDialogHeader'
|
||||||
|
|
||||||
|
const AlertDialogFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
||||||
|
<div
|
||||||
|
className={cn('flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2', className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
AlertDialogFooter.displayName = 'AlertDialogFooter'
|
||||||
|
|
||||||
|
const AlertDialogTitle = React.forwardRef<
|
||||||
|
React.ComponentRef<typeof AlertDialogPrimitive.Title>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<AlertDialogPrimitive.Title
|
||||||
|
ref={ref}
|
||||||
|
className={cn('text-lg font-semibold', className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName
|
||||||
|
|
||||||
|
const AlertDialogDescription = React.forwardRef<
|
||||||
|
React.ComponentRef<typeof AlertDialogPrimitive.Description>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<AlertDialogPrimitive.Description
|
||||||
|
ref={ref}
|
||||||
|
className={cn('text-muted-foreground text-sm', className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
AlertDialogDescription.displayName = AlertDialogPrimitive.Description.displayName
|
||||||
|
|
||||||
|
const AlertDialogAction = React.forwardRef<
|
||||||
|
React.ComponentRef<typeof AlertDialogPrimitive.Action>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<AlertDialogPrimitive.Action ref={ref} className={cn(buttonVariants(), className)} {...props} />
|
||||||
|
))
|
||||||
|
AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName
|
||||||
|
|
||||||
|
const AlertDialogCancel = React.forwardRef<
|
||||||
|
React.ComponentRef<typeof AlertDialogPrimitive.Cancel>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<AlertDialogPrimitive.Cancel
|
||||||
|
ref={ref}
|
||||||
|
className={cn(buttonVariants({ variant: 'outline' }), 'mt-2 sm:mt-0', className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName
|
||||||
|
|
||||||
|
export {
|
||||||
|
AlertDialog,
|
||||||
|
AlertDialogPortal,
|
||||||
|
AlertDialogOverlay,
|
||||||
|
AlertDialogTrigger,
|
||||||
|
AlertDialogContent,
|
||||||
|
AlertDialogHeader,
|
||||||
|
AlertDialogFooter,
|
||||||
|
AlertDialogTitle,
|
||||||
|
AlertDialogDescription,
|
||||||
|
AlertDialogAction,
|
||||||
|
AlertDialogCancel
|
||||||
|
}
|
@@ -4,7 +4,8 @@ import { cva, type VariantProps } from 'class-variance-authority'
|
|||||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/Tooltip'
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/Tooltip'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
const buttonVariants = cva(
|
// eslint-disable-next-line react-refresh/only-export-components
|
||||||
|
export const buttonVariants = cva(
|
||||||
'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
|
'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
|
@@ -7,16 +7,16 @@ import { EdgeArrowProgram, NodePointProgram, NodeCircleProgram } from 'sigma/ren
|
|||||||
import { NodeBorderProgram } from '@sigma/node-border'
|
import { NodeBorderProgram } from '@sigma/node-border'
|
||||||
import EdgeCurveProgram, { EdgeCurvedArrowProgram } from '@sigma/edge-curve'
|
import EdgeCurveProgram, { EdgeCurvedArrowProgram } from '@sigma/edge-curve'
|
||||||
|
|
||||||
import FocusOnNode from '@/components/FocusOnNode'
|
import FocusOnNode from '@/components/graph/FocusOnNode'
|
||||||
import LayoutsControl from '@/components/LayoutsControl'
|
import LayoutsControl from '@/components/graph/LayoutsControl'
|
||||||
import GraphControl from '@/components/GraphControl'
|
import GraphControl from '@/components/graph/GraphControl'
|
||||||
// import ThemeToggle from '@/components/ThemeToggle'
|
// import ThemeToggle from '@/components/ThemeToggle'
|
||||||
import ZoomControl from '@/components/ZoomControl'
|
import ZoomControl from '@/components/graph/ZoomControl'
|
||||||
import FullScreenControl from '@/components/FullScreenControl'
|
import FullScreenControl from '@/components/graph/FullScreenControl'
|
||||||
import Settings from '@/components/Settings'
|
import Settings from '@/components/graph/Settings'
|
||||||
import GraphSearch from '@/components/GraphSearch'
|
import GraphSearch from '@/components/graph/GraphSearch'
|
||||||
import GraphLabels from '@/components/GraphLabels'
|
import GraphLabels from '@/components/graph/GraphLabels'
|
||||||
import PropertiesView from '@/components/PropertiesView'
|
import PropertiesView from '@/components/graph/PropertiesView'
|
||||||
|
|
||||||
import { useSettingsStore } from '@/stores/settings'
|
import { useSettingsStore } from '@/stores/settings'
|
||||||
import { useGraphStore } from '@/stores/graph'
|
import { useGraphStore } from '@/stores/graph'
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import Button from '@/components/ui/Button'
|
import Button from '@/components/ui/Button'
|
||||||
import { SiteInfo } from '@/lib/constants'
|
import { SiteInfo } from '@/lib/constants'
|
||||||
import ThemeToggle from '@/components/ThemeToggle'
|
import ThemeToggle from '@/components/graph/ThemeToggle'
|
||||||
import { TabsList, TabsTrigger } from '@/components/ui/Tabs'
|
import { TabsList, TabsTrigger } from '@/components/ui/Tabs'
|
||||||
import { useSettingsStore } from '@/stores/settings'
|
import { useSettingsStore } from '@/stores/settings'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { useContext } from 'react'
|
import { useContext } from 'react'
|
||||||
import { ThemeProviderContext } from '@/components/ThemeProvider'
|
import { ThemeProviderContext } from '@/components/graph/ThemeProvider'
|
||||||
|
|
||||||
const useTheme = () => {
|
const useTheme = () => {
|
||||||
const context = useContext(ThemeProviderContext)
|
const context = useContext(ThemeProviderContext)
|
||||||
|
Reference in New Issue
Block a user