| @@ -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`), | |||
| @@ -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 | |||