From 54bb309d87d2ece783589eca2829a6f3f8818bac Mon Sep 17 00:00:00 2001 From: Joel Date: Tue, 30 May 2023 15:09:25 +0800 Subject: [PATCH 1/3] fix: remove sentry for community edtion and dev (#259) --- web/next.config.js | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/web/next.config.js b/web/next.config.js index 3851933bc9..bb1b73f05f 100644 --- a/web/next.config.js +++ b/web/next.config.js @@ -1,5 +1,10 @@ const { withSentryConfig } = require('@sentry/nextjs') +const EDITION = process.env.NEXT_PUBLIC_EDITION +const IS_CE_EDITION = EDITION === 'SELF_HOSTED' +const isDevelopment = process.env.NODE_ENV === 'development' +const isHideSentry = isDevelopment || IS_CE_EDITION + const withMDX = require('@next/mdx')({ extension: /\.mdx?$/, options: { @@ -31,9 +36,6 @@ const nextConfig = { // https://nextjs.org/docs/api-reference/next.config.js/ignoring-typescript-errors ignoreBuildErrors: true, }, - sentry: { - hideSourceMaps: true, - }, async redirects() { return [ { @@ -43,6 +45,13 @@ const nextConfig = { }, ] }, + ...(isHideSentry + ? {} + : { + sentry: { + hideSourceMaps: true, + }, + }), } // https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup @@ -54,8 +63,7 @@ const sentryWebpackPluginOptions = { assets: './**', ignore: ['./node_modules/**'], }, - // https://github.com/getsentry/sentry-webpack-plugin#options. } -module.exports = withMDX(withSentryConfig(nextConfig, sentryWebpackPluginOptions)) +module.exports = isHideSentry ? withMDX(nextConfig) : withMDX(withSentryConfig(nextConfig, sentryWebpackPluginOptions)) From 91bcbd0b268ffe99686b9c13f9998719fe18e1da Mon Sep 17 00:00:00 2001 From: Joel Date: Tue, 30 May 2023 15:26:26 +0800 Subject: [PATCH 2/3] fix: svg attr in ts file (#260) --- .../[datasetId]/layout.tsx | 66 +++++++------- web/app/components/app-sidebar/basic.tsx | 26 +++--- web/app/components/app/log/index.tsx | 20 ++--- .../saved-items/no-data/index.tsx | 13 +-- web/app/components/base/input/index.tsx | 2 +- .../documents/detail/embedding/index.tsx | 86 +++++++++---------- .../components/datasets/documents/index.tsx | 9 +- .../components/datasets/documents/list.tsx | 70 +++++++-------- web/app/components/explore/app-card/index.tsx | 19 ++-- web/app/components/explore/sidebar/index.tsx | 49 +++++------ 10 files changed, 182 insertions(+), 178 deletions(-) diff --git a/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout.tsx b/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout.tsx index 48b0bbc9c5..5b16a827bd 100644 --- a/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout.tsx +++ b/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout.tsx @@ -1,14 +1,14 @@ 'use client' import type { FC } from 'react' import React, { useEffect } from 'react' -import { usePathname, useSelectedLayoutSegments } from 'next/navigation' +import { usePathname } from 'next/navigation' import useSWR from 'swr' import { useTranslation } from 'react-i18next' -import { getLocaleOnClient } from '@/i18n/client' import { Cog8ToothIcon, // CommandLineIcon, Squares2X2Icon, + // eslint-disable-next-line sort-imports PuzzlePieceIcon, DocumentTextIcon, } from '@heroicons/react/24/outline' @@ -18,9 +18,10 @@ import { DocumentTextIcon as DocumentTextSolidIcon, } from '@heroicons/react/24/solid' import Link from 'next/link' +import s from './style.module.css' import { fetchDataDetail, fetchDatasetRelatedApps } from '@/service/datasets' import type { RelatedApp } from '@/models/datasets' -import s from './style.module.css' +import { getLocaleOnClient } from '@/i18n/client' import AppSideBar from '@/app/components/app-sidebar' import Divider from '@/app/components/base/divider' import Indicator from '@/app/components/header/indicator' @@ -38,7 +39,7 @@ export type IAppDetailLayoutProps = { const LikedItem: FC<{ type?: 'plugin' | 'app'; appStatus?: boolean; detail: RelatedApp }> = ({ type = 'app', appStatus = true, - detail + detail, }) => { return ( @@ -58,7 +59,7 @@ const LikedItem: FC<{ type?: 'plugin' | 'app'; appStatus?: boolean; detail: Rela const TargetIcon: FC<{ className?: string }> = ({ className }) => { return - + @@ -79,7 +80,7 @@ const TargetSolidIcon: FC<{ className?: string }> = ({ className }) => { const BookOpenIcon: FC<{ className?: string }> = ({ className }) => { return - + } @@ -109,9 +110,8 @@ const DatasetDetailLayout: FC = (props) => { ] useEffect(() => { - if (datasetRes) { + if (datasetRes) document.title = `${datasetRes.name || 'Dataset'} - Dify` - } }, [datasetRes]) const ExtraInfo: FC = () => { @@ -119,32 +119,34 @@ const DatasetDetailLayout: FC = (props) => { return
- {relatedApps?.data?.length ? ( - <> -
{relatedApps?.total || '--'} {t('common.datasetMenus.relatedApp')}
- {relatedApps?.data?.map((item) => ())} - - ) : ( -
-
-
- -
-
- + {relatedApps?.data?.length + ? ( + <> +
{relatedApps?.total || '--'} {t('common.datasetMenus.relatedApp')}
+ {relatedApps?.data?.map(item => ())} + + ) + : ( +
+
+
+ +
+
+ +
+
{t('common.datasetMenus.emptyTip')}
+ + + {t('common.datasetMenus.viewDoc')} +
-
{t('common.datasetMenus.emptyTip')}
- - - {t('common.datasetMenus.viewDoc')} - -
- )} + )}
} diff --git a/web/app/components/app-sidebar/basic.tsx b/web/app/components/app-sidebar/basic.tsx index 55094c6190..1a8c0f03f1 100644 --- a/web/app/components/app-sidebar/basic.tsx +++ b/web/app/components/app-sidebar/basic.tsx @@ -15,8 +15,8 @@ export function randomString(length: number) { export type IAppBasicProps = { iconType?: 'app' | 'api' | 'dataset' - icon?: string, - icon_background?: string, + icon?: string + icon_background?: string name: string type: string | React.ReactNode hoverTip?: string @@ -24,12 +24,12 @@ export type IAppBasicProps = { } const AlgorithmSvg = - - - - - - + + + + + + const DatasetSvg = @@ -37,9 +37,9 @@ const DatasetSvg = const ICON_MAP = { - 'app': , - 'api': , - 'dataset': + app: , + api: , + dataset: , } export default function AppBasic({ icon, icon_background, name, type, hoverTip, textStyle, iconType = 'app' }: IAppBasicProps) { @@ -50,8 +50,8 @@ export default function AppBasic({ icon, icon_background, name, type, hoverTip,
)} - {iconType !== 'app' && -
+ {iconType !== 'app' + &&
{ICON_MAP[iconType]}
diff --git a/web/app/components/app/log/index.tsx b/web/app/components/app/log/index.tsx index eb4dbdd636..6930000a26 100644 --- a/web/app/components/app/log/index.tsx +++ b/web/app/components/app/log/index.tsx @@ -31,7 +31,7 @@ const limit = 10 const ThreeDotsIcon: FC<{ className?: string }> = ({ className }) => { return - + } @@ -63,9 +63,9 @@ const Logs: FC = ({ appId }) => { limit, ...(queryParams.period !== 'all' ? { - start: dayjs().subtract(queryParams.period as number, 'day').format('YYYY-MM-DD HH:mm'), - end: dayjs().format('YYYY-MM-DD HH:mm'), - } + start: dayjs().subtract(queryParams.period as number, 'day').format('YYYY-MM-DD HH:mm'), + end: dayjs().format('YYYY-MM-DD HH:mm'), + } : {}), ...omit(queryParams, ['period']), } @@ -77,16 +77,16 @@ const Logs: FC = ({ appId }) => { // When the details are obtained, proceed to the next request const { data: chatConversations, mutate: mutateChatList } = useSWR(() => isChatMode ? { - url: `/apps/${appId}/chat-conversations`, - params: query, - } + url: `/apps/${appId}/chat-conversations`, + params: query, + } : null, fetchChatConversations) const { data: completionConversations, mutate: mutateCompletionList } = useSWR(() => !isChatMode ? { - url: `/apps/${appId}/completion-conversations`, - params: query, - } + url: `/apps/${appId}/completion-conversations`, + params: query, + } : null, fetchCompletionConversations) const total = isChatMode ? chatConversations?.total : completionConversations?.total diff --git a/web/app/components/app/text-generate/saved-items/no-data/index.tsx b/web/app/components/app/text-generate/saved-items/no-data/index.tsx index bd7fe29bc1..f11740b920 100644 --- a/web/app/components/app/text-generate/saved-items/no-data/index.tsx +++ b/web/app/components/app/text-generate/saved-items/no-data/index.tsx @@ -1,24 +1,25 @@ 'use client' -import React, { FC } from 'react' +import type { FC } from 'react' +import React from 'react' import { useTranslation } from 'react-i18next' -import Button from '@/app/components/base/button' import { PlusIcon } from '@heroicons/react/24/outline' -export interface INoDataProps { +import Button from '@/app/components/base/button' +export type INoDataProps = { onStartCreateContent: () => void } const markIcon = ( - + ) const lightIcon = ( - + ) const NoData: FC = ({ - onStartCreateContent + onStartCreateContent, }) => { const { t } = useTranslation() diff --git a/web/app/components/base/input/index.tsx b/web/app/components/base/input/index.tsx index eff2cee749..de2db34944 100644 --- a/web/app/components/base/input/index.tsx +++ b/web/app/components/base/input/index.tsx @@ -18,7 +18,7 @@ type InputProps = { const GlassIcon: FC<{ className?: string }> = ({ className }) => ( - + ) diff --git a/web/app/components/datasets/documents/detail/embedding/index.tsx b/web/app/components/datasets/documents/detail/embedding/index.tsx index 40ce2713e7..720400f5c6 100644 --- a/web/app/components/datasets/documents/detail/embedding/index.tsx +++ b/web/app/components/datasets/documents/detail/embedding/index.tsx @@ -1,28 +1,27 @@ -import { FC, useCallback, useMemo, useState } from 'react' -import React from 'react' +import type { FC } from 'react' +import React, { useCallback, useMemo, useState } from 'react' import useSWR from 'swr' import { useRouter } from 'next/navigation' import { useContext } from 'use-context-selector' import { useTranslation } from 'react-i18next' import { omit } from 'lodash-es' import cn from 'classnames' +import { ArrowRightIcon } from '@heroicons/react/24/solid' +import SegmentCard from '../completed/SegmentCard' +import { FieldInfo } from '../metadata' +import style from '../completed/style.module.css' +import { DocumentContext } from '../index' +import s from './style.module.css' import Button from '@/app/components/base/button' import Divider from '@/app/components/base/divider' -import Loading from '@/app/components/base/loading' import { ToastContext } from '@/app/components/base/toast' -import { FullDocumentDetail, ProcessRuleResponse } from '@/models/datasets' +import type { FullDocumentDetail, ProcessRuleResponse } from '@/models/datasets' import type { CommonResponse } from '@/models/common' import { asyncRunSafe } from '@/utils' import { formatNumber } from '@/utils/format' -import { fetchProcessRule, fetchIndexingEstimate, fetchIndexingStatus, pauseDocIndexing, resumeDocIndexing } from '@/service/datasets' -import SegmentCard from '../completed/SegmentCard' -import { FieldInfo } from '../metadata' -import s from './style.module.css' -import style from '../completed/style.module.css' -import { DocumentContext } from '../index' +import { fetchIndexingEstimate, fetchIndexingStatus, fetchProcessRule, pauseDocIndexing, resumeDocIndexing } from '@/service/datasets' import DatasetDetailContext from '@/context/dataset-detail' import StopEmbeddingModal from '@/app/components/datasets/create/stop-embedding-modal' -import { ArrowRightIcon } from '@heroicons/react/24/solid' type Props = { detail?: FullDocumentDetail @@ -35,7 +34,7 @@ type Props = { const StopIcon: FC<{ className?: string }> = ({ className }) => { return - + @@ -47,9 +46,8 @@ const StopIcon: FC<{ className?: string }> = ({ className }) => { const ResumeIcon: FC<{ className?: string }> = ({ className }) => { return - + - } const RuleDetail: FC<{ sourceData?: ProcessRuleResponse; docName?: string }> = ({ sourceData, docName }) => { @@ -61,43 +59,43 @@ const RuleDetail: FC<{ sourceData?: ProcessRuleResponse; docName?: string }> = ( segmentLength: t('datasetDocuments.embedding.segmentLength'), textCleaning: t('datasetDocuments.embedding.textCleaning'), } + + const getRuleName = (key: string) => { + if (key === 'remove_extra_spaces') + return t('datasetCreation.stepTwo.removeExtraSpaces') + + if (key === 'remove_urls_emails') + return t('datasetCreation.stepTwo.removeUrlEmails') + + if (key === 'remove_stopwords') + return t('datasetCreation.stepTwo.removeStopwords') + } + const getValue = useCallback((field: string) => { - let value: string | number | undefined = '-'; + let value: string | number | undefined = '-' switch (field) { case 'docName': value = docName - break; + break case 'mode': - value = sourceData?.mode === 'automatic' ? (t('datasetDocuments.embedding.automatic') as string) : (t('datasetDocuments.embedding.custom') as string); - break; + value = sourceData?.mode === 'automatic' ? (t('datasetDocuments.embedding.automatic') as string) : (t('datasetDocuments.embedding.custom') as string) + break case 'segmentLength': value = sourceData?.rules?.segmentation?.max_tokens - break; + break default: - value = sourceData?.mode === 'automatic' ? - (t('datasetDocuments.embedding.automatic') as string) : - sourceData?.rules?.pre_processing_rules?.map(rule => { - if (rule.enabled) { + value = sourceData?.mode === 'automatic' + ? (t('datasetDocuments.embedding.automatic') as string) + // eslint-disable-next-line array-callback-return + : sourceData?.rules?.pre_processing_rules?.map((rule) => { + if (rule.enabled) return getRuleName(rule.id) - } }).filter(Boolean).join(';') - break; + break } return value }, [sourceData, docName]) - const getRuleName = (key: string) => { - if (key === 'remove_extra_spaces') { - return t('datasetCreation.stepTwo.removeExtraSpaces') - } - if (key === 'remove_urls_emails') { - return t('datasetCreation.stepTwo.removeUrlEmails') - } - if (key === 'remove_stopwords') { - return t('datasetCreation.stepTwo.removeStopwords') - } - } - return
{Object.keys(segmentationRuleMap).map((field) => { return = ({ detail, stopPosition = 'top', datasetId: d datasetId: localDatasetId, documentId: localDocumentId, }, apiParams => fetchIndexingEstimate(omit(apiParams, 'action')), { - revalidateOnFocus: false + revalidateOnFocus: false, }) const { data: ruleDetail, error: ruleError } = useSWR({ action: 'fetchProcessRule', - params: { documentId: localDocumentId } + params: { documentId: localDocumentId }, }, apiParams => fetchProcessRule(omit(apiParams, 'action')), { revalidateOnFocus: false, }) @@ -159,7 +157,8 @@ const EmbeddingDetail: FC = ({ detail, stopPosition = 'top', datasetId: d const percent = useMemo(() => { const completedCount = indexingStatusDetail?.completed_segments || 0 const totalCount = indexingStatusDetail?.total_segments || 0 - if (totalCount === 0) return 0 + if (totalCount === 0) + return 0 const percent = Math.round(completedCount * 100 / totalCount) return percent > 100 ? 100 : percent }, [indexingStatusDetail]) @@ -170,7 +169,8 @@ const EmbeddingDetail: FC = ({ detail, stopPosition = 'top', datasetId: d if (!e) { notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') }) statusMutate() - } else { + } + else { notify({ type: 'error', message: t('common.actionMsg.modificationFailed') }) } } @@ -211,7 +211,7 @@ const EmbeddingDetail: FC = ({ detail, stopPosition = 'top', datasetId: d (isEmbeddingPaused || isEmbeddingError) && s.barPaused, indexingStatusDetail?.indexing_status === 'completed' && 'rounded-r-md') } - style={{ width: `${percent}%` }} + style={{ width: `${percent}%` }} />
@@ -255,7 +255,7 @@ const EmbeddingDetail: FC = ({ detail, stopPosition = 'top', datasetId: d
{t('datasetDocuments.embedding.previewTip')}
- {[1, 2, 3].map((v) => ( + {[1, 2, 3].map(v => ( ))}
diff --git a/web/app/components/datasets/documents/index.tsx b/web/app/components/datasets/documents/index.tsx index bd7a9f56f4..8fa1e74ebd 100644 --- a/web/app/components/datasets/documents/index.tsx +++ b/web/app/components/datasets/documents/index.tsx @@ -1,13 +1,12 @@ 'use client' import type { FC } from 'react' -import React, { useState, useMemo } from 'react' +import React, { useMemo, useState } from 'react' import useSWR from 'swr' import { useTranslation } from 'react-i18next' import { useRouter } from 'next/navigation' -import { debounce } from 'lodash-es' +import { debounce, omit } from 'lodash-es' // import Link from 'next/link' import { PlusIcon } from '@heroicons/react/24/solid' -import { omit } from 'lodash-es' import List from './list' import s from './style.module.css' import Loading from '@/app/components/base/loading' @@ -22,13 +21,13 @@ const limit = 15 const FolderPlusIcon: FC<{ className?: string }> = ({ className }) => { return - + } const ThreeDotsIcon: FC<{ className?: string }> = ({ className }) => { return - + } diff --git a/web/app/components/datasets/documents/list.tsx b/web/app/components/datasets/documents/list.tsx index f53c63f032..b77e305a07 100644 --- a/web/app/components/datasets/documents/list.tsx +++ b/web/app/components/datasets/documents/list.tsx @@ -1,13 +1,16 @@ +/* eslint-disable no-mixed-operators */ 'use client' import type { FC } from 'react' -import React, { useState, useEffect } from 'react' -import { TrashIcon, ArrowDownIcon } from '@heroicons/react/24/outline' +import React, { useEffect, useState } from 'react' +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 s from './style.module.css' import Switch from '@/app/components/base/switch' import Divider from '@/app/components/base/divider' import Popover from '@/app/components/base/popover' @@ -20,26 +23,24 @@ import Indicator from '@/app/components/header/indicator' import { asyncRunSafe } from '@/utils' import { formatNumber } from '@/utils/format' import { archiveDocument, deleteDocument, disableDocument, enableDocument } from '@/service/datasets' -import type { DocumentListResponse, DocumentDisplayStatus } from '@/models/datasets' +import type { DocumentDisplayStatus, DocumentListResponse } from '@/models/datasets' import type { CommonResponse } from '@/models/common' -import cn from 'classnames' -import s from './style.module.css' export const SettingsIcon: FC<{ className?: string }> = ({ className }) => { return - + } export const FilePlusIcon: FC<{ className?: string }> = ({ className }) => { return - + } export const ArchiveIcon: FC<{ className?: string }> = ({ className }) => { return - + } @@ -59,12 +60,12 @@ export const useIndexStatus = () => { // status item for list export const StatusItem: FC<{ - status: DocumentDisplayStatus; - reverse?: boolean; + status: DocumentDisplayStatus + reverse?: boolean scene?: 'list' | 'detail' textCls?: string }> = ({ status, reverse = false, scene = 'list', textCls = '' }) => { - const DOC_INDEX_STATUS_MAP = useIndexStatus(); + const DOC_INDEX_STATUS_MAP = useIndexStatus() const localStatus = status.toLowerCase() as keyof typeof DOC_INDEX_STATUS_MAP return
void scene?: 'list' | 'detail' className?: string @@ -95,7 +96,7 @@ export const OperationAction: FC<{ const { notify } = useContext(ToastContext) const { t } = useTranslation() - const isListScene = scene === 'list'; + const isListScene = scene === 'list' const onOperate = async (operationName: OperationName) => { let opApi = deleteDocument @@ -123,16 +124,16 @@ export const OperationAction: FC<{ return
e.stopPropagation()} + onClick={e => e.stopPropagation()} > {isListScene && <> - {archived ? - + {archived + ?
{ }} disabled={true} size='md' />
-
: - onOperate(v ? 'enable' : 'disable')} size='md' /> +
+ : onOperate(v ? 'enable' : 'disable')} size='md' /> } } @@ -187,7 +188,7 @@ export const OperationAction: FC<{ trigger='click' position='br' btnElement={
} - btnClassName={(open) => cn(isListScene ? s.actionIconWrapperList : s.actionIconWrapperDetail, open ? '!bg-gray-100 !shadow-none' : '!bg-transparent')} + btnClassName={open => cn(isListScene ? s.actionIconWrapperList : s.actionIconWrapperDetail, open ? '!bg-gray-100 !shadow-none' : '!bg-transparent')} className={`!w-[200px] h-fit !z-20 ${className}`} /> {showModal && setShowModal(false)} className={s.delModal} closable> @@ -221,12 +222,12 @@ export const renderTdValue = (value: string | number | null, isEmptyStyle = fals } const renderCount = (count: number | undefined) => { - if (!count) { + if (!count) return renderTdValue(0, true) - } - if (count < 1000) { - return count; - } + + if (count < 1000) + return count + return `${formatNumber((count / 1000).toFixed(1))}k` } @@ -242,20 +243,21 @@ type IDocumentListProps = { const DocumentList: FC = ({ documents = [], datasetId, onUpdate }) => { const { t } = useTranslation() const router = useRouter() - const [localDocs, setLocalDocs] = useState(documents); - const [enableSort, setEnableSort] = useState(false); + const [localDocs, setLocalDocs] = useState(documents) + const [enableSort, setEnableSort] = useState(false) useEffect(() => { setLocalDocs(documents) }, [documents]) const onClickSort = () => { - setEnableSort(!enableSort); + setEnableSort(!enableSort) if (!enableSort) { - const sortedDocs = [...localDocs].sort((a, b) => dayjs(a.created_at).isBefore(dayjs(b.created_at)) ? -1 : 1); - setLocalDocs(sortedDocs); - } else { - setLocalDocs(documents); + const sortedDocs = [...localDocs].sort((a, b) => dayjs(a.created_at).isBefore(dayjs(b.created_at)) ? -1 : 1) + setLocalDocs(sortedDocs) + } + else { + setLocalDocs(documents) } } @@ -290,7 +292,7 @@ const DocumentList: FC = ({ documents = [], datasetId, onUpd {doc.position}
- {doc?.name?.replace(/\.[^/.]+$/, "")}.{suffix} + {doc?.name?.replace(/\.[^/.]+$/, '')}.{suffix} {renderCount(doc.word_count)} {renderCount(doc.hit_count)} diff --git a/web/app/components/explore/app-card/index.tsx b/web/app/components/explore/app-card/index.tsx index ff56b457f2..24f38d666b 100644 --- a/web/app/components/explore/app-card/index.tsx +++ b/web/app/components/explore/app-card/index.tsx @@ -1,25 +1,24 @@ 'use client' import cn from 'classnames' import { useTranslation } from 'react-i18next' -import { App } from '@/models/explore' -import AppModeLabel from '@/app/(commonLayout)/apps/AppModeLabel' -import AppIcon from '@/app/components/base/app-icon' import { PlusIcon } from '@heroicons/react/20/solid' import Button from '../../base/button' - import s from './style.module.css' +import type { App } from '@/models/explore' +import AppModeLabel from '@/app/(commonLayout)/apps/AppModeLabel' +import AppIcon from '@/app/components/base/app-icon' const CustomizeBtn = ( - + ) export type AppCardProps = { - app: App, - canCreate: boolean, - onCreate: () => void, - onAddToWorkspace: (appId: string) => void, + app: App + canCreate: boolean + onCreate: () => void + onAddToWorkspace: (appId: string) => void } const AppCard = ({ @@ -29,7 +28,7 @@ const AppCard = ({ onAddToWorkspace, }: AppCardProps) => { const { t } = useTranslation() - const {app: appBasicInfo} = app + const { app: appBasicInfo } = app return (
diff --git a/web/app/components/explore/sidebar/index.tsx b/web/app/components/explore/sidebar/index.tsx index 2d44676100..5a9df13771 100644 --- a/web/app/components/explore/sidebar/index.tsx +++ b/web/app/components/explore/sidebar/index.tsx @@ -1,14 +1,15 @@ 'use client' -import React, { FC, useEffect, useState } from 'react' +import type { FC } from 'react' +import React, { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' -import ExploreContext from '@/context/explore-context' import cn from 'classnames' import { useSelectedLayoutSegments } from 'next/navigation' import Link from 'next/link' -import Item from './app-nav-item' -import { fetchInstalledAppList as doFetchInstalledAppList, uninstallApp, updatePinStatus } from '@/service/explore' import Toast from '../../base/toast' +import Item from './app-nav-item' +import { fetchInstalledAppList as doFetchInstalledAppList, uninstallApp, updatePinStatus } from '@/service/explore' +import ExploreContext from '@/context/explore-context' import Confirm from '@/app/components/base/confirm' const SelectedDiscoveryIcon = () => ( @@ -19,12 +20,12 @@ const SelectedDiscoveryIcon = () => ( const DiscoveryIcon = () => ( - + ) const SideBar: FC<{ - controlUpdateInstalledApps: number, + controlUpdateInstalledApps: number }> = ({ controlUpdateInstalledApps, }) => { @@ -35,10 +36,10 @@ const SideBar: FC<{ const { installedApps, setInstalledApps } = useContext(ExploreContext) const fetchInstalledAppList = async () => { - const {installed_apps} : any = await doFetchInstalledAppList() + const { installed_apps }: any = await doFetchInstalledAppList() setInstalledApps(installed_apps) } - + const [showConfirm, setShowConfirm] = useState(false) const [currId, setCurrId] = useState('') const handleDelete = async () => { @@ -47,7 +48,7 @@ const SideBar: FC<{ setShowConfirm(false) Toast.notify({ type: 'success', - message: t('common.api.remove') + message: t('common.api.remove'), }) fetchInstalledAppList() } @@ -56,7 +57,7 @@ const SideBar: FC<{ await updatePinStatus(id, isPinned) Toast.notify({ type: 'success', - message: t('common.api.success') + message: t('common.api.success'), }) fetchInstalledAppList() } @@ -74,8 +75,8 @@ const SideBar: FC<{
{isDiscoverySelected ? : }
{t('explore.sidebar.discovery')}
@@ -86,12 +87,12 @@ const SideBar: FC<{
{t('explore.sidebar.workspace')}
- {installedApps.map(({id, is_pinned, uninstallable, app : { name, icon, icon_background }}) => { + {installedApps.map(({ id, is_pinned, uninstallable, app: { name, icon, icon_background } }) => { return ( - )} {showConfirm && ( - setShowConfirm(false)} - onConfirm={handleDelete} - onCancel={() => setShowConfirm(false)} - /> - )} + setShowConfirm(false)} + onConfirm={handleDelete} + onCancel={() => setShowConfirm(false)} + /> + )}
) } From 5e772bd10b678759f4cadfbfe388bd3755f4bdef Mon Sep 17 00:00:00 2001 From: Joel Date: Tue, 30 May 2023 16:15:08 +0800 Subject: [PATCH 3/3] fix: stop response btn hide messages (#261) --- .../app/configuration/debug/index.tsx | 95 ++++++------ web/app/components/share/chat/index.tsx | 144 +++++++++--------- 2 files changed, 119 insertions(+), 120 deletions(-) diff --git a/web/app/components/app/configuration/debug/index.tsx b/web/app/components/app/configuration/debug/index.tsx index a4d19be265..363cb4c3e3 100644 --- a/web/app/components/app/configuration/debug/index.tsx +++ b/web/app/components/app/configuration/debug/index.tsx @@ -1,36 +1,36 @@ 'use client' import type { FC } from 'react' import { useTranslation } from 'react-i18next' -import React, { useEffect, useState, useRef } from 'react' +import React, { useEffect, useRef, useState } from 'react' import cn from 'classnames' import produce from 'immer' import { useBoolean, useGetState } from 'ahooks' import { useContext } from 'use-context-selector' +import dayjs from 'dayjs' +import HasNotSetAPIKEY from '../base/warning-mask/has-not-set-api' +import FormattingChanged from '../base/warning-mask/formatting-changed' +import GroupName from '../base/group-name' import { AppType } from '@/types/app' import PromptValuePanel, { replaceStringWithValues } from '@/app/components/app/configuration/prompt-value-panel' import type { IChatItem } from '@/app/components/app/chat' import Chat from '@/app/components/app/chat' import ConfigContext from '@/context/debug-configuration' import { ToastContext } from '@/app/components/base/toast' -import { sendChatMessage, sendCompletionMessage, fetchSuggestedQuestions, fetchConvesationMessages } from '@/service/debug' +import { fetchConvesationMessages, fetchSuggestedQuestions, sendChatMessage, sendCompletionMessage } from '@/service/debug' import Button from '@/app/components/base/button' import type { ModelConfig as BackendModelConfig } from '@/types/app' import { promptVariablesToUserInputsForm } from '@/utils/model-config' -import HasNotSetAPIKEY from '../base/warning-mask/has-not-set-api' -import FormattingChanged from '../base/warning-mask/formatting-changed' import TextGeneration from '@/app/components/app/text-generate/item' -import GroupName from '../base/group-name' -import dayjs from 'dayjs' import { IS_CE_EDITION } from '@/config' -interface IDebug { +type IDebug = { hasSetAPIKEY: boolean onSetting: () => void } const Debug: FC = ({ hasSetAPIKEY = true, - onSetting + onSetting, }) => { const { t } = useTranslation() const { @@ -51,14 +51,12 @@ const Debug: FC = ({ completionParams, } = useContext(ConfigContext) - const [chatList, setChatList, getChatList] = useGetState([]) const chatListDomRef = useRef(null) useEffect(() => { // scroll to bottom - if (chatListDomRef.current) { + if (chatListDomRef.current) chatListDomRef.current.scrollTop = chatListDomRef.current.scrollHeight - } }, [chatList]) const getIntroduction = () => replaceStringWithValues(introduction, modelConfig.configs.prompt_variables, inputs) @@ -68,7 +66,7 @@ const Debug: FC = ({ id: `${Date.now()}`, content: getIntroduction(), isAnswer: true, - isOpeningStatement: true + isOpeningStatement: true, }]) } }, [introduction, modelConfig.configs.prompt_variables, inputs]) @@ -76,11 +74,12 @@ const Debug: FC = ({ const [isResponsing, { setTrue: setResponsingTrue, setFalse: setResponsingFalse }] = useBoolean(false) const [abortController, setAbortController] = useState(null) const [isShowFormattingChangeConfirm, setIsShowFormattingChangeConfirm] = useState(false) + const [isShowSuggestion, setIsShowSuggestion] = useState(false) useEffect(() => { - if (formattingChanged && chatList.some(item => !item.isAnswer)) { + if (formattingChanged && chatList.some(item => !item.isAnswer)) setIsShowFormattingChangeConfirm(true) - } + setFormattingChanged(false) }, [formattingChanged]) @@ -88,12 +87,14 @@ const Debug: FC = ({ setConversationId(null) abortController?.abort() setResponsingFalse() - setChatList(introduction ? [{ - id: `${Date.now()}`, - content: getIntroduction(), - isAnswer: true, - isOpeningStatement: true - }] : []) + setChatList(introduction + ? [{ + id: `${Date.now()}`, + content: getIntroduction(), + isAnswer: true, + isOpeningStatement: true, + }] + : []) setIsShowSuggestion(false) } @@ -119,12 +120,11 @@ const Debug: FC = ({ }) // compatible with old version // debugger requiredVars.forEach(({ key }) => { - if (hasEmptyInput) { + if (hasEmptyInput) return - } - if (!inputs[key]) { + + if (!inputs[key]) hasEmptyInput = true - } }) if (hasEmptyInput) { @@ -134,7 +134,6 @@ const Debug: FC = ({ return !hasEmptyInput } - const [isShowSuggestion, setIsShowSuggestion] = useState(false) const doShowSuggestion = isShowSuggestion && !isResponsing const [suggestQuestions, setSuggestQuestions] = useState([]) const onSend = async (message: string) => { @@ -147,7 +146,7 @@ const Debug: FC = ({ dataset: { enabled: true, id, - } + }, })) const postModelConfig: BackendModelConfig = { @@ -155,17 +154,17 @@ const Debug: FC = ({ user_input_form: promptVariablesToUserInputsForm(modelConfig.configs.prompt_variables), opening_statement: introduction, more_like_this: { - enabled: false + enabled: false, }, suggested_questions_after_answer: suggestedQuestionsAfterAnswerConfig, agent_mode: { enabled: true, - tools: [...postDatasets] + tools: [...postDatasets], }, model: { provider: modelConfig.provider, name: modelConfig.model_id, - completion_params: completionParams as any + completion_params: completionParams as any, }, } @@ -215,32 +214,32 @@ const Debug: FC = ({ setConversationId(newConversationId) _newConversationId = newConversationId } - if (messageId) { + if (messageId) responseItem.id = messageId - } + // closesure new list is outdated. const newListWithAnswer = produce( getChatList().filter(item => item.id !== responseItem.id && item.id !== placeholderAnswerId), (draft) => { - if (!draft.find(item => item.id === questionId)) { + if (!draft.find(item => item.id === questionId)) draft.push({ ...questionItem }) - } + draft.push({ ...responseItem }) }) setChatList(newListWithAnswer) }, async onCompleted(hasError?: boolean) { setResponsingFalse() - if (hasError) { + if (hasError) return - } + if (_newConversationId) { const { data }: any = await fetchConvesationMessages(appId, _newConversationId as string) const newResponseItem = data.find((item: any) => item.id === responseItem.id) - if (!newResponseItem) { + if (!newResponseItem) return - } - setChatList(produce(getChatList(), draft => { + + setChatList(produce(getChatList(), (draft) => { const index = draft.findIndex(item => item.id === responseItem.id) if (index !== -1) { draft[index] = { @@ -249,7 +248,7 @@ const Debug: FC = ({ time: dayjs.unix(newResponseItem.created_at).format('hh:mm A'), tokens: newResponseItem.answer_tokens + newResponseItem.message_tokens, latency: newResponseItem.provider_response_latency.toFixed(2), - } + }, } } })) @@ -263,10 +262,10 @@ const Debug: FC = ({ onError() { setResponsingFalse() // role back placeholder answer - setChatList(produce(getChatList(), draft => { + setChatList(produce(getChatList(), (draft) => { draft.splice(draft.findIndex(item => item.id === placeholderAnswerId), 1) })) - } + }, }) return true } @@ -277,7 +276,7 @@ const Debug: FC = ({ }, [controlClearChatMessage]) const [completionQuery, setCompletionQuery] = useState('') - const [completionRes, setCompletionRes] = useState(``) + const [completionRes, setCompletionRes] = useState('') const sendTextCompletion = async () => { if (isResponsing) { @@ -297,7 +296,7 @@ const Debug: FC = ({ dataset: { enabled: true, id, - } + }, })) const postModelConfig: BackendModelConfig = { @@ -308,16 +307,15 @@ const Debug: FC = ({ more_like_this: moreLikeThisConifg, agent_mode: { enabled: true, - tools: [...postDatasets] + tools: [...postDatasets], }, model: { provider: modelConfig.provider, name: modelConfig.model_id, - completion_params: completionParams as any + completion_params: completionParams as any, }, } - const data = { inputs, query: completionQuery, @@ -338,11 +336,10 @@ const Debug: FC = ({ }, onError() { setResponsingFalse() - } + }, }) } - return ( <>
@@ -368,7 +365,7 @@ const Debug: FC = ({ {/* Chat */} {mode === AppType.chat && (
-
+
{/* {JSON.stringify(chatList)} */} = ({ isInstalledApp = false, - installedAppInfo + installedAppInfo, }) => { const { t } = useTranslation() const media = useBreakpoints() @@ -53,7 +52,7 @@ const Main: FC = ({ const [plan, setPlan] = useState('basic') // basic/plus/pro // in mobile, show sidebar by click button const [isShowSidebar, { setTrue: showSidebar, setFalse: hideSidebar }] = useBoolean(false) - // Can Use metadata(https://beta.nextjs.org/docs/api-reference/metadata) to set title. But it only works in server side client. + // Can Use metadata(https://beta.nextjs.org/docs/api-reference/metadata) to set title. But it only works in server side client. useEffect(() => { if (siteInfo?.title) { if (plan !== 'basic') @@ -61,7 +60,6 @@ const Main: FC = ({ else document.title = `${siteInfo.title} - Powered by Dify` } - }, [siteInfo?.title, plan]) /* @@ -81,7 +79,7 @@ const Main: FC = ({ resetNewConversationInputs, setCurrInputs, setNewConversationInfo, - setExistConversationInfo + setExistConversationInfo, } = useConversation() const [hasMore, setHasMore] = useState(false) const onMoreLoaded = ({ data: conversations, has_more }: any) => { @@ -101,9 +99,9 @@ const Main: FC = ({ setChatList(generateNewChatListWithOpenstatement('', inputs)) } const hasSetInputs = (() => { - if (!isNewConversation) { + if (!isNewConversation) return true - } + return isChatStarted })() @@ -111,7 +109,8 @@ const Main: FC = ({ const conversationIntroduction = currConversationInfo?.introduction || '' const handleConversationSwitch = () => { - if (!inited) return + if (!inited) + return if (!appId) { // wait for appId setTimeout(handleConversationSwitch, 100) @@ -130,12 +129,13 @@ const Main: FC = ({ name: item?.name || '', introduction: notSyncToStateIntroduction, }) - } else { + } + else { notSyncToStateInputs = newConversationInputs setCurrInputs(notSyncToStateInputs) } - // update chat list of current conversation + // update chat list of current conversation if (!isNewConversation && !conversationIdChangeBecauseOfNew && !isResponsing) { fetchChatList(currConversationId, isInstalledApp, installedAppInfo?.id).then((res: any) => { const { data } = res @@ -158,9 +158,8 @@ const Main: FC = ({ }) } - if (isNewConversation && isChatStarted) { + if (isNewConversation && isChatStarted) setChatList(generateNewChatListWithOpenstatement()) - } setControlFocus(Date.now()) } @@ -170,7 +169,8 @@ const Main: FC = ({ if (id === '-1') { createNewChat() setConversationIdChangeBecauseOfNew(true) - } else { + } + else { setConversationIdChangeBecauseOfNew(false) } // trigger handleConversationSwitch @@ -186,9 +186,8 @@ const Main: FC = ({ const chatListDomRef = useRef(null) useEffect(() => { // scroll to bottom - if (chatListDomRef.current) { + if (chatListDomRef.current) chatListDomRef.current.scrollTop = chatListDomRef.current.scrollHeight - } }, [chatList, currConversationId]) // user can not edit inputs if user had send message const canEditInpus = !chatList.some(item => item.isAnswer === false) && isNewConversation @@ -196,15 +195,15 @@ const Main: FC = ({ // if new chat is already exist, do not create new chat abortController?.abort() setResponsingFalse() - if (conversationList.some(item => item.id === '-1')) { + if (conversationList.some(item => item.id === '-1')) return - } - setConversationList(produce(conversationList, draft => { + + setConversationList(produce(conversationList, (draft) => { draft.unshift({ id: '-1', name: t('share.chat.newChatDefaultName'), inputs: newConversationInputs, - introduction: conversationIntroduction + introduction: conversationIntroduction, }) })) } @@ -213,36 +212,37 @@ const Main: FC = ({ const generateNewChatListWithOpenstatement = (introduction?: string, inputs?: Record | null) => { let caculatedIntroduction = introduction || conversationIntroduction || '' const caculatedPromptVariables = inputs || currInputs || null - if (caculatedIntroduction && caculatedPromptVariables) { + if (caculatedIntroduction && caculatedPromptVariables) caculatedIntroduction = replaceStringWithValues(caculatedIntroduction, promptConfig?.prompt_variables || [], caculatedPromptVariables) - } + // console.log(isPublicVersion) const openstatement = { id: `${Date.now()}`, content: caculatedIntroduction, isAnswer: true, feedbackDisabled: true, - isOpeningStatement: isPublicVersion + isOpeningStatement: isPublicVersion, } - if (caculatedIntroduction) { + if (caculatedIntroduction) return [openstatement] - } + return [] } const fetchInitData = () => { - return Promise.all([isInstalledApp ? { - app_id: installedAppInfo?.id, - site: { - title: installedAppInfo?.app.name, - prompt_public: false, - copyright: '' - }, - plan: 'basic', - }: fetchAppInfo(), fetchConversations(isInstalledApp, installedAppInfo?.id), fetchAppParams(isInstalledApp, installedAppInfo?.id)]) + return Promise.all([isInstalledApp + ? { + app_id: installedAppInfo?.id, + site: { + title: installedAppInfo?.app.name, + prompt_public: false, + copyright: '', + }, + plan: 'basic', + } + : fetchAppInfo(), fetchConversations(isInstalledApp, installedAppInfo?.id), fetchAppParams(isInstalledApp, installedAppInfo?.id)]) } - // init useEffect(() => { (async () => { @@ -255,16 +255,16 @@ const Main: FC = ({ setIsPublicVersion(tempIsPublicVersion) const prompt_template = '' // handle current conversation id - const { data: conversations, has_more } = conversationData as { data: ConversationItem[], has_more: boolean } + const { data: conversations, has_more } = conversationData as { data: ConversationItem[]; has_more: boolean } const _conversationId = getConversationIdFromStorage(appId) const isNotNewConversation = conversations.some(item => item.id === _conversationId) setHasMore(has_more) // fetch new conversation info const { user_input_form, opening_statement: introduction, suggested_questions_after_answer }: any = appParams const prompt_variables = userInputsFormToPromptVariables(user_input_form) - if(siteInfo.default_language) { + if (siteInfo.default_language) changeLanguage(siteInfo.default_language) - } + setNewConversationInfo({ name: t('share.chat.newChatDefaultName'), introduction, @@ -272,20 +272,22 @@ const Main: FC = ({ setSiteInfo(siteInfo as SiteInfo) setPromptConfig({ prompt_template, - prompt_variables: prompt_variables, + prompt_variables, } as PromptConfig) setSuggestedQuestionsAfterAnswerConfig(suggested_questions_after_answer) setConversationList(conversations as ConversationItem[]) - if (isNotNewConversation) { + if (isNotNewConversation) setCurrConversationId(_conversationId, appId, false) - } + setInited(true) - } catch (e: any) { + } + catch (e: any) { if (e.status === 404) { setAppUnavailable(true) - } else { + } + else { setIsUnknwonReason(true) setAppUnavailable(true) } @@ -303,21 +305,20 @@ const Main: FC = ({ const checkCanSend = () => { const prompt_variables = promptConfig?.prompt_variables const inputs = currInputs - if (!inputs || !prompt_variables || prompt_variables?.length === 0) { + if (!inputs || !prompt_variables || prompt_variables?.length === 0) return true - } + let hasEmptyInput = false const requiredVars = prompt_variables?.filter(({ key, name, required }) => { const res = (!key || !key.trim()) || (!name || !name.trim()) || (required || required === undefined || required === null) return res }) || [] // compatible with old version requiredVars.forEach(({ key }) => { - if (hasEmptyInput) { + if (hasEmptyInput) return - } - if (!inputs?.[key]) { + + if (!inputs?.[key]) hasEmptyInput = true - } }) if (hasEmptyInput) { @@ -378,9 +379,8 @@ const Main: FC = ({ onData: (message: string, isFirstMessage: boolean, { conversationId: newConversationId, messageId }: any) => { responseItem.content = responseItem.content + message responseItem.id = messageId - if (isFirstMessage && newConversationId) { + if (isFirstMessage && newConversationId) tempNewConversationId = newConversationId - } // closesure new list is outdated. const newListWithAnswer = produce( @@ -395,9 +395,9 @@ const Main: FC = ({ }, async onCompleted(hasError?: boolean) { setResponsingFalse() - if (hasError) { + if (hasError) return - } + let currChatList = conversationList if (getConversationIdChangeBecauseOfNew()) { const { data: conversations, has_more }: any = await fetchConversations(isInstalledApp, installedAppInfo?.id) @@ -418,7 +418,7 @@ const Main: FC = ({ onError() { setResponsingFalse() // role back placeholder answer - setChatList(produce(getChatList(), draft => { + setChatList(produce(getChatList(), (draft) => { draft.splice(draft.findIndex(item => item.id === placeholderAnswerId), 1) })) }, @@ -476,18 +476,20 @@ const Main: FC = ({ onCreateNewChat={() => handleConversationIdChange('-1')} /> )} - + {/* {isNewConversation ? 'new' : 'exist'} {JSON.stringify(newConversationInputs ? newConversationInputs : {})} {JSON.stringify(existConversationInputs ? existConversationInputs : {})} */} -
{/* sidebar */} {!isMobile && renderSidebar()} @@ -504,8 +506,8 @@ const Main: FC = ({ {/* main */}
= ({ { hasSetInputs && ( -
+