Feat: frontend support timezone of timestamp (#4070)

This commit is contained in:
KVOJJJin 2024-05-04 16:15:32 +08:00 committed by GitHub
parent f68b6b0e5e
commit c0476c7881
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 88 additions and 58 deletions

View File

@ -2,7 +2,6 @@
import type { FC } from 'react'
import React, { useState } from 'react'
import { useTranslation } from 'react-i18next'
import dayjs from 'dayjs'
import EditItem, { EditItemType } from './edit-item'
import Drawer from '@/app/components/base/drawer-plus'
import { MessageCheckRemove } from '@/app/components/base/icons/src/vender/line/communication'
@ -11,6 +10,8 @@ import { addAnnotation, editAnnotation } from '@/service/annotation'
import Toast from '@/app/components/base/toast'
import { useProviderContext } from '@/context/provider-context'
import AnnotationFull from '@/app/components/billing/annotation-full'
import useTimestamp from '@/hooks/use-timestamp'
type Props = {
isShow: boolean
onHide: () => void
@ -41,6 +42,7 @@ const EditAnnotationModal: FC<Props> = ({
onlyEditResponse,
}) => {
const { t } = useTranslation()
const { formatTime } = useTimestamp()
const { plan, enableBilling } = useProviderContext()
const isAdd = !annotationId
const isAnnotationFull = (enableBilling && plan.usage.annotatedResponse >= plan.total.annotatedResponse)
@ -117,15 +119,14 @@ const EditAnnotationModal: FC<Props> = ({
<MessageCheckRemove />
<div>{t('appAnnotation.editModal.removeThisCache')}</div>
</div>
{createdAt && <div>{t('appAnnotation.editModal.createdAt')}&nbsp;{dayjs(createdAt * 1000).format('YYYY-MM-DD HH:mm')}</div>}
{createdAt && <div>{t('appAnnotation.editModal.createdAt')}&nbsp;{formatTime(createdAt, t('appLog.dateTimeFormat') as string)}</div>}
</div>
)
: undefined
}
</div>
}
>
</Drawer>
/>
<DeleteConfirmModal
isShow={showModal}
onHide={() => setShowModal(false)}

View File

@ -3,11 +3,11 @@ import type { FC } from 'react'
import React from 'react'
import { useTranslation } from 'react-i18next'
import cn from 'classnames'
import dayjs from 'dayjs'
import { Edit02, Trash03 } from '../../base/icons/src/vender/line/general'
import s from './style.module.css'
import type { AnnotationItem } from './type'
import RemoveAnnotationConfirmModal from './remove-annotation-confirm-modal'
import useTimestamp from '@/hooks/use-timestamp'
type Props = {
list: AnnotationItem[]
@ -21,6 +21,7 @@ const List: FC<Props> = ({
onRemove,
}) => {
const { t } = useTranslation()
const { formatTime } = useTimestamp()
const [currId, setCurrId] = React.useState<string | null>(null)
const [showConfirmDelete, setShowConfirmDelete] = React.useState(false)
return (
@ -54,7 +55,7 @@ const List: FC<Props> = ({
className='whitespace-nowrap overflow-hidden text-ellipsis max-w-[250px]'
title={item.answer}
>{item.answer}</td>
<td>{dayjs(item.created_at * 1000).format('YYYY-MM-DD HH:mm')}</td>
<td>{formatTime(item.created_at, t('appLog.dateTimeFormat') as string)}</td>
<td>{item.hit_count}</td>
<td className='w-[96px]' onClick={e => e.stopPropagation()}>
{/* Actions */}

View File

@ -3,7 +3,6 @@ import type { FC } from 'react'
import React, { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import cn from 'classnames'
import dayjs from 'dayjs'
import { Pagination } from 'react-headless-pagination'
import { ArrowLeftIcon, ArrowRightIcon } from '@heroicons/react/24/outline'
import EditItem, { EditItemType } from '../edit-annotation-modal/edit-item'
@ -16,6 +15,7 @@ import DeleteConfirmModal from '@/app/components/base/modal/delete-confirm-modal
import TabSlider from '@/app/components/base/tab-slider-plain'
import { fetchHitHistoryList } from '@/service/annotation'
import { APP_PAGE_LIMIT } from '@/config'
import useTimestamp from '@/hooks/use-timestamp'
type Props = {
appId: string
@ -43,6 +43,7 @@ const ViewAnnotationModal: FC<Props> = ({
const [newQuestion, setNewQuery] = useState(question)
const [newAnswer, setNewAnswer] = useState(answer)
const { t } = useTranslation()
const { formatTime } = useTimestamp()
const [currPage, setCurrPage] = React.useState<number>(0)
const [total, setTotal] = useState(0)
const [hitHistoryList, setHitHistoryList] = useState<HitHistoryItem[]>([])
@ -119,7 +120,7 @@ const ViewAnnotationModal: FC<Props> = ({
<td className='whitespace-nowrap'>{t('appAnnotation.hitHistoryTable.response')}</td>
<td className='whitespace-nowrap'>{t('appAnnotation.hitHistoryTable.source')}</td>
<td className='whitespace-nowrap'>{t('appAnnotation.hitHistoryTable.score')}</td>
<td className='whitespace-nowrap w-[140px]'>{t('appAnnotation.hitHistoryTable.time')}</td>
<td className='whitespace-nowrap w-[160px]'>{t('appAnnotation.hitHistoryTable.time')}</td>
</tr>
</thead>
<tbody className="text-gray-500">
@ -142,7 +143,7 @@ const ViewAnnotationModal: FC<Props> = ({
>{item.response}</td>
<td>{item.source}</td>
<td>{item.score ? item.score.toFixed(2) : '-'}</td>
<td>{dayjs(item.created_at * 1000).format('YYYY-MM-DD HH:mm')}</td>
<td>{formatTime(item.created_at, t('appLog.dateTimeFormat') as string)}</td>
</tr>
))}
</tbody>
@ -214,12 +215,11 @@ const ViewAnnotationModal: FC<Props> = ({
<MessageCheckRemove />
<div>{t('appAnnotation.editModal.removeThisCache')}</div>
</div>
<div>{t('appAnnotation.editModal.createdAt')}&nbsp;{dayjs(createdAt * 1000).format('YYYY-MM-DD HH:mm')}</div>
<div>{t('appAnnotation.editModal.createdAt')}&nbsp;{formatTime(createdAt, t('appLog.dateTimeFormat') as string)}</div>
</div>
)
: undefined}
>
</Drawer>
/>
<DeleteConfirmModal
isShow={showModal}
onHide={() => setShowModal(false)}

View File

@ -11,6 +11,8 @@ import {
import { get } from 'lodash-es'
import InfiniteScroll from 'react-infinite-scroll-component'
import dayjs from 'dayjs'
import utc from 'dayjs/plugin/utc'
import timezone from 'dayjs/plugin/timezone'
import { createContext, useContext } from 'use-context-selector'
import { useShallow } from 'zustand/react/shallow'
import { useTranslation } from 'react-i18next'
@ -40,6 +42,11 @@ import AgentLogModal from '@/app/components/base/agent-log-modal'
import PromptLogModal from '@/app/components/base/prompt-log-modal'
import MessageLogModal from '@/app/components/base/message-log-modal'
import { useStore as useAppStore } from '@/app/components/app/store'
import { useAppContext } from '@/context/app-context'
import useTimestamp from '@/hooks/use-timestamp'
dayjs.extend(utc)
dayjs.extend(timezone)
type IConversationList = {
logs?: ChatConversationsResponse | CompletionConversationsResponse
@ -78,7 +85,7 @@ const PARAM_MAP = {
}
// Format interface data for easy display
const getFormattedChatList = (messages: ChatMessage[], conversationId: string) => {
const getFormattedChatList = (messages: ChatMessage[], conversationId: string, timezone: string, format: string) => {
const newChatList: IChatItem[] = []
messages.forEach((item: ChatMessage) => {
newChatList.push({
@ -115,7 +122,7 @@ const getFormattedChatList = (messages: ChatMessage[], conversationId: string) =
query: item.query,
},
more: {
time: dayjs.unix(item.created_at).format('hh:mm A'),
time: dayjs.unix(item.created_at).tz(timezone).format(format),
tokens: item.answer_tokens + item.message_tokens,
latency: item.provider_response_latency.toFixed(2),
},
@ -154,6 +161,8 @@ type IDetailPanel<T> = {
}
function DetailPanel<T extends ChatConversationFullDetailResponse | CompletionConversationFullDetailResponse>({ detail, onFeedback }: IDetailPanel<T>) {
const { userProfile: { timezone } } = useAppContext()
const { formatTime } = useTimestamp()
const { onClose, appDetail } = useContext(DrawerContext)
const { currentLogItem, setCurrentLogItem, showPromptLogModal, setShowPromptLogModal, showAgentLogModal, setShowAgentLogModal, showMessageLogModal, setShowMessageLogModal } = useAppStore(useShallow(state => ({
currentLogItem: state.currentLogItem,
@ -188,7 +197,7 @@ function DetailPanel<T extends ChatConversationFullDetailResponse | CompletionCo
const varValues = messageRes.data[0].inputs
setVarValues(varValues)
}
const newItems = [...getFormattedChatList(messageRes.data, detail.id), ...items]
const newItems = [...getFormattedChatList(messageRes.data, detail.id, timezone!, t('appLog.dateTimeFormat') as string), ...items]
if (messageRes.has_more === false && detail?.model_config?.configs?.introduction) {
newItems.unshift({
id: 'introduction',
@ -271,7 +280,7 @@ function DetailPanel<T extends ChatConversationFullDetailResponse | CompletionCo
<div className='border-b border-gray-100 py-4 px-6 flex items-center justify-between'>
<div>
<div className='text-gray-500 text-[10px] leading-[14px]'>{isChatMode ? t('appLog.detail.conversationId') : t('appLog.detail.time')}</div>
<div className='text-gray-700 text-[13px] leading-[18px]'>{isChatMode ? detail.id?.split('-').slice(-1)[0] : dayjs.unix(detail.created_at).format(t('appLog.dateTimeFormat') as string)}</div>
<div className='text-gray-700 text-[13px] leading-[18px]'>{isChatMode ? detail.id?.split('-').slice(-1)[0] : formatTime(detail.created_at, t('appLog.dateTimeFormat') as string)}</div>
</div>
<div className='flex items-center flex-wrap gap-y-1 justify-end'>
{!isAdvanced && (
@ -535,6 +544,7 @@ const ChatConversationDetailComp: FC<{ appId?: string; conversationId?: string }
*/
const ConversationList: FC<IConversationList> = ({ logs, appDetail, onRefresh }) => {
const { t } = useTranslation()
const { formatTime } = useTimestamp()
const media = useBreakpoints()
const isMobile = media === MediaType.mobile
@ -549,7 +559,7 @@ const ConversationList: FC<IConversationList> = ({ logs, appDetail, onRefresh })
<Tooltip
htmlContent={
<span className='text-xs text-gray-500 inline-flex items-center'>
<EditIconSolid className='mr-1' />{`${t('appLog.detail.annotationTip', { user: annotation?.account?.name })} ${dayjs.unix(annotation?.created_at || dayjs().unix()).format('MM-DD hh:mm A')}`}
<EditIconSolid className='mr-1' />{`${t('appLog.detail.annotationTip', { user: annotation?.account?.name })} ${formatTime(annotation?.created_at || dayjs().unix(), 'MM-DD hh:mm A')}`}
</span>
}
className={(isHighlight && !isChatMode) ? '' : '!hidden'}
@ -598,7 +608,7 @@ const ConversationList: FC<IConversationList> = ({ logs, appDetail, onRefresh })
setCurrentConversation(log)
}}>
<td className='text-center align-middle'>{!log.read_at && <span className='inline-block bg-[#3F83F8] h-1.5 w-1.5 rounded'></span>}</td>
<td className='w-[160px]'>{dayjs.unix(log.created_at).format(t('appLog.dateTimeFormat') as string)}</td>
<td className='w-[160px]'>{formatTime(log.created_at, t('appLog.dateTimeFormat') as string)}</td>
<td>{renderTdValue(endUser || defaultValue, !endUser)}</td>
<td style={{ maxWidth: isChatMode ? 300 : 200 }}>
{renderTdValue(leftValue || t('appLog.table.empty.noChat'), !leftValue, isChatMode && log.annotated)}

View File

@ -1,7 +1,6 @@
'use client'
import type { FC } from 'react'
import React, { useState } from 'react'
import dayjs from 'dayjs'
import { useTranslation } from 'react-i18next'
import cn from 'classnames'
import s from './style.module.css'
@ -12,6 +11,7 @@ import Loading from '@/app/components/base/loading'
import Drawer from '@/app/components/base/drawer'
import Indicator from '@/app/components/header/indicator'
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
import useTimestamp from '@/hooks/use-timestamp'
type ILogs = {
logs?: WorkflowLogsResponse
@ -23,6 +23,7 @@ const defaultValue = 'N/A'
const WorkflowAppLogList: FC<ILogs> = ({ logs, appDetail, onRefresh }) => {
const { t } = useTranslation()
const { formatTime } = useTimestamp()
const media = useBreakpoints()
const isMobile = media === MediaType.mobile
@ -99,7 +100,7 @@ const WorkflowAppLogList: FC<ILogs> = ({ logs, appDetail, onRefresh }) => {
setShowDrawer(true)
}}>
<td className='text-center align-middle'>{!log.read_at && <span className='inline-block bg-[#3F83F8] h-1.5 w-1.5 rounded'></span>}</td>
<td className='w-[160px]'>{dayjs.unix(log.created_at).format(t('appLog.dateTimeFormat') as string)}</td>
<td className='w-[160px]'>{formatTime(log.created_at, t('appLog.dateTimeFormat') as string)}</td>
<td>{statusTdRender(log.workflow_run.status)}</td>
<td>
<div className={cn(

View File

@ -1,10 +1,10 @@
'use client'
import type { FC } from 'react'
import { useTranslation } from 'react-i18next'
import dayjs from 'dayjs'
import StatusPanel from '@/app/components/workflow/run/status'
import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor'
import { CodeLanguage } from '@/app/components/workflow/nodes/code/types'
import useTimestamp from '@/hooks/use-timestamp'
type ResultPanelProps = {
status: string
@ -14,7 +14,7 @@ type ResultPanelProps = {
inputs?: any
outputs?: any
created_by?: string
created_at?: string
created_at: string
agentMode?: string
tools?: string[]
iterations?: number
@ -28,12 +28,13 @@ const ResultPanel: FC<ResultPanelProps> = ({
inputs,
outputs,
created_by,
created_at = 0,
created_at,
agentMode,
tools,
iterations,
}) => {
const { t } = useTranslation()
const { formatTime } = useTimestamp()
return (
<div className='bg-white py-2'>
@ -83,7 +84,7 @@ const ResultPanel: FC<ResultPanelProps> = ({
<div className='flex'>
<div className='shrink-0 w-[104px] px-2 py-[5px] text-gray-500 text-xs leading-[18px] truncate'>{t('runLog.meta.startTime')}</div>
<div className='grow px-2 py-[5px] text-gray-900 text-xs leading-[18px]'>
<span>{dayjs(created_at).format('YYYY-MM-DD hh:mm:ss')}</span>
<span>{formatTime(Date.parse(created_at) / 1000, t('appLog.dateTimeFormat') as string)}</span>
</div>
</div>
<div className='flex'>

View File

@ -6,7 +6,6 @@ import {
} from 'react'
import { useTranslation } from 'react-i18next'
import { produce, setAutoFreeze } from 'immer'
import dayjs from 'dayjs'
import type {
ChatConfig,
ChatItem,
@ -20,6 +19,7 @@ import { ssePost } from '@/service/base'
import { replaceStringWithValues } from '@/app/components/app/configuration/prompt-value-panel'
import type { Annotation } from '@/models/log'
import { WorkflowRunningStatus } from '@/app/components/workflow/types'
import useTimestamp from '@/hooks/use-timestamp'
type GetAbortController = (abortController: AbortController) => void
type SendCallback = {
@ -78,6 +78,7 @@ export const useChat = (
stopChat?: (taskId: string) => void,
) => {
const { t } = useTranslation()
const { formatTime } = useTimestamp()
const { notify } = useToastContext()
const connversationId = useRef('')
const hasStopResponded = useRef(false)
@ -336,7 +337,7 @@ export const useChat = (
: []),
],
more: {
time: dayjs.unix(newResponseItem.created_at).format('hh:mm A'),
time: formatTime(newResponseItem.created_at, 'hh:mm A'),
tokens: newResponseItem.answer_tokens + newResponseItem.message_tokens,
latency: newResponseItem.provider_response_latency.toFixed(2),
},
@ -498,6 +499,7 @@ export const useChat = (
promptVariablesConfig,
handleUpdateChatList,
handleResponding,
formatTime,
])
const handleAnnotationEdited = useCallback((query: string, answer: string, index: number) => {

View File

@ -5,12 +5,12 @@ import React, { useEffect, useState } from 'react'
import { useDebounceFn } from 'ahooks'
import { ArrowDownIcon, TrashIcon } from '@heroicons/react/24/outline'
import { ExclamationCircleIcon } from '@heroicons/react/24/solid'
import dayjs from 'dayjs'
import { pick } from 'lodash-es'
import { useContext } from 'use-context-selector'
import { useRouter } from 'next/navigation'
import { useTranslation } from 'react-i18next'
import cn from 'classnames'
import dayjs from 'dayjs'
import s from './style.module.css'
import Switch from '@/app/components/base/switch'
import Divider from '@/app/components/base/divider'
@ -29,6 +29,7 @@ import ProgressBar from '@/app/components/base/progress-bar'
import { DataSourceType, type DocumentDisplayStatus, type SimpleDocumentDetail } from '@/models/datasets'
import type { CommonResponse } from '@/models/common'
import { DotsHorizontal, HelpCircle } from '@/app/components/base/icons/src/vender/line/general'
import useTimestamp from '@/hooks/use-timestamp'
export const SettingsIcon = ({ className }: SVGProps<SVGElement>) => {
return <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" className={className ?? ''}>
@ -305,6 +306,7 @@ type IDocumentListProps = {
*/
const DocumentList: FC<IDocumentListProps> = ({ embeddingAvailable, documents = [], datasetId, onUpdate }) => {
const { t } = useTranslation()
const { formatTime } = useTimestamp()
const router = useRouter()
const [localDocs, setLocalDocs] = useState<LocalDoc[]>(documents)
const [enableSort, setEnableSort] = useState(false)
@ -368,7 +370,7 @@ const DocumentList: FC<IDocumentListProps> = ({ embeddingAvailable, documents =
<td>{renderCount(doc.word_count)}</td>
<td>{renderCount(doc.hit_count)}</td>
<td className='text-gray-500 text-[13px]'>
{dayjs.unix(doc.created_at).format(t('datasetHitTesting.dateTimeFormat') as string)}
{formatTime(doc.created_at, t('datasetHitTesting.dateTimeFormat') as string)}
</td>
<td>
{

View File

@ -5,7 +5,6 @@ import { useTranslation } from 'react-i18next'
import useSWR from 'swr'
import { omit } from 'lodash-es'
import cn from 'classnames'
import dayjs from 'dayjs'
import { useBoolean } from 'ahooks'
import { useContext } from 'use-context-selector'
import SegmentCard from '../documents/detail/completed/SegmentCard'
@ -24,6 +23,7 @@ import { fetchTestingRecords } from '@/service/datasets'
import DatasetDetailContext from '@/context/dataset-detail'
import type { RetrievalConfig } from '@/types/app'
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
import useTimestamp from '@/hooks/use-timestamp'
const limit = 10
@ -43,6 +43,7 @@ const RecordsEmpty: FC = () => {
const HitTesting: FC<Props> = ({ datasetId }: Props) => {
const { t } = useTranslation()
const { formatTime } = useTimestamp()
const media = useBreakpoints()
const isMobile = media === MediaType.mobile
@ -129,7 +130,7 @@ const HitTesting: FC<Props> = ({ datasetId }: Props) => {
</td>
<td className='max-w-xs group-hover:text-primary-600'>{record.content}</td>
<td className='w-36'>
{dayjs.unix(record.created_at).format(t('datasetHitTesting.dateTimeFormat') as string)}
{formatTime(record.created_at, t('datasetHitTesting.dateTimeFormat') as string)}
</td>
</tr>
})}

View File

@ -6,7 +6,6 @@ import {
import { useTranslation } from 'react-i18next'
import { PlusIcon, XMarkIcon } from '@heroicons/react/20/solid'
import useSWR, { useSWRConfig } from 'swr'
import { useContext } from 'use-context-selector'
import copy from 'copy-to-clipboard'
import SecretKeyGenerateModal from './secret-key-generate'
import s from './style.module.css'
@ -26,8 +25,7 @@ import type { CreateApiKeyResponse } from '@/models/app'
import Tooltip from '@/app/components/base/tooltip'
import Loading from '@/app/components/base/loading'
import Confirm from '@/app/components/base/confirm'
import I18n from '@/context/i18n'
import { LanguagesSupported } from '@/i18n/language'
import useTimestamp from '@/hooks/use-timestamp'
import { useAppContext } from '@/context/app-context'
type ISecretKeyModalProps = {
@ -42,6 +40,7 @@ const SecretKeyModal = ({
onClose,
}: ISecretKeyModalProps) => {
const { t } = useTranslation()
const { formatTime } = useTimestamp()
const { currentWorkspace, isCurrentWorkspaceManager } = useAppContext()
const [showConfirmDelete, setShowConfirmDelete] = useState(false)
const [isVisible, setVisible] = useState(false)
@ -55,9 +54,6 @@ const SecretKeyModal = ({
const [delKeyID, setDelKeyId] = useState('')
const { locale } = useContext(I18n)
// const [isCopied, setIsCopied] = useState(false)
const [copyValue, setCopyValue] = useState('')
useEffect(() => {
@ -100,13 +96,6 @@ const SecretKeyModal = ({
return `${token.slice(0, 3)}...${token.slice(-20)}`
}
const formatDate = (timestamp: string) => {
if (locale === LanguagesSupported[0])
return new Intl.DateTimeFormat('en-US', { year: 'numeric', month: 'long', day: 'numeric' }).format((+timestamp) * 1000)
else
return new Intl.DateTimeFormat('fr-CA', { year: 'numeric', month: '2-digit', day: '2-digit' }).format((+timestamp) * 1000)
}
return (
<Modal isShow={isShow} onClose={onClose} title={`${t('appApi.apiKeyModal.apiSecretKey')}`} className={`${s.customModal} px-8 flex flex-col`}>
<XMarkIcon className={`w-6 h-6 absolute cursor-pointer text-gray-500 ${s.close}`} onClick={onClose} />
@ -117,18 +106,16 @@ const SecretKeyModal = ({
<div className='flex flex-col flex-grow mt-4 overflow-hidden'>
<div className='flex items-center flex-shrink-0 text-xs font-semibold text-gray-500 border-b border-solid h-9'>
<div className='flex-shrink-0 w-64 px-3'>{t('appApi.apiKeyModal.secretKey')}</div>
<div className='flex-shrink-0 px-3 w-28'>{t('appApi.apiKeyModal.created')}</div>
<div className='flex-shrink-0 px-3 w-28'>{t('appApi.apiKeyModal.lastUsed')}</div>
<div className='flex-shrink-0 px-3 w-[200px]'>{t('appApi.apiKeyModal.created')}</div>
<div className='flex-shrink-0 px-3 w-[200px]'>{t('appApi.apiKeyModal.lastUsed')}</div>
<div className='flex-grow px-3'></div>
</div>
<div className='flex-grow overflow-auto'>
{apiKeysList.data.map(api => (
<div className='flex items-center text-sm font-normal text-gray-700 border-b border-solid h-9' key={api.id}>
<div className='flex-shrink-0 w-64 px-3 font-mono truncate'>{generateToken(api.token)}</div>
<div className='flex-shrink-0 px-3 truncate w-28'>{formatDate(api.created_at)}</div>
{/* <div className='flex-shrink-0 px-3 truncate w-28'>{dayjs((+api.created_at) * 1000).format('MMM D, YYYY')}</div> */}
{/* <div className='flex-shrink-0 px-3 truncate w-28'>{api.last_used_at ? dayjs((+api.last_used_at) * 1000).format('MMM D, YYYY') : 'Never'}</div> */}
<div className='flex-shrink-0 px-3 truncate w-28'>{api.last_used_at ? formatDate(api.last_used_at) : t('appApi.never')}</div>
<div className='flex-shrink-0 px-3 truncate w-[200px]'>{formatTime(Number(api.created_at), t('appLog.dateTimeFormat') as string)}</div>
<div className='flex-shrink-0 px-3 truncate w-[200px]'>{api.last_used_at ? formatTime(Number(api.created_at), t('appLog.dateTimeFormat') as string) : t('appApi.never')}</div>
<div className='flex flex-grow px-3'>
<Tooltip
selector={`key-${api.token}`}

View File

@ -1,5 +1,5 @@
.customModal {
max-width: 40rem !important;
max-width: 800px !important;
max-height: calc(100vh - 80px);
}

View File

@ -1,11 +1,12 @@
import { memo } from 'react'
import dayjs from 'dayjs'
import { useTranslation } from 'react-i18next'
import { useWorkflow } from '../hooks'
import { useStore } from '@/app/components/workflow/store'
import useTimestamp from '@/hooks/use-timestamp'
const EditingTitle = () => {
const { t } = useTranslation()
const { formatTime } = useTimestamp()
const { formatTimeFromNow } = useWorkflow()
const draftUpdatedAt = useStore(state => state.draftUpdatedAt)
const publishedAt = useStore(state => state.publishedAt)
@ -15,7 +16,7 @@ const EditingTitle = () => {
{
!!draftUpdatedAt && (
<>
{t('workflow.common.autoSaved')} {dayjs(draftUpdatedAt).format('HH:mm:ss')}
{t('workflow.common.autoSaved')} {formatTime(draftUpdatedAt / 1000, 'HH:mm:ss')}
</>
)
}

View File

@ -1,8 +1,7 @@
'use client'
import type { FC } from 'react'
import { useTranslation } from 'react-i18next'
// import cn from 'classnames'
import dayjs from 'dayjs'
import useTimestamp from '@/hooks/use-timestamp'
type Props = {
status: string
@ -24,6 +23,7 @@ const MetaData: FC<Props> = ({
showSteps = true,
}) => {
const { t } = useTranslation()
const { formatTime } = useTimestamp()
return (
<div className='relative'>
@ -64,7 +64,7 @@ const MetaData: FC<Props> = ({
<div className='my-[5px] w-[72px] h-2 rounded-sm bg-[rgba(0,0,0,0.05)]'/>
)}
{status !== 'running' && (
<span>{dayjs(startTime * 1000).format('YYYY-MM-DD hh:mm:ss')}</span>
<span>{formatTime(startTime, t('appLog.dateTimeFormat') as string)}</span>
)}
</div>
</div>

View File

@ -1,8 +1,8 @@
'use client'
import { useTranslation } from 'react-i18next'
import dayjs from 'dayjs'
import { formatFileSize, formatNumber, formatTime } from '@/utils/format'
import type { DocType } from '@/models/datasets'
import useTimestamp from '@/hooks/use-timestamp'
export type inputType = 'input' | 'select' | 'textarea'
export type metadataType = DocType | 'originInfo' | 'technicalParameters'
@ -31,6 +31,8 @@ const fieldPrefix = 'datasetDocuments.metadata.field'
export const useMetadataMap = (): MetadataMap => {
const { t } = useTranslation()
const { formatTime: formatTimestamp } = useTimestamp()
return {
book: {
text: t('datasetDocuments.metadata.type.book'),
@ -230,11 +232,11 @@ export const useMetadataMap = (): MetadataMap => {
},
'created_at': {
label: t(`${fieldPrefix}.originInfo.uploadDate`),
render: value => dayjs.unix(value).format(t('datasetDocuments.metadata.dateTimeFormat') as string),
render: value => formatTimestamp(value, t('datasetDocuments.metadata.dateTimeFormat') as string),
},
'completed_at': {
label: t(`${fieldPrefix}.originInfo.lastUpdateDate`),
render: value => dayjs.unix(value).format(t('datasetDocuments.metadata.dateTimeFormat') as string),
render: value => formatTimestamp(value, t('datasetDocuments.metadata.dateTimeFormat') as string),
},
'data_source_type': {
label: t(`${fieldPrefix}.originInfo.source`),

View File

@ -0,0 +1,21 @@
'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])
return { formatTime }
}
export default useTimestamp