mirror of
https://github.com/langgenius/dify.git
synced 2024-11-16 03:32:23 +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 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')} {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>
|
||||
)
|
||||
: undefined
|
||||
}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
</Drawer>
|
||||
/>
|
||||
<DeleteConfirmModal
|
||||
isShow={showModal}
|
||||
onHide={() => setShowModal(false)}
|
||||
|
|
|
@ -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 */}
|
||||
|
|
|
@ -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')} {dayjs(createdAt * 1000).format('YYYY-MM-DD HH:mm')}</div>
|
||||
<div>{t('appAnnotation.editModal.createdAt')} {formatTime(createdAt, t('appLog.dateTimeFormat') as string)}</div>
|
||||
</div>
|
||||
)
|
||||
: undefined}
|
||||
>
|
||||
</Drawer>
|
||||
/>
|
||||
<DeleteConfirmModal
|
||||
isShow={showModal}
|
||||
onHide={() => setShowModal(false)}
|
||||
|
|
|
@ -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)}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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'>
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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>
|
||||
{
|
||||
|
|
|
@ -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>
|
||||
})}
|
||||
|
|
|
@ -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}`}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
.customModal {
|
||||
max-width: 40rem !important;
|
||||
max-width: 800px !important;
|
||||
max-height: calc(100vh - 80px);
|
||||
}
|
||||
|
||||
|
|
|
@ -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')}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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`),
|
||||
|
|
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