Initial commit

This commit is contained in:
饺子大魔王
2025-04-11 08:14:27 +08:00
commit 626af60456
1540 changed files with 110511 additions and 0 deletions
+43
View File
@@ -0,0 +1,43 @@
import { useAsyncEffect } from 'ahooks'
import { appDefaultIconBackground } from '@/config'
import { searchEmoji } from '@/utils/emoji'
import type { AppIconType } from '@/types/app'
type UseAppFaviconOptions = {
enable?: boolean
icon_type?: AppIconType | null
icon?: string
icon_background?: string | null
icon_url?: string | null
}
export function useAppFavicon(options: UseAppFaviconOptions) {
const {
enable = true,
icon_type = 'emoji',
icon,
icon_background,
icon_url,
} = options
useAsyncEffect(async () => {
if (!enable || (icon_type === 'image' && !icon_url) || (icon_type === 'emoji' && !icon))
return
const isValidImageIcon = icon_type === 'image' && icon_url
const link: HTMLLinkElement = document.querySelector('link[rel*="icon"]') || document.createElement('link')
link.href = isValidImageIcon
? icon_url
: 'data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22>'
+ `<rect width=%22100%25%22 height=%22100%25%22 fill=%22${encodeURIComponent(icon_background || appDefaultIconBackground)}%22 rx=%2230%22 ry=%2230%22 />`
+ `<text x=%2212.5%22 y=%221em%22 font-size=%2275%22>${icon ? await searchEmoji(icon) : '🤖'
}</text>`
+ '</svg>'
link.rel = 'shortcut icon'
link.type = 'image/svg'
document.getElementsByTagName('head')[0].appendChild(link)
}, [enable, icon, icon_background])
}
+29
View File
@@ -0,0 +1,29 @@
'use client'
import React from 'react'
export enum MediaType {
mobile = 'mobile',
tablet = 'tablet',
pc = 'pc',
}
const useBreakpoints = () => {
const [width, setWidth] = React.useState(globalThis.innerWidth)
const media = (() => {
if (width <= 640)
return MediaType.mobile
if (width <= 768)
return MediaType.tablet
return MediaType.pc
})()
React.useEffect(() => {
const handleWindowResize = () => setWidth(window.innerWidth)
window.addEventListener('resize', handleWindowResize)
return () => window.removeEventListener('resize', handleWindowResize)
}, [])
return media
}
export default useBreakpoints
+69
View File
@@ -0,0 +1,69 @@
import { useState } from 'react'
import { produce } from 'immer'
import { useGetState } from 'ahooks'
import type { ConversationItem } from '@/types/app'
const storageConversationIdKey = 'conversationIdInfo'
type ConversationInfoType = Omit<ConversationItem, 'inputs' | 'id'>
function useConversation() {
const [conversationList, setConversationList] = useState<ConversationItem[]>([])
const [currConversationId, doSetCurrConversationId, getCurrConversationId] = useGetState<string>('-1')
// when set conversation id, we do not have set appId
const setCurrConversationId = (id: string, appId: string, isSetToLocalStroge = true, newConversationName = '') => {
doSetCurrConversationId(id)
if (isSetToLocalStroge && id !== '-1') {
// conversationIdInfo: {[appId1]: conversationId1, [appId2]: conversationId2}
const conversationIdInfo = globalThis.localStorage?.getItem(storageConversationIdKey) ? JSON.parse(globalThis.localStorage?.getItem(storageConversationIdKey) || '') : {}
conversationIdInfo[appId] = id
globalThis.localStorage?.setItem(storageConversationIdKey, JSON.stringify(conversationIdInfo))
}
}
const getConversationIdFromStorage = (appId: string) => {
const conversationIdInfo = globalThis.localStorage?.getItem(storageConversationIdKey) ? JSON.parse(globalThis.localStorage?.getItem(storageConversationIdKey) || '') : {}
const id = conversationIdInfo[appId]
return id
}
const isNewConversation = currConversationId === '-1'
// input can be updated by user
const [newConversationInputs, setNewConversationInputs] = useState<Record<string, any> | null>(null)
const resetNewConversationInputs = () => {
if (!newConversationInputs)
return
setNewConversationInputs(produce(newConversationInputs, (draft) => {
Object.keys(draft).forEach((key) => {
draft[key] = ''
})
}))
}
const [existConversationInputs, setExistConversationInputs] = useState<Record<string, any> | null>(null)
const currInputs = isNewConversation ? newConversationInputs : existConversationInputs
const setCurrInputs = isNewConversation ? setNewConversationInputs : setExistConversationInputs
// info is muted
const [newConversationInfo, setNewConversationInfo] = useState<ConversationInfoType | null>(null)
const [existConversationInfo, setExistConversationInfo] = useState<ConversationInfoType | null>(null)
const currConversationInfo = isNewConversation ? newConversationInfo : existConversationInfo
return {
conversationList,
setConversationList,
currConversationId,
getCurrConversationId,
setCurrConversationId,
getConversationIdFromStorage,
isNewConversation,
currInputs,
newConversationInputs,
existConversationInputs,
resetNewConversationInputs,
setCurrInputs,
currConversationInfo,
setNewConversationInfo,
setExistConversationInfo,
}
}
export default useConversation
+25
View File
@@ -0,0 +1,25 @@
'use client'
import { useCallback } from 'react'
import dayjs from 'dayjs'
import utc from 'dayjs/plugin/utc'
import timezone from 'dayjs/plugin/timezone'
import { useAppContext } from '@/context/app-context'
dayjs.extend(utc)
dayjs.extend(timezone)
const useTimestamp = () => {
const { userProfile: { timezone } } = useAppContext()
const formatTime = useCallback((value: number, format: string) => {
return dayjs.unix(value).tz(timezone).format(format)
}, [timezone])
const formatDate = useCallback((value: string, format: string) => {
return dayjs(value).tz(timezone).format(format)
}, [timezone])
return { formatTime, formatDate }
}
export default useTimestamp