feat: 新版webui

This commit is contained in:
bietiaop
2025-01-24 21:13:44 +08:00
parent 1d0d25eea2
commit ee1291e42c
201 changed files with 18454 additions and 3422 deletions

View File

@@ -0,0 +1,15 @@
import { useLocalStorage } from '@uidotdev/usehooks'
import key from '@/const/key'
const useAuth = () => {
const [token, setToken] = useLocalStorage<string>(key.token, '')
return {
token,
isAuth: !!token,
revokeAuth: () => setToken('')
}
}
export default useAuth

View File

@@ -0,0 +1,187 @@
import { updateConfig as storeUpdateConfig } from '@/store/modules/config'
import { deepClone } from '@/utils/object'
import QQManager from '@/controllers/qq_manager'
import { useAppDispatch, useAppSelector } from './use-store'
const useConfig = () => {
const config = useAppSelector((state) => state.config.value)
const dispatch = useAppDispatch()
const createNetworkConfig = async <T extends keyof OneBotConfig['network']>(
key: T,
value: OneBotConfig['network'][T][0]
) => {
if (
value.name &&
config.network[key].some((item) => item.name === value.name)
) {
throw new Error('已经存在相同的配置项名')
}
const newConfig = deepClone(config)
;(newConfig.network[key] as (typeof value)[]).push(value)
await QQManager.setOB11Config(newConfig)
dispatch(storeUpdateConfig(newConfig))
return newConfig
}
const updateNetworkConfig = async <T extends keyof OneBotConfig['network']>(
key: T,
value: OneBotConfig['network'][T][0]
) => {
const newConfig = deepClone(config)
const name = value.name
const index = newConfig.network[key].findIndex((item) => item.name === name)
if (index === -1) {
throw new Error('找不到对应的配置项')
}
newConfig.network[key][index] = value
await QQManager.setOB11Config(newConfig)
dispatch(storeUpdateConfig(newConfig))
return newConfig
}
const deleteNetworkConfig = async <T extends keyof OneBotConfig['network']>(
key: T,
name: string
) => {
const newConfig = deepClone(config)
const index = newConfig.network[key].findIndex((item) => item.name === name)
if (index === -1) {
throw new Error('找不到对应的配置项')
}
newConfig.network[key].splice(index, 1)
await QQManager.setOB11Config(newConfig)
dispatch(storeUpdateConfig(newConfig))
return newConfig
}
const enableNetworkConfig = async <T extends keyof OneBotConfig['network']>(
key: T,
name: string
) => {
const newConfig = deepClone(config)
const index = newConfig.network[key].findIndex((item) => item.name === name)
if (index === -1) {
throw new Error('找不到对应的配置项')
}
newConfig.network[key][index].enable = !newConfig.network[key][index].enable
await QQManager.setOB11Config(newConfig)
dispatch(storeUpdateConfig(newConfig))
return newConfig
}
const enableDebugNetworkConfig = async <
T extends keyof OneBotConfig['network']
>(
key: T,
name: string
) => {
const newConfig = deepClone(config)
const index = newConfig.network[key].findIndex((item) => item.name === name)
if (index === -1) {
throw new Error('找不到对应的配置项')
}
newConfig.network[key][index].debug = !newConfig.network[key][index].debug
await QQManager.setOB11Config(newConfig)
dispatch(storeUpdateConfig(newConfig))
return newConfig
}
const updateSingleConfig = async <T extends keyof OneBotConfig>(
key: T,
value: OneBotConfig[T]
) => {
const newConfig = deepClone(config)
newConfig[key] = value
await QQManager.setOB11Config(newConfig)
dispatch(storeUpdateConfig(newConfig))
return newConfig
}
const updateConfig = async (newConfig: OneBotConfig) => {
await QQManager.setOB11Config(newConfig)
dispatch(storeUpdateConfig(newConfig))
return newConfig
}
const refreshConfig = async () => {
const newConfig = await QQManager.getOB11Config()
if (JSON.stringify(newConfig) === JSON.stringify(config)) {
return config
}
dispatch(storeUpdateConfig(newConfig))
return newConfig
}
const mergeConfig = async (newConfig: OneBotConfig) => {
const mergedConfig = deepClone(config)
Object.assign(mergedConfig, newConfig)
await QQManager.setOB11Config(mergedConfig)
dispatch(storeUpdateConfig(mergedConfig))
return mergedConfig
}
const saveConfigWithoutNetwork = async (newConfig: OneBotConfig) => {
newConfig.network = config.network
await QQManager.setOB11Config(newConfig)
dispatch(storeUpdateConfig(newConfig))
return newConfig
}
return {
config,
createNetworkConfig,
refreshConfig,
updateConfig,
updateSingleConfig,
updateNetworkConfig,
deleteNetworkConfig,
enableNetworkConfig,
enableDebugNetworkConfig,
mergeConfig,
saveConfigWithoutNetwork
}
}
export default useConfig

View File

@@ -0,0 +1,11 @@
import React from 'react'
import { DialogContext } from '@/contexts/dialog'
const useDialog = () => {
const dialog = React.useContext(DialogContext)
return dialog
}
export default useDialog

