mirror of
https://gitee.com/dify_ai/dify.git
synced 2024-11-30 02:08:37 +08:00
Feat: frontend support timezone of timestamp (#4070)
This commit is contained in:
parent
f68b6b0e5e
commit
c0476c7881
@ -2,7 +2,6 @@
|
|||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import dayjs from 'dayjs'
|
|
||||||
import EditItem, { EditItemType } from './edit-item'
|
import EditItem, { EditItemType } from './edit-item'
|
||||||
import Drawer from '@/app/components/base/drawer-plus'
|
import Drawer from '@/app/components/base/drawer-plus'
|
||||||
import { MessageCheckRemove } from '@/app/components/base/icons/src/vender/line/communication'
|
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 Toast from '@/app/components/base/toast'
|
||||||
import { useProviderContext } from '@/context/provider-context'
|
import { useProviderContext } from '@/context/provider-context'
|
||||||
import AnnotationFull from '@/app/components/billing/annotation-full'
|
import AnnotationFull from '@/app/components/billing/annotation-full'
|
||||||
|
import useTimestamp from '@/hooks/use-timestamp'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
isShow: boolean
|
isShow: boolean
|
||||||
onHide: () => void
|
onHide: () => void
|
||||||
@ -41,6 +42,7 @@ const EditAnnotationModal: FC<Props> = ({
|
|||||||
onlyEditResponse,
|
onlyEditResponse,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
const { formatTime } = useTimestamp()
|
||||||
const { plan, enableBilling } = useProviderContext()
|
const { plan, enableBilling } = useProviderContext()
|
||||||
const isAdd = !annotationId
|
const isAdd = !annotationId
|
||||||
const isAnnotationFull = (enableBilling && plan.usage.annotatedResponse >= plan.total.annotatedResponse)
|
const isAnnotationFull = (enableBilling && plan.usage.annotatedResponse >= plan.total.annotatedResponse)
|
||||||
@ -117,15 +119,14 @@ const EditAnnotationModal: FC<Props> = ({
|
|||||||
<MessageCheckRemove />
|
<MessageCheckRemove />
|
||||||
<div>{t('appAnnotation.editModal.removeThisCache')}</div>
|
<div>{t('appAnnotation.editModal.removeThisCache')}</div>
|
||||||
</div>
|
</div>
|
||||||
{createdAt && <div>{t('appAnnotation.editModal.createdAt')} {dayjs(createdAt * 1000).format('YYYY-MM-DD HH:mm')}</div>}
|
{createdAt && <div>{t('appAnnotation.editModal.createdAt')} {formatTime(createdAt, t('appLog.dateTimeFormat') as string)}</div>}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
>
|
/>
|
||||||
</Drawer>
|
|
||||||
<DeleteConfirmModal
|
<DeleteConfirmModal
|
||||||
isShow={showModal}
|
isShow={showModal}
|
||||||
onHide={() => setShowModal(false)}
|
onHide={() => setShowModal(false)}
|
||||||
|
@ -3,11 +3,11 @@ import type { FC } from 'react'
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import cn from 'classnames'
|
import cn from 'classnames'
|
||||||
import dayjs from 'dayjs'
|
|
||||||
import { Edit02, Trash03 } from '../../base/icons/src/vender/line/general'
|
import { Edit02, Trash03 } from '../../base/icons/src/vender/line/general'
|
||||||
import s from './style.module.css'
|
import s from './style.module.css'
|
||||||
import type { AnnotationItem } from './type'
|
import type { AnnotationItem } from './type'
|
||||||
import RemoveAnnotationConfirmModal from './remove-annotation-confirm-modal'
|
import RemoveAnnotationConfirmModal from './remove-annotation-confirm-modal'
|
||||||
|
import useTimestamp from '@/hooks/use-timestamp'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
list: AnnotationItem[]
|
list: AnnotationItem[]
|
||||||
@ -21,6 +21,7 @@ const List: FC<Props> = ({
|
|||||||
onRemove,
|
onRemove,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
const { formatTime } = useTimestamp()
|
||||||
const [currId, setCurrId] = React.useState<string | null>(null)
|
const [currId, setCurrId] = React.useState<string | null>(null)
|
||||||
const [showConfirmDelete, setShowConfirmDelete] = React.useState(false)
|
const [showConfirmDelete, setShowConfirmDelete] = React.useState(false)
|
||||||
return (
|
return (
|
||||||
@ -54,7 +55,7 @@ const List: FC<Props> = ({
|
|||||||
className='whitespace-nowrap overflow-hidden text-ellipsis max-w-[250px]'
|
className='whitespace-nowrap overflow-hidden text-ellipsis max-w-[250px]'
|
||||||
title={item.answer}
|
title={item.answer}
|
||||||
>{item.answer}</td>
|
>{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>{item.hit_count}</td>
|
||||||
<td className='w-[96px]' onClick={e => e.stopPropagation()}>
|
<td className='w-[96px]' onClick={e => e.stopPropagation()}>
|
||||||
{/* Actions */}
|
{/* Actions */}
|
||||||
|
@ -3,7 +3,6 @@ import type { FC } from 'react'
|
|||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import cn from 'classnames'
|
import cn from 'classnames'
|
||||||
import dayjs from 'dayjs'
|
|
||||||
import { Pagination } from 'react-headless-pagination'
|
import { Pagination } from 'react-headless-pagination'
|
||||||
import { ArrowLeftIcon, ArrowRightIcon } from '@heroicons/react/24/outline'
|
import { ArrowLeftIcon, ArrowRightIcon } from '@heroicons/react/24/outline'
|
||||||
import EditItem, { EditItemType } from '../edit-annotation-modal/edit-item'
|
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 TabSlider from '@/app/components/base/tab-slider-plain'
|
||||||
import { fetchHitHistoryList } from '@/service/annotation'
|
import { fetchHitHistoryList } from '@/service/annotation'
|
||||||
import { APP_PAGE_LIMIT } from '@/config'
|
import { APP_PAGE_LIMIT } from '@/config'
|
||||||
|
import useTimestamp from '@/hooks/use-timestamp'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
appId: string
|
appId: string
|
||||||
@ -43,6 +43,7 @@ const ViewAnnotationModal: FC<Props> = ({
|
|||||||
const [newQuestion, setNewQuery] = useState(question)
|
const [newQuestion, setNewQuery] = useState(question)
|
||||||
const [newAnswer, setNewAnswer] = useState(answer)
|
const [newAnswer, setNewAnswer] = useState(answer)
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
const { formatTime } = useTimestamp()
|
||||||
const [currPage, setCurrPage] = React.useState<number>(0)
|
const [currPage, setCurrPage] = React.useState<number>(0)
|
||||||
const [total, setTotal] = useState(0)
|
const [total, setTotal] = useState(0)
|
||||||
const [hitHistoryList, setHitHistoryList] = useState<HitHistoryItem[]>([])
|
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.response')}</td>
|
||||||
<td className='whitespace-nowrap'>{t('appAnnotation.hitHistoryTable.source')}</td>
|
<td className='whitespace-nowrap'>{t('appAnnotation.hitHistoryTable.source')}</td>
|
||||||
<td className='whitespace-nowrap'>{t('appAnnotation.hitHistoryTable.score')}</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>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody className="text-gray-500">
|
<tbody className="text-gray-500">
|
||||||
@ -142,7 +143,7 @@ const ViewAnnotationModal: FC<Props> = ({
|
|||||||
>{item.response}</td>
|
>{item.response}</td>
|
||||||
<td>{item.source}</td>
|
<td>{item.source}</td>
|
||||||
<td>{item.score ? item.score.toFixed(2) : '-'}</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>
|
</tr>
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
@ -214,12 +215,11 @@ const ViewAnnotationModal: FC<Props> = ({
|
|||||||
<MessageCheckRemove />
|
<MessageCheckRemove />
|
||||||
<div>{t('appAnnotation.editModal.removeThisCache')}</div>
|
<div>{t('appAnnotation.editModal.removeThisCache')}</div>
|
||||||
</div>
|
</div>
|
||||||
<div>{t('appAnnotation.editModal.createdAt')} {dayjs(createdAt * 1000).format('YYYY-MM-DD HH:mm')}</div>
|
<div>{t('appAnnotation.editModal.createdAt')} {formatTime(createdAt, t('appLog.dateTimeFormat') as string)}</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
: undefined}
|
: undefined}
|
||||||
>
|
/>
|
||||||
</Drawer>
|
|
||||||
<DeleteConfirmModal
|
<DeleteConfirmModal
|
||||||
isShow={showModal}
|
isShow={showModal}
|
||||||
onHide={() => setShowModal(false)}
|
onHide={() => setShowModal(false)}
|
||||||
|
@ -11,6 +11,8 @@ import {
|
|||||||
import { get } from 'lodash-es'
|
import { get } from 'lodash-es'
|
||||||
import InfiniteScroll from 'react-infinite-scroll-component'
|
import InfiniteScroll from 'react-infinite-scroll-component'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
|
import utc from 'dayjs/plugin/utc'
|
||||||
|
import timezone from 'dayjs/plugin/timezone'
|
||||||
import { createContext, useContext } from 'use-context-selector'
|
import { createContext, useContext } from 'use-context-selector'
|
||||||
import { useShallow } from 'zustand/react/shallow'
|
import { useShallow } from 'zustand/react/shallow'
|
||||||
import { useTranslation } from 'react-i18next'
|
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 PromptLogModal from '@/app/components/base/prompt-log-modal'
|
||||||
import MessageLogModal from '@/app/components/base/message-log-modal'
|
import MessageLogModal from '@/app/components/base/message-log-modal'
|
||||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
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 = {
|
type IConversationList = {
|
||||||
logs?: ChatConversationsResponse | CompletionConversationsResponse
|
logs?: ChatConversationsResponse | CompletionConversationsResponse
|
||||||
@ -78,7 +85,7 @@ const PARAM_MAP = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Format interface data for easy display
|
// 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[] = []
|
const newChatList: IChatItem[] = []
|
||||||
messages.forEach((item: ChatMessage) => {
|
messages.forEach((item: ChatMessage) => {
|
||||||
newChatList.push({
|
newChatList.push({
|
||||||
@ -115,7 +122,7 @@ const getFormattedChatList = (messages: ChatMessage[], conversationId: string) =
|
|||||||
query: item.query,
|
query: item.query,
|
||||||
},
|
},
|
||||||
more: {
|
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,
|
tokens: item.answer_tokens + item.message_tokens,
|
||||||
latency: item.provider_response_latency.toFixed(2),
|
latency: item.provider_response_latency.toFixed(2),
|
||||||
},
|
},
|
||||||
@ -154,6 +161,8 @@ type IDetailPanel<T> = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function DetailPanel<T extends ChatConversationFullDetailResponse | CompletionConversationFullDetailResponse>({ detail, onFeedback }: 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 { onClose, appDetail } = useContext(DrawerContext)
|
||||||
const { currentLogItem, setCurrentLogItem, showPromptLogModal, setShowPromptLogModal, showAgentLogModal, setShowAgentLogModal, showMessageLogModal, setShowMessageLogModal } = useAppStore(useShallow(state => ({
|
const { currentLogItem, setCurrentLogItem, showPromptLogModal, setShowPromptLogModal, showAgentLogModal, setShowAgentLogModal, showMessageLogModal, setShowMessageLogModal } = useAppStore(useShallow(state => ({
|
||||||
currentLogItem: state.currentLogItem,
|
currentLogItem: state.currentLogItem,
|
||||||
@ -188,7 +197,7 @@ function DetailPanel<T extends ChatConversationFullDetailResponse | CompletionCo
|
|||||||
const varValues = messageRes.data[0].inputs
|
const varValues = messageRes.data[0].inputs
|
||||||
setVarValues(varValues)
|
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) {
|
if (messageRes.has_more === false && detail?.model_config?.configs?.introduction) {
|
||||||
newItems.unshift({
|
newItems.unshift({
|
||||||
id: 'introduction',
|
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 className='border-b border-gray-100 py-4 px-6 flex items-center justify-between'>
|
||||||
<div>
|
<div>
|
||||||
<div className='text-gray-500 text-[10px] leading-[14px]'>{isChatMode ? t('appLog.detail.conversationId') : t('appLog.detail.time')}</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>
|
||||||
<div className='flex items-center flex-wrap gap-y-1 justify-end'>
|
<div className='flex items-center flex-wrap gap-y-1 justify-end'>
|
||||||
{!isAdvanced && (
|
{!isAdvanced && (
|
||||||
@ -535,6 +544,7 @@ const ChatConversationDetailComp: FC<{ appId?: string; conversationId?: string }
|
|||||||
*/
|
*/
|
||||||
const ConversationList: FC<IConversationList> = ({ logs, appDetail, onRefresh }) => {
|
const ConversationList: FC<IConversationList> = ({ logs, appDetail, onRefresh }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
const { formatTime } = useTimestamp()
|
||||||
|
|
||||||
const media = useBreakpoints()
|
const media = useBreakpoints()
|
||||||
const isMobile = media === MediaType.mobile
|
const isMobile = media === MediaType.mobile
|
||||||
@ -549,7 +559,7 @@ const ConversationList: FC<IConversationList> = ({ logs, appDetail, onRefresh })
|
|||||||
<Tooltip
|
<Tooltip
|
||||||
htmlContent={
|
htmlContent={
|
||||||
<span className='text-xs text-gray-500 inline-flex items-center'>
|
<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>
|
</span>
|
||||||
}
|
}
|
||||||
className={(isHighlight && !isChatMode) ? '' : '!hidden'}
|
className={(isHighlight && !isChatMode) ? '' : '!hidden'}
|
||||||
@ -598,7 +608,7 @@ const ConversationList: FC<IConversationList> = ({ logs, appDetail, onRefresh })
|
|||||||
setCurrentConversation(log)
|
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='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>{renderTdValue(endUser || defaultValue, !endUser)}</td>
|
||||||
<td style={{ maxWidth: isChatMode ? 300 : 200 }}>
|
<td style={{ maxWidth: isChatMode ? 300 : 200 }}>
|
||||||
{renderTdValue(leftValue || t('appLog.table.empty.noChat'), !leftValue, isChatMode && log.annotated)}
|
{renderTdValue(leftValue || t('appLog.table.empty.noChat'), !leftValue, isChatMode && log.annotated)}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
import dayjs from 'dayjs'
|
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import cn from 'classnames'
|
import cn from 'classnames'
|
||||||
import s from './style.module.css'
|
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 Drawer from '@/app/components/base/drawer'
|
||||||
import Indicator from '@/app/components/header/indicator'
|
import Indicator from '@/app/components/header/indicator'
|
||||||
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
||||||
|
import useTimestamp from '@/hooks/use-timestamp'
|
||||||
|
|
||||||
type ILogs = {
|
type ILogs = {
|
||||||
logs?: WorkflowLogsResponse
|
logs?: WorkflowLogsResponse
|
||||||
@ -23,6 +23,7 @@ const defaultValue = 'N/A'
|
|||||||
|
|
||||||
const WorkflowAppLogList: FC<ILogs> = ({ logs, appDetail, onRefresh }) => {
|
const WorkflowAppLogList: FC<ILogs> = ({ logs, appDetail, onRefresh }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
const { formatTime } = useTimestamp()
|
||||||
|
|
||||||
const media = useBreakpoints()
|
const media = useBreakpoints()
|
||||||
const isMobile = media === MediaType.mobile
|
const isMobile = media === MediaType.mobile
|
||||||
@ -99,7 +100,7 @@ const WorkflowAppLogList: FC<ILogs> = ({ logs, appDetail, onRefresh }) => {
|
|||||||
setShowDrawer(true)
|
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='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>{statusTdRender(log.workflow_run.status)}</td>
|
||||||
<td>
|
<td>
|
||||||
<div className={cn(
|
<div className={cn(
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import dayjs from 'dayjs'
|
|
||||||
import StatusPanel from '@/app/components/workflow/run/status'
|
import StatusPanel from '@/app/components/workflow/run/status'
|
||||||
import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor'
|
import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor'
|
||||||
import { CodeLanguage } from '@/app/components/workflow/nodes/code/types'
|
import { CodeLanguage } from '@/app/components/workflow/nodes/code/types'
|
||||||
|
import useTimestamp from '@/hooks/use-timestamp'
|
||||||
|
|
||||||
type ResultPanelProps = {
|
type ResultPanelProps = {
|
||||||
status: string
|
status: string
|
||||||
@ -14,7 +14,7 @@ type ResultPanelProps = {
|
|||||||
inputs?: any
|
inputs?: any
|
||||||
outputs?: any
|
outputs?: any
|
||||||
created_by?: string
|
created_by?: string
|
||||||
created_at?: string
|
created_at: string
|
||||||
agentMode?: string
|
agentMode?: string
|
||||||
tools?: string[]
|
tools?: string[]
|
||||||
iterations?: number
|
iterations?: number
|
||||||
@ -28,12 +28,13 @@ const ResultPanel: FC<ResultPanelProps> = ({
|
|||||||
inputs,
|
inputs,
|
||||||
outputs,
|
outputs,
|
||||||
created_by,
|
created_by,
|
||||||
created_at = 0,
|
created_at,
|
||||||
agentMode,
|
agentMode,
|
||||||
tools,
|
tools,
|
||||||
iterations,
|
iterations,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
const { formatTime } = useTimestamp()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='bg-white py-2'>
|
<div className='bg-white py-2'>
|
||||||
@ -83,7 +84,7 @@ const ResultPanel: FC<ResultPanelProps> = ({
|
|||||||
<div className='flex'>
|
<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='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]'>
|
<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>
|
</div>
|
||||||
<div className='flex'>
|
<div className='flex'>
|
||||||
|
@ -6,7 +6,6 @@ import {
|
|||||||
} from 'react'
|
} from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { produce, setAutoFreeze } from 'immer'
|
import { produce, setAutoFreeze } from 'immer'
|
||||||
import dayjs from 'dayjs'
|
|
||||||
import type {
|
import type {
|
||||||
ChatConfig,
|
ChatConfig,
|
||||||
ChatItem,
|
ChatItem,
|
||||||
@ -20,6 +19,7 @@ import { ssePost } from '@/service/base'
|
|||||||
import { replaceStringWithValues } from '@/app/components/app/configuration/prompt-value-panel'
|
import { replaceStringWithValues } from '@/app/components/app/configuration/prompt-value-panel'
|
||||||
import type { Annotation } from '@/models/log'
|
import type { Annotation } from '@/models/log'
|
||||||
import { WorkflowRunningStatus } from '@/app/components/workflow/types'
|
import { WorkflowRunningStatus } from '@/app/components/workflow/types'
|
||||||
|
import useTimestamp from '@/hooks/use-timestamp'
|
||||||
|
|
||||||
type GetAbortController = (abortController: AbortController) => void
|
type GetAbortController = (abortController: AbortController) => void
|
||||||
type SendCallback = {
|
type SendCallback = {
|
||||||
@ -78,6 +78,7 @@ export const useChat = (
|
|||||||
stopChat?: (taskId: string) => void,
|
stopChat?: (taskId: string) => void,
|
||||||
) => {
|
) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
const { formatTime } = useTimestamp()
|
||||||
const { notify } = useToastContext()
|
const { notify } = useToastContext()
|
||||||
const connversationId = useRef('')
|
const connversationId = useRef('')
|
||||||
const hasStopResponded = useRef(false)
|
const hasStopResponded = useRef(false)
|
||||||
@ -336,7 +337,7 @@ export const useChat = (
|
|||||||
: []),
|
: []),
|
||||||
],
|
],
|
||||||
more: {
|
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,
|
tokens: newResponseItem.answer_tokens + newResponseItem.message_tokens,
|
||||||
latency: newResponseItem.provider_response_latency.toFixed(2),
|
latency: newResponseItem.provider_response_latency.toFixed(2),
|
||||||
},
|
},
|
||||||
@ -498,6 +499,7 @@ export const useChat = (
|
|||||||
promptVariablesConfig,
|
promptVariablesConfig,
|
||||||
handleUpdateChatList,
|
handleUpdateChatList,
|
||||||
handleResponding,
|
handleResponding,
|
||||||
|
formatTime,
|
||||||
])
|
])
|
||||||
|
|
||||||
const handleAnnotationEdited = useCallback((query: string, answer: string, index: number) => {
|
const handleAnnotationEdited = useCallback((query: string, answer: string, index: number) => {
|
||||||
|
@ -5,12 +5,12 @@ import React, { useEffect, useState } from 'react'
|
|||||||
import { useDebounceFn } from 'ahooks'
|
import { useDebounceFn } from 'ahooks'
|
||||||
import { ArrowDownIcon, TrashIcon } from '@heroicons/react/24/outline'
|
import { ArrowDownIcon, TrashIcon } from '@heroicons/react/24/outline'
|
||||||
import { ExclamationCircleIcon } from '@heroicons/react/24/solid'
|
import { ExclamationCircleIcon } from '@heroicons/react/24/solid'
|
||||||
import dayjs from 'dayjs'
|
|
||||||
import { pick } from 'lodash-es'
|
import { pick } from 'lodash-es'
|
||||||
import { useContext } from 'use-context-selector'
|
import { useContext } from 'use-context-selector'
|
||||||
import { useRouter } from 'next/navigation'
|
import { useRouter } from 'next/navigation'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import cn from 'classnames'
|
import cn from 'classnames'
|
||||||
|
import dayjs from 'dayjs'
|
||||||
import s from './style.module.css'
|
import s from './style.module.css'
|
||||||
import Switch from '@/app/components/base/switch'
|
import Switch from '@/app/components/base/switch'
|
||||||
import Divider from '@/app/components/base/divider'
|
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 { DataSourceType, type DocumentDisplayStatus, type SimpleDocumentDetail } from '@/models/datasets'
|
||||||
import type { CommonResponse } from '@/models/common'
|
import type { CommonResponse } from '@/models/common'
|
||||||
import { DotsHorizontal, HelpCircle } from '@/app/components/base/icons/src/vender/line/general'
|
import { DotsHorizontal, HelpCircle } from '@/app/components/base/icons/src/vender/line/general'
|
||||||
|
import useTimestamp from '@/hooks/use-timestamp'
|
||||||
|
|
||||||
export const SettingsIcon = ({ className }: SVGProps<SVGElement>) => {
|
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 ?? ''}>
|
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 DocumentList: FC<IDocumentListProps> = ({ embeddingAvailable, documents = [], datasetId, onUpdate }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
const { formatTime } = useTimestamp()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const [localDocs, setLocalDocs] = useState<LocalDoc[]>(documents)
|
const [localDocs, setLocalDocs] = useState<LocalDoc[]>(documents)
|
||||||
const [enableSort, setEnableSort] = useState(false)
|
const [enableSort, setEnableSort] = useState(false)
|
||||||
@ -368,7 +370,7 @@ const DocumentList: FC<IDocumentListProps> = ({ embeddingAvailable, documents =
|
|||||||
<td>{renderCount(doc.word_count)}</td>
|
<td>{renderCount(doc.word_count)}</td>
|
||||||
<td>{renderCount(doc.hit_count)}</td>
|
<td>{renderCount(doc.hit_count)}</td>
|
||||||
<td className='text-gray-500 text-[13px]'>
|
<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>
|
||||||
<td>
|
<td>
|
||||||
{
|
{
|
||||||
|
@ -5,7 +5,6 @@ import { useTranslation } from 'react-i18next'
|
|||||||
import useSWR from 'swr'
|
import useSWR from 'swr'
|
||||||
import { omit } from 'lodash-es'
|
import { omit } from 'lodash-es'
|
||||||
import cn from 'classnames'
|
import cn from 'classnames'
|
||||||
import dayjs from 'dayjs'
|
|
||||||
import { useBoolean } from 'ahooks'
|
import { useBoolean } from 'ahooks'
|
||||||
import { useContext } from 'use-context-selector'
|
import { useContext } from 'use-context-selector'
|
||||||
import SegmentCard from '../documents/detail/completed/SegmentCard'
|
import SegmentCard from '../documents/detail/completed/SegmentCard'
|
||||||
@ -24,6 +23,7 @@ import { fetchTestingRecords } from '@/service/datasets'
|
|||||||
import DatasetDetailContext from '@/context/dataset-detail'
|
import DatasetDetailContext from '@/context/dataset-detail'
|
||||||
import type { RetrievalConfig } from '@/types/app'
|
import type { RetrievalConfig } from '@/types/app'
|
||||||
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
||||||
|
import useTimestamp from '@/hooks/use-timestamp'
|
||||||
|
|
||||||
const limit = 10
|
const limit = 10
|
||||||
|
|
||||||
@ -43,6 +43,7 @@ const RecordsEmpty: FC = () => {
|
|||||||
|
|
||||||
const HitTesting: FC<Props> = ({ datasetId }: Props) => {
|
const HitTesting: FC<Props> = ({ datasetId }: Props) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
const { formatTime } = useTimestamp()
|
||||||
|
|
||||||
const media = useBreakpoints()
|
const media = useBreakpoints()
|
||||||
const isMobile = media === MediaType.mobile
|
const isMobile = media === MediaType.mobile
|
||||||
@ -129,7 +130,7 @@ const HitTesting: FC<Props> = ({ datasetId }: Props) => {
|
|||||||
</td>
|
</td>
|
||||||
<td className='max-w-xs group-hover:text-primary-600'>{record.content}</td>
|
<td className='max-w-xs group-hover:text-primary-600'>{record.content}</td>
|
||||||
<td className='w-36'>
|
<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>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
})}
|
})}
|
||||||
|
@ -6,7 +6,6 @@ import {
|
|||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { PlusIcon, XMarkIcon } from '@heroicons/react/20/solid'
|
import { PlusIcon, XMarkIcon } from '@heroicons/react/20/solid'
|
||||||
import useSWR, { useSWRConfig } from 'swr'
|
import useSWR, { useSWRConfig } from 'swr'
|
||||||
import { useContext } from 'use-context-selector'
|
|
||||||
import copy from 'copy-to-clipboard'
|
import copy from 'copy-to-clipboard'
|
||||||
import SecretKeyGenerateModal from './secret-key-generate'
|
import SecretKeyGenerateModal from './secret-key-generate'
|
||||||
import s from './style.module.css'
|
import s from './style.module.css'
|
||||||
@ -26,8 +25,7 @@ import type { CreateApiKeyResponse } from '@/models/app'
|
|||||||
import Tooltip from '@/app/components/base/tooltip'
|
import Tooltip from '@/app/components/base/tooltip'
|
||||||
import Loading from '@/app/components/base/loading'
|
import Loading from '@/app/components/base/loading'
|
||||||
import Confirm from '@/app/components/base/confirm'
|
import Confirm from '@/app/components/base/confirm'
|
||||||
import I18n from '@/context/i18n'
|
import useTimestamp from '@/hooks/use-timestamp'
|
||||||
import { LanguagesSupported } from '@/i18n/language'
|
|
||||||
import { useAppContext } from '@/context/app-context'
|
import { useAppContext } from '@/context/app-context'
|
||||||
|
|
||||||
type ISecretKeyModalProps = {
|
type ISecretKeyModalProps = {
|
||||||
@ -42,6 +40,7 @@ const SecretKeyModal = ({
|
|||||||
onClose,
|
onClose,
|
||||||
}: ISecretKeyModalProps) => {
|
}: ISecretKeyModalProps) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
const { formatTime } = useTimestamp()
|
||||||
const { currentWorkspace, isCurrentWorkspaceManager } = useAppContext()
|
const { currentWorkspace, isCurrentWorkspaceManager } = useAppContext()
|
||||||
const [showConfirmDelete, setShowConfirmDelete] = useState(false)
|
const [showConfirmDelete, setShowConfirmDelete] = useState(false)
|
||||||
const [isVisible, setVisible] = useState(false)
|
const [isVisible, setVisible] = useState(false)
|
||||||
@ -55,9 +54,6 @@ const SecretKeyModal = ({
|
|||||||
|
|
||||||
const [delKeyID, setDelKeyId] = useState('')
|
const [delKeyID, setDelKeyId] = useState('')
|
||||||
|
|
||||||
const { locale } = useContext(I18n)
|
|
||||||
|
|
||||||
// const [isCopied, setIsCopied] = useState(false)
|
|
||||||
const [copyValue, setCopyValue] = useState('')
|
const [copyValue, setCopyValue] = useState('')
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -100,13 +96,6 @@ const SecretKeyModal = ({
|
|||||||
return `${token.slice(0, 3)}...${token.slice(-20)}`
|
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 (
|
return (
|
||||||
<Modal isShow={isShow} onClose={onClose} title={`${t('appApi.apiKeyModal.apiSecretKey')}`} className={`${s.customModal} px-8 flex flex-col`}>
|
<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} />
|
<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 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 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 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-[200px]'>{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.lastUsed')}</div>
|
||||||
<div className='flex-grow px-3'></div>
|
<div className='flex-grow px-3'></div>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex-grow overflow-auto'>
|
<div className='flex-grow overflow-auto'>
|
||||||
{apiKeysList.data.map(api => (
|
{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 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 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-[200px]'>{formatTime(Number(api.created_at), t('appLog.dateTimeFormat') as string)}</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-[200px]'>{api.last_used_at ? formatTime(Number(api.created_at), t('appLog.dateTimeFormat') as string) : t('appApi.never')}</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 flex-grow px-3'>
|
<div className='flex flex-grow px-3'>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
selector={`key-${api.token}`}
|
selector={`key-${api.token}`}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
.customModal {
|
.customModal {
|
||||||
max-width: 40rem !important;
|
max-width: 800px !important;
|
||||||
max-height: calc(100vh - 80px);
|
max-height: calc(100vh - 80px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
import { memo } from 'react'
|
import { memo } from 'react'
|
||||||
import dayjs from 'dayjs'
|
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useWorkflow } from '../hooks'
|
import { useWorkflow } from '../hooks'
|
||||||
import { useStore } from '@/app/components/workflow/store'
|
import { useStore } from '@/app/components/workflow/store'
|
||||||
|
import useTimestamp from '@/hooks/use-timestamp'
|
||||||
|
|
||||||
const EditingTitle = () => {
|
const EditingTitle = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
const { formatTime } = useTimestamp()
|
||||||
const { formatTimeFromNow } = useWorkflow()
|
const { formatTimeFromNow } = useWorkflow()
|
||||||
const draftUpdatedAt = useStore(state => state.draftUpdatedAt)
|
const draftUpdatedAt = useStore(state => state.draftUpdatedAt)
|
||||||
const publishedAt = useStore(state => state.publishedAt)
|
const publishedAt = useStore(state => state.publishedAt)
|
||||||
@ -15,7 +16,7 @@ const EditingTitle = () => {
|
|||||||
{
|
{
|
||||||
!!draftUpdatedAt && (
|
!!draftUpdatedAt && (
|
||||||
<>
|
<>
|
||||||
{t('workflow.common.autoSaved')} {dayjs(draftUpdatedAt).format('HH:mm:ss')}
|
{t('workflow.common.autoSaved')} {formatTime(draftUpdatedAt / 1000, 'HH:mm:ss')}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
// import cn from 'classnames'
|
import useTimestamp from '@/hooks/use-timestamp'
|
||||||
import dayjs from 'dayjs'
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
status: string
|
status: string
|
||||||
@ -24,6 +23,7 @@ const MetaData: FC<Props> = ({
|
|||||||
showSteps = true,
|
showSteps = true,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
const { formatTime } = useTimestamp()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='relative'>
|
<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)]'/>
|
<div className='my-[5px] w-[72px] h-2 rounded-sm bg-[rgba(0,0,0,0.05)]'/>
|
||||||
)}
|
)}
|
||||||
{status !== 'running' && (
|
{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>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import dayjs from 'dayjs'
|
|
||||||
import { formatFileSize, formatNumber, formatTime } from '@/utils/format'
|
import { formatFileSize, formatNumber, formatTime } from '@/utils/format'
|
||||||
import type { DocType } from '@/models/datasets'
|
import type { DocType } from '@/models/datasets'
|
||||||
|
import useTimestamp from '@/hooks/use-timestamp'
|
||||||
|
|
||||||
export type inputType = 'input' | 'select' | 'textarea'
|
export type inputType = 'input' | 'select' | 'textarea'
|
||||||
export type metadataType = DocType | 'originInfo' | 'technicalParameters'
|
export type metadataType = DocType | 'originInfo' | 'technicalParameters'
|
||||||
@ -31,6 +31,8 @@ const fieldPrefix = 'datasetDocuments.metadata.field'
|
|||||||
|
|
||||||
export const useMetadataMap = (): MetadataMap => {
|
export const useMetadataMap = (): MetadataMap => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
const { formatTime: formatTimestamp } = useTimestamp()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
book: {
|
book: {
|
||||||
text: t('datasetDocuments.metadata.type.book'),
|
text: t('datasetDocuments.metadata.type.book'),
|
||||||
@ -230,11 +232,11 @@ export const useMetadataMap = (): MetadataMap => {
|
|||||||
},
|
},
|
||||||
'created_at': {
|
'created_at': {
|
||||||
label: t(`${fieldPrefix}.originInfo.uploadDate`),
|
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': {
|
'completed_at': {
|
||||||
label: t(`${fieldPrefix}.originInfo.lastUpdateDate`),
|
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': {
|
'data_source_type': {
|
||||||
label: t(`${fieldPrefix}.originInfo.source`),
|
label: t(`${fieldPrefix}.originInfo.source`),
|
||||||
|
21
web/hooks/use-timestamp.ts
Normal file
21
web/hooks/use-timestamp.ts
Normal 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
|
Loading…
Reference in New Issue
Block a user