Initial commit

This commit is contained in:
2025-10-14 14:17:21 +08:00
commit ac715a8b88
35011 changed files with 3834178 additions and 0 deletions

View File

@@ -0,0 +1,44 @@
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])
}

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

View File

@@ -0,0 +1,15 @@
import { useLanguage } from '@/app/components/header/account-setting/model-provider-page/hooks'
export const renderI18nObject = (obj: Record<string, string>, language: string) => {
if (!obj) return ''
if (obj?.[language]) return obj[language]
if (obj?.en_US) return obj.en_US
return Object.values(obj)[0]
}
export const useRenderI18nObject = () => {
const language = useLanguage()
return (obj: Record<string, string>) => {
return renderI18nObject(obj, language)
}
}

View File

@@ -0,0 +1,32 @@
import { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
export const useKnowledge = () => {
const { t } = useTranslation()
const formatIndexingTechnique = useCallback((indexingTechnique: string) => {
return t(`dataset.indexingTechnique.${indexingTechnique}`)
}, [t])
const formatIndexingMethod = useCallback((indexingMethod: string, isEco?: boolean) => {
if (isEco)
return t('dataset.indexingMethod.invertedIndex')
return t(`dataset.indexingMethod.${indexingMethod}`)
}, [t])
const formatIndexingTechniqueAndMethod = useCallback((indexingTechnique: string, indexingMethod: string) => {
let result = formatIndexingTechnique(indexingTechnique)
if (indexingMethod)
result += ` · ${formatIndexingMethod(indexingMethod, indexingTechnique === 'economy')}`
return result
}, [formatIndexingTechnique, formatIndexingMethod])
return {
formatIndexingTechnique,
formatIndexingMethod,
formatIndexingTechniqueAndMethod,
}
}

View File

@@ -0,0 +1,395 @@
'use client'
import { useTranslation } from 'react-i18next'
import { formatFileSize, formatNumber, formatTime } from '@/utils/format'
import { type DocType, ProcessMode } from '@/models/datasets'
import useTimestamp from '@/hooks/use-timestamp'
export type inputType = 'input' | 'select' | 'textarea'
export type metadataType = DocType | 'originInfo' | 'technicalParameters'
type MetadataMap =
Record<
metadataType,
{
text: string
allowEdit?: boolean
icon?: React.ReactNode
iconName?: string
subFieldsMap: Record<
string,
{
label: string
inputType?: inputType
field?: string
render?: (value: any, total?: number) => React.ReactNode | string
}
>
}
>
const fieldPrefix = 'datasetDocuments.metadata.field'
export const useMetadataMap = (): MetadataMap => {
const { t } = useTranslation()
const { formatTime: formatTimestamp } = useTimestamp()
return {
book: {
text: t('datasetDocuments.metadata.type.book'),
iconName: 'bookOpen',
subFieldsMap: {
title: { label: t(`${fieldPrefix}.book.title`) },
language: {
label: t(`${fieldPrefix}.book.language`),
inputType: 'select',
},
author: { label: t(`${fieldPrefix}.book.author`) },
publisher: { label: t(`${fieldPrefix}.book.publisher`) },
publication_date: { label: t(`${fieldPrefix}.book.publicationDate`) },
isbn: { label: t(`${fieldPrefix}.book.ISBN`) },
category: {
label: t(`${fieldPrefix}.book.category`),
inputType: 'select',
},
},
},
web_page: {
text: t('datasetDocuments.metadata.type.webPage'),
iconName: 'globe',
subFieldsMap: {
'title': { label: t(`${fieldPrefix}.webPage.title`) },
'url': { label: t(`${fieldPrefix}.webPage.url`) },
'language': {
label: t(`${fieldPrefix}.webPage.language`),
inputType: 'select',
},
'author/publisher': { label: t(`${fieldPrefix}.webPage.authorPublisher`) },
'publish_date': { label: t(`${fieldPrefix}.webPage.publishDate`) },
'topic/keywords': { label: t(`${fieldPrefix}.webPage.topicKeywords`) },
'description': { label: t(`${fieldPrefix}.webPage.description`) },
},
},
paper: {
text: t('datasetDocuments.metadata.type.paper'),
iconName: 'graduationHat',
subFieldsMap: {
'title': { label: t(`${fieldPrefix}.paper.title`) },
'language': {
label: t(`${fieldPrefix}.paper.language`),
inputType: 'select',
},
'author': { label: t(`${fieldPrefix}.paper.author`) },
'publish_date': { label: t(`${fieldPrefix}.paper.publishDate`) },
'journal/conference_name': {
label: t(`${fieldPrefix}.paper.journalConferenceName`),
},
'volume/issue/page_numbers': { label: t(`${fieldPrefix}.paper.volumeIssuePage`) },
'doi': { label: t(`${fieldPrefix}.paper.DOI`) },
'topic/keywords': { label: t(`${fieldPrefix}.paper.topicKeywords`) },
'abstract': {
label: t(`${fieldPrefix}.paper.abstract`),
inputType: 'textarea',
},
},
},
social_media_post: {
text: t('datasetDocuments.metadata.type.socialMediaPost'),
iconName: 'atSign',
subFieldsMap: {
'platform': { label: t(`${fieldPrefix}.socialMediaPost.platform`) },
'author/username': {
label: t(`${fieldPrefix}.socialMediaPost.authorUsername`),
},
'publish_date': { label: t(`${fieldPrefix}.socialMediaPost.publishDate`) },
'post_url': { label: t(`${fieldPrefix}.socialMediaPost.postURL`) },
'topics/tags': { label: t(`${fieldPrefix}.socialMediaPost.topicsTags`) },
},
},
personal_document: {
text: t('datasetDocuments.metadata.type.personalDocument'),
iconName: 'file',
subFieldsMap: {
'title': { label: t(`${fieldPrefix}.personalDocument.title`) },
'author': { label: t(`${fieldPrefix}.personalDocument.author`) },
'creation_date': {
label: t(`${fieldPrefix}.personalDocument.creationDate`),
},
'last_modified_date': {
label: t(`${fieldPrefix}.personalDocument.lastModifiedDate`),
},
'document_type': {
label: t(`${fieldPrefix}.personalDocument.documentType`),
inputType: 'select',
},
'tags/category': {
label: t(`${fieldPrefix}.personalDocument.tagsCategory`),
},
},
},
business_document: {
text: t('datasetDocuments.metadata.type.businessDocument'),
iconName: 'briefcase',
subFieldsMap: {
'title': { label: t(`${fieldPrefix}.businessDocument.title`) },
'author': { label: t(`${fieldPrefix}.businessDocument.author`) },
'creation_date': {
label: t(`${fieldPrefix}.businessDocument.creationDate`),
},
'last_modified_date': {
label: t(`${fieldPrefix}.businessDocument.lastModifiedDate`),
},
'document_type': {
label: t(`${fieldPrefix}.businessDocument.documentType`),
inputType: 'select',
},
'department/team': {
label: t(`${fieldPrefix}.businessDocument.departmentTeam`),
},
},
},
im_chat_log: {
text: t('datasetDocuments.metadata.type.IMChat'),
iconName: 'messageTextCircle',
subFieldsMap: {
'chat_platform': { label: t(`${fieldPrefix}.IMChat.chatPlatform`) },
'chat_participants/group_name': {
label: t(`${fieldPrefix}.IMChat.chatPartiesGroupName`),
},
'start_date': { label: t(`${fieldPrefix}.IMChat.startDate`) },
'end_date': { label: t(`${fieldPrefix}.IMChat.endDate`) },
'participants': { label: t(`${fieldPrefix}.IMChat.participants`) },
'topicKeywords': {
label: t(`${fieldPrefix}.IMChat.topicKeywords`),
inputType: 'textarea',
},
'fileType': { label: t(`${fieldPrefix}.IMChat.fileType`) },
},
},
wikipedia_entry: {
text: t('datasetDocuments.metadata.type.wikipediaEntry'),
allowEdit: false,
subFieldsMap: {
'title': { label: t(`${fieldPrefix}.wikipediaEntry.title`) },
'language': {
label: t(`${fieldPrefix}.wikipediaEntry.language`),
inputType: 'select',
},
'web_page_url': { label: t(`${fieldPrefix}.wikipediaEntry.webpageURL`) },
'editor/contributor': {
label: t(`${fieldPrefix}.wikipediaEntry.editorContributor`),
},
'last_edit_date': {
label: t(`${fieldPrefix}.wikipediaEntry.lastEditDate`),
},
'summary/introduction': {
label: t(`${fieldPrefix}.wikipediaEntry.summaryIntroduction`),
inputType: 'textarea',
},
},
},
synced_from_notion: {
text: t('datasetDocuments.metadata.type.notion'),
allowEdit: false,
subFieldsMap: {
'title': { label: t(`${fieldPrefix}.notion.title`) },
'language': { label: t(`${fieldPrefix}.notion.lang`), inputType: 'select' },
'author/creator': { label: t(`${fieldPrefix}.notion.author`) },
'creation_date': { label: t(`${fieldPrefix}.notion.createdTime`) },
'last_modified_date': {
label: t(`${fieldPrefix}.notion.lastModifiedTime`),
},
'notion_page_link': { label: t(`${fieldPrefix}.notion.url`) },
'category/tags': { label: t(`${fieldPrefix}.notion.tag`) },
'description': { label: t(`${fieldPrefix}.notion.desc`) },
},
},
synced_from_github: {
text: t('datasetDocuments.metadata.type.github'),
allowEdit: false,
subFieldsMap: {
'repository_name': { label: t(`${fieldPrefix}.github.repoName`) },
'repository_description': { label: t(`${fieldPrefix}.github.repoDesc`) },
'repository_owner/organization': { label: t(`${fieldPrefix}.github.repoOwner`) },
'code_filename': { label: t(`${fieldPrefix}.github.fileName`) },
'code_file_path': { label: t(`${fieldPrefix}.github.filePath`) },
'programming_language': { label: t(`${fieldPrefix}.github.programmingLang`) },
'github_link': { label: t(`${fieldPrefix}.github.url`) },
'open_source_license': { label: t(`${fieldPrefix}.github.license`) },
'commit_date': { label: t(`${fieldPrefix}.github.lastCommitTime`) },
'commit_author': {
label: t(`${fieldPrefix}.github.lastCommitAuthor`),
},
},
},
originInfo: {
text: '',
allowEdit: false,
subFieldsMap: {
'name': { label: t(`${fieldPrefix}.originInfo.originalFilename`) },
'data_source_info.upload_file.size': {
label: t(`${fieldPrefix}.originInfo.originalFileSize`),
render: value => formatFileSize(value),
},
'created_at': {
label: t(`${fieldPrefix}.originInfo.uploadDate`),
render: value => formatTimestamp(value, t('datasetDocuments.metadata.dateTimeFormat') as string),
},
'completed_at': {
label: t(`${fieldPrefix}.originInfo.lastUpdateDate`),
render: value => formatTimestamp(value, t('datasetDocuments.metadata.dateTimeFormat') as string),
},
'data_source_type': {
label: t(`${fieldPrefix}.originInfo.source`),
render: value => t(`datasetDocuments.metadata.source.${value}`),
},
},
},
technicalParameters: {
text: t('datasetDocuments.metadata.type.technicalParameters'),
allowEdit: false,
subFieldsMap: {
'dataset_process_rule.mode': {
label: t(`${fieldPrefix}.technicalParameters.segmentSpecification`),
render: value => value === ProcessMode.general ? (t('datasetDocuments.embedding.custom') as string) : (t('datasetDocuments.embedding.hierarchical') as string),
},
'dataset_process_rule.rules.segmentation.max_tokens': {
label: t(`${fieldPrefix}.technicalParameters.segmentLength`),
render: value => formatNumber(value),
},
'average_segment_length': {
label: t(`${fieldPrefix}.technicalParameters.avgParagraphLength`),
render: value => `${formatNumber(value)} characters`,
},
'segment_count': {
label: t(`${fieldPrefix}.technicalParameters.paragraphs`),
render: value => `${formatNumber(value)} paragraphs`,
},
'hit_count': {
label: t(`${fieldPrefix}.technicalParameters.hitCount`),
render: (value, total) => {
const v = value || 0
return `${!total ? 0 : ((v / total) * 100).toFixed(2)}% (${v}/${total})`
},
},
'indexing_latency': {
label: t(`${fieldPrefix}.technicalParameters.embeddingTime`),
render: value => formatTime(value),
},
'tokens': {
label: t(`${fieldPrefix}.technicalParameters.embeddedSpend`),
render: value => `${formatNumber(value)} tokens`,
},
},
},
}
}
const langPrefix = 'datasetDocuments.metadata.languageMap.'
export const useLanguages = () => {
const { t } = useTranslation()
return {
zh: t(`${langPrefix}zh`),
en: t(`${langPrefix}en`),
es: t(`${langPrefix}es`),
fr: t(`${langPrefix}fr`),
de: t(`${langPrefix}de`),
ja: t(`${langPrefix}ja`),
ko: t(`${langPrefix}ko`),
ru: t(`${langPrefix}ru`),
ar: t(`${langPrefix}ar`),
pt: t(`${langPrefix}pt`),
it: t(`${langPrefix}it`),
nl: t(`${langPrefix}nl`),
pl: t(`${langPrefix}pl`),
sv: t(`${langPrefix}sv`),
tr: t(`${langPrefix}tr`),
he: t(`${langPrefix}he`),
hi: t(`${langPrefix}hi`),
da: t(`${langPrefix}da`),
fi: t(`${langPrefix}fi`),
no: t(`${langPrefix}no`),
hu: t(`${langPrefix}hu`),
el: t(`${langPrefix}el`),
cs: t(`${langPrefix}cs`),
th: t(`${langPrefix}th`),
id: t(`${langPrefix}id`),
ro: t(`${langPrefix}ro`),
}
}
const bookCategoryPrefix = 'datasetDocuments.metadata.categoryMap.book.'
export const useBookCategories = () => {
const { t } = useTranslation()
return {
fiction: t(`${bookCategoryPrefix}fiction`),
biography: t(`${bookCategoryPrefix}biography`),
history: t(`${bookCategoryPrefix}history`),
science: t(`${bookCategoryPrefix}science`),
technology: t(`${bookCategoryPrefix}technology`),
education: t(`${bookCategoryPrefix}education`),
philosophy: t(`${bookCategoryPrefix}philosophy`),
religion: t(`${bookCategoryPrefix}religion`),
socialSciences: t(`${bookCategoryPrefix}socialSciences`),
art: t(`${bookCategoryPrefix}art`),
travel: t(`${bookCategoryPrefix}travel`),
health: t(`${bookCategoryPrefix}health`),
selfHelp: t(`${bookCategoryPrefix}selfHelp`),
businessEconomics: t(`${bookCategoryPrefix}businessEconomics`),
cooking: t(`${bookCategoryPrefix}cooking`),
childrenYoungAdults: t(`${bookCategoryPrefix}childrenYoungAdults`),
comicsGraphicNovels: t(`${bookCategoryPrefix}comicsGraphicNovels`),
poetry: t(`${bookCategoryPrefix}poetry`),
drama: t(`${bookCategoryPrefix}drama`),
other: t(`${bookCategoryPrefix}other`),
}
}
const personalDocCategoryPrefix
= 'datasetDocuments.metadata.categoryMap.personalDoc.'
export const usePersonalDocCategories = () => {
const { t } = useTranslation()
return {
notes: t(`${personalDocCategoryPrefix}notes`),
blogDraft: t(`${personalDocCategoryPrefix}blogDraft`),
diary: t(`${personalDocCategoryPrefix}diary`),
researchReport: t(`${personalDocCategoryPrefix}researchReport`),
bookExcerpt: t(`${personalDocCategoryPrefix}bookExcerpt`),
schedule: t(`${personalDocCategoryPrefix}schedule`),
list: t(`${personalDocCategoryPrefix}list`),
projectOverview: t(`${personalDocCategoryPrefix}projectOverview`),
photoCollection: t(`${personalDocCategoryPrefix}photoCollection`),
creativeWriting: t(`${personalDocCategoryPrefix}creativeWriting`),
codeSnippet: t(`${personalDocCategoryPrefix}codeSnippet`),
designDraft: t(`${personalDocCategoryPrefix}designDraft`),
personalResume: t(`${personalDocCategoryPrefix}personalResume`),
other: t(`${personalDocCategoryPrefix}other`),
}
}
const businessDocCategoryPrefix
= 'datasetDocuments.metadata.categoryMap.businessDoc.'
export const useBusinessDocCategories = () => {
const { t } = useTranslation()
return {
meetingMinutes: t(`${businessDocCategoryPrefix}meetingMinutes`),
researchReport: t(`${businessDocCategoryPrefix}researchReport`),
proposal: t(`${businessDocCategoryPrefix}proposal`),
employeeHandbook: t(`${businessDocCategoryPrefix}employeeHandbook`),
trainingMaterials: t(`${businessDocCategoryPrefix}trainingMaterials`),
requirementsDocument: t(`${businessDocCategoryPrefix}requirementsDocument`),
designDocument: t(`${businessDocCategoryPrefix}designDocument`),
productSpecification: t(`${businessDocCategoryPrefix}productSpecification`),
financialReport: t(`${businessDocCategoryPrefix}financialReport`),
marketAnalysis: t(`${businessDocCategoryPrefix}marketAnalysis`),
projectPlan: t(`${businessDocCategoryPrefix}projectPlan`),
teamStructure: t(`${businessDocCategoryPrefix}teamStructure`),
policiesProcedures: t(`${businessDocCategoryPrefix}policiesProcedures`),
contractsAgreements: t(`${businessDocCategoryPrefix}contractsAgreements`),
emailCorrespondence: t(`${businessDocCategoryPrefix}emailCorrespondence`),
other: t(`${businessDocCategoryPrefix}other`),
}
}

View File

@@ -0,0 +1,74 @@
import type { Emitter, EventType, Handler, WildcardHandler } from 'mitt'
import create from 'mitt'
import { useEffect, useRef } from 'react'
const merge = <T extends Record<string, any>>(
...args: Array<T | undefined>
): T => {
return Object.assign({}, ...args)
}
export type _Events = Record<EventType, unknown>
export type UseSubcribeOption = {
/**
* Whether the subscription is enabled.
* @default true
*/
enabled: boolean;
}
export type ExtendedOn<Events extends _Events> = {
<Key extends keyof Events>(
type: Key,
handler: Handler<Events[Key]>,
options?: UseSubcribeOption,
): void;
(
type: '*',
handler: WildcardHandler<Events>,
option?: UseSubcribeOption,
): void;
}
export type UseMittReturn<Events extends _Events> = {
useSubcribe: ExtendedOn<Events>;
emit: Emitter<Events>['emit'];
}
const defaultSubcribeOption: UseSubcribeOption = {
enabled: true,
}
function useMitt<Events extends _Events>(
mitt?: Emitter<Events>,
): UseMittReturn<Events> {
const emitterRef = useRef<Emitter<Events>>()
if (!emitterRef.current)
emitterRef.current = mitt ?? create<Events>()
if (mitt && emitterRef.current !== mitt) {
emitterRef.current.off('*')
emitterRef.current = mitt
}
const emitter = emitterRef.current
const useSubcribe: ExtendedOn<Events> = (
type: string,
handler: any,
option?: UseSubcribeOption,
) => {
const { enabled } = merge(defaultSubcribeOption, option)
useEffect(() => {
if (enabled) {
emitter.on(type, handler)
return () => emitter.off(type, handler)
}
})
}
return {
emit: emitter.emit,
useSubcribe,
}
}
export { useMitt }

View File

@@ -0,0 +1,49 @@
import { useEffect, useRef, useState } from 'react'
import type { ModerationService } from '@/models/common'
function splitStringByLength(inputString: string, chunkLength: number) {
const resultArray = []
for (let i = 0; i < inputString.length; i += chunkLength)
resultArray.push(inputString.substring(i, i + chunkLength))
return resultArray
}
export const useModerate = (
content: string,
stop: boolean,
moderationService: (text: string) => ReturnType<ModerationService>,
separateLength = 50,
) => {
const moderatedContentMap = useRef<Map<number, string>>(new Map())
const moderatingIndex = useRef<number[]>([])
const [contentArr, setContentArr] = useState<string[]>([])
const handleModerate = () => {
const stringArr = splitStringByLength(content, separateLength)
const lastIndex = stringArr.length - 1
stringArr.forEach((item, index) => {
if (!(index in moderatingIndex.current) && !moderatedContentMap.current.get(index)) {
if (index === lastIndex && !stop)
return
moderatingIndex.current.push(index)
moderationService(item).then((res) => {
if (res.flagged) {
moderatedContentMap.current.set(index, res.text)
setContentArr([...stringArr.slice(0, index), res.text, ...stringArr.slice(index + 1)])
}
})
}
})
setContentArr(stringArr)
}
useEffect(() => {
if (content)
handleModerate()
}, [content, stop])
return contentArr.map((item, index) => moderatedContentMap.current.get(index) || item).join('')
}

View File

@@ -0,0 +1,119 @@
'use client'
import { useCallback, useEffect, useState } from 'react'
import { useRouter, useSearchParams } from 'next/navigation'
import { useTranslation } from 'react-i18next'
import useSWR from 'swr'
import {
fetchDataSourceNotionBinding,
} from '@/service/common'
import type { IConfirm } from '@/app/components/base/confirm'
import Confirm from '@/app/components/base/confirm'
export type ConfirmType = Pick<IConfirm, 'type' | 'title' | 'content'>
export const useAnthropicCheckPay = () => {
const { t } = useTranslation()
const [confirm, setConfirm] = useState<ConfirmType | null>(null)
const searchParams = useSearchParams()
const providerName = searchParams.get('provider_name')
const paymentResult = searchParams.get('payment_result')
useEffect(() => {
if (providerName === 'anthropic' && (paymentResult === 'succeeded' || paymentResult === 'cancelled')) {
setConfirm({
type: paymentResult === 'succeeded' ? 'info' : 'warning',
title: paymentResult === 'succeeded' ? t('common.actionMsg.paySucceeded') : t('common.actionMsg.payCancelled'),
})
}
}, [providerName, paymentResult, t])
return confirm
}
export const useBillingPay = () => {
const { t } = useTranslation()
const [confirm, setConfirm] = useState<ConfirmType | null>(null)
const searchParams = useSearchParams()
const paymentType = searchParams.get('payment_type')
const paymentResult = searchParams.get('payment_result')
useEffect(() => {
if (paymentType === 'billing' && (paymentResult === 'succeeded' || paymentResult === 'cancelled')) {
setConfirm({
type: paymentResult === 'succeeded' ? 'info' : 'warning',
title: paymentResult === 'succeeded' ? t('common.actionMsg.paySucceeded') : t('common.actionMsg.payCancelled'),
})
}
}, [paymentType, paymentResult, t])
return confirm
}
export const useCheckNotion = () => {
const router = useRouter()
const [confirm, setConfirm] = useState<ConfirmType | null>(null)
const [canBinding, setCanBinding] = useState(false)
const searchParams = useSearchParams()
const type = searchParams.get('type')
const notionCode = searchParams.get('code')
const notionError = searchParams.get('error')
const { data } = useSWR(
(canBinding && notionCode)
? `/oauth/data-source/binding/notion?code=${notionCode}`
: null,
fetchDataSourceNotionBinding,
)
useEffect(() => {
if (data)
router.replace('/')
}, [data, router])
useEffect(() => {
if (type === 'notion') {
if (notionError) {
setConfirm({
type: 'warning',
title: notionError,
})
}
else if (notionCode) {
setCanBinding(true)
}
}
}, [type, notionCode, notionError])
return confirm
}
export const CheckModal = () => {
const router = useRouter()
const { t } = useTranslation()
const [showPayStatusModal, setShowPayStatusModal] = useState(true)
const anthropicConfirmInfo = useAnthropicCheckPay()
const notionConfirmInfo = useCheckNotion()
const billingConfirmInfo = useBillingPay()
const handleCancelShowPayStatusModal = useCallback(() => {
setShowPayStatusModal(false)
router.replace('/')
}, [router])
const confirmInfo = anthropicConfirmInfo || notionConfirmInfo || billingConfirmInfo
if (!confirmInfo || !showPayStatusModal)
return null
return (
<Confirm
isShow
onCancel={handleCancelShowPayStatusModal}
onConfirm={handleCancelShowPayStatusModal}
showCancel={false}
type={confirmInfo.type === 'info' ? 'info' : 'warning' }
title={confirmInfo.title}
content={(confirmInfo as unknown as { desc: string }).desc || ''}
confirmText={(confirmInfo.type === 'info' && t('common.operation.ok')) || ''}
/>
)
}

View File

@@ -0,0 +1,43 @@
import { usePathname, useSearchParams } from 'next/navigation'
import { useState } from 'react'
type UseTabSearchParamsOptions = {
defaultTab: string
routingBehavior?: 'push' | 'replace'
searchParamName?: string
disableSearchParams?: boolean
}
/**
* Custom hook to manage tab state via URL search parameters in a Next.js application.
* This hook allows for syncing the active tab with the browser's URL, enabling bookmarking and sharing of URLs with a specific tab activated.
*
* @param {UseTabSearchParamsOptions} options Configuration options for the hook:
* - `defaultTab`: The tab to default to when no tab is specified in the URL.
* - `routingBehavior`: Optional. Determines how changes to the active tab update the browser's history ('push' or 'replace'). Default is 'push'.
* - `searchParamName`: Optional. The name of the search parameter that holds the tab state in the URL. Default is 'category'.
* @returns A tuple where the first element is the active tab and the second element is a function to set the active tab.
*/
export const useTabSearchParams = ({
defaultTab,
routingBehavior = 'push',
searchParamName = 'category',
disableSearchParams = false,
}: UseTabSearchParamsOptions) => {
const pathName = usePathname()
const searchParams = useSearchParams()
const [activeTab, setTab] = useState<string>(
!disableSearchParams
? (searchParams.get(searchParamName) || defaultTab)
: defaultTab,
)
const setActiveTab = (newActiveTab: string) => {
setTab(newActiveTab)
if (disableSearchParams)
return
history[`${routingBehavior}State`](null, '', `${pathName}?${searchParamName}=${newActiveTab}`)
}
return [activeTab, setActiveTab] as const
}

View File

@@ -0,0 +1,13 @@
import { Theme } from '@/types/app'
import { useTheme as useBaseTheme } from 'next-themes'
const useTheme = () => {
const { theme, resolvedTheme, ...rest } = useBaseTheme()
return {
// only returns 'light' or 'dark' theme
theme: theme === Theme.system ? resolvedTheme as Theme : theme as Theme,
...rest,
}
}
export default useTheme

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