View File

@@ -0,0 +1,11 @@
import React from 'react'
import { AudioContext } from '@/contexts/songs'
const useMusic = () => {
const music = React.useContext(AudioContext)
return music
}
export default useMusic

View File

@@ -0,0 +1,6 @@
import { useDispatch, useSelector } from 'react-redux'
import type { AppDispatch, RootState } from '@/store'
export const useAppDispatch = useDispatch.withTypes<AppDispatch>()
export const useAppSelector = useSelector.withTypes<RootState>()

View File

@@ -0,0 +1,42 @@
// originally written by @imoaazahmed
import { useLocalStorage } from '@uidotdev/usehooks'
import { useEffect, useMemo } from 'react'
const ThemeProps = {
key: 'theme',
light: 'light',
dark: 'dark'
} as const
type Theme = typeof ThemeProps.light | typeof ThemeProps.dark
export const useTheme = (defaultTheme?: Theme) => {
const [theme, setTheme] = useLocalStorage<Theme>(ThemeProps.key, defaultTheme)
const isDark = useMemo(() => {
return theme === ThemeProps.dark
}, [theme])
const isLight = useMemo(() => {
return theme === ThemeProps.light
}, [theme])
const _setTheme = (theme: Theme) => {
setTheme(theme)
document.documentElement.classList.remove(ThemeProps.light, ThemeProps.dark)
document.documentElement.classList.add(theme)
}
const setLightTheme = () => _setTheme(ThemeProps.light)
const setDarkTheme = () => _setTheme(ThemeProps.dark)
const toggleTheme = () =>
theme === ThemeProps.dark ? setLightTheme() : setDarkTheme()
useEffect(() => {
_setTheme(theme)
})
return { theme, isDark, isLight, setLightTheme, setDarkTheme, toggleTheme }
}

View File

@@ -0,0 +1,67 @@
import type { Selection } from '@react-types/shared'
import { useReactive } from 'ahooks'
import { useCallback, useState } from 'react'
import toast from 'react-hot-toast'
import useWebSocket from 'react-use-websocket'
import { ReadyState } from 'react-use-websocket'
import { renderFilterMessageType } from '@/components/onebot/filter_message_type'
import { isOB11Event, isOB11RequestResponse } from '@/utils/onebot'
import type { AllOB11WsResponse } from '@/types/onebot'
export { ReadyState } from 'react-use-websocket'
export function useWebSocketDebug(url: string, token: string) {
const messageHistory = useReactive<AllOB11WsResponse[]>([])
const [filterTypes, setFilterTypes] = useState<Selection>('all')
const filteredMessages = messageHistory.filter((msg) => {
if (filterTypes === 'all' || filterTypes.size === 0) return true
if (isOB11Event(msg)) return filterTypes.has(msg.post_type)
if (isOB11RequestResponse(msg)) return filterTypes.has('request')
})
const { sendMessage, readyState } = useWebSocket(url, {
onMessage: useCallback((event: WebSocketEventMap['message']) => {
try {
const data = JSON.parse(event.data)
messageHistory.unshift(data)
} catch (error) {
toast.error('WebSocket 消息解析失败')
}
}, []),
queryParams: {
access_token: token
},
onError: (event) => {
toast.error('WebSocket 连接失败')
console.error('WebSocket error:', event)
},
onOpen: () => {
messageHistory.splice(0, messageHistory.length)
}
})
const _sendMessage = (msg: string) => {
if (readyState !== ReadyState.OPEN) {
throw new Error('WebSocket 连接未建立')
}
sendMessage(msg)
}
const FilterMessagesType = renderFilterMessageType(
filterTypes,
setFilterTypes
)
return {
sendMessage: _sendMessage,
readyState,
messageHistory,
filteredMessages,
filterTypes,
setFilterTypes,
FilterMessagesType
}
}

View File

@@ -0,0 +1,31 @@
import Quill from 'quill'
import 'quill/dist/quill.core.css'
import { useEffect, useRef, useState } from 'react'
interface UseCustomQuillProps {
modules: Record<string, unknown>
formats: string[]
placeholder: string
}
export const useCustomQuill = ({
modules,
formats,
placeholder
}: UseCustomQuillProps) => {
const quillRef = useRef<HTMLDivElement | null>(null)
const [quill, setQuill] = useState<Quill | null>(null)
useEffect(() => {
if (quillRef.current) {
const quillInstance = new Quill(quillRef.current, {
modules,
formats,
placeholder
})
setQuill(quillInstance)
}
}, [])
return { quillRef, quill, Quill }
}

View File

@@ -0,0 +1,25 @@
import { createElement } from 'react'
import ShowStructedMessage from '@/components/chat_input/components/show_structed_message'
import { OB11Segment } from '@/types/onebot'
import useDialog from './use-dialog'
const useShowStructuredMessage = () => {
const dialog = useDialog()
const showStructuredMessage = (messages: OB11Segment[]) => {
dialog.alert({
title: '消息内容',
size: '3xl',
content: createElement(ShowStructedMessage, {
messages: messages
})
})
}
return showStructuredMessage
}
export default useShowStructuredMessage