mirror of https://github.com/langgenius/dify.git
app overview
This commit is contained in:
parent
eae4c80679
commit
a67777b8e2
|
|
@ -4,7 +4,7 @@ import dayjs from 'dayjs'
|
|||
import quarterOfYear from 'dayjs/plugin/quarterOfYear'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import type { PeriodParams } from '@/app/components/app/overview/appChart'
|
||||
import { AvgResponseTime, AvgSessionInteractions, ConversationsChart, CostChart, EndUsersChart, TokenPerSecond, UserSatisfactionRate } from '@/app/components/app/overview/appChart'
|
||||
import { AvgResponseTime, AvgSessionInteractions, AvgUserInteractions, ConversationsChart, CostChart, EndUsersChart, TokenPerSecond, UserSatisfactionRate, WorkflowCostChart, WorkflowDailyTerminalsChart, WorkflowMessagesChart } from '@/app/components/app/overview/appChart'
|
||||
import type { Item } from '@/app/components/base/select'
|
||||
import { SimpleSelect } from '@/app/components/base/select'
|
||||
import { TIME_PERIOD_LIST } from '@/app/components/app/log/filter'
|
||||
|
|
@ -24,6 +24,7 @@ export default function ChartView({ appId }: IChartViewProps) {
|
|||
const { t } = useTranslation()
|
||||
const { appDetail } = useAppStore()
|
||||
const isChatApp = appDetail?.mode !== 'completion' && appDetail?.mode !== 'workflow'
|
||||
const isWorkflow = appDetail?.mode === 'workflow'
|
||||
const [period, setPeriod] = useState<PeriodParams>({ name: t('appLog.filter.period.last7days'), query: { start: today.subtract(7, 'day').format(queryDateFormat), end: today.format(queryDateFormat) } })
|
||||
|
||||
const onSelect = (item: Item) => {
|
||||
|
|
@ -54,24 +55,42 @@ export default function ChartView({ appId }: IChartViewProps) {
|
|||
defaultValue={7}
|
||||
/>
|
||||
</div>
|
||||
<div className='grid gap-6 grid-cols-1 xl:grid-cols-2 w-full mb-6'>
|
||||
<ConversationsChart period={period} id={appId} />
|
||||
<EndUsersChart period={period} id={appId} />
|
||||
</div>
|
||||
<div className='grid gap-6 grid-cols-1 xl:grid-cols-2 w-full mb-6'>
|
||||
{isChatApp
|
||||
? (
|
||||
<AvgSessionInteractions period={period} id={appId} />
|
||||
)
|
||||
: (
|
||||
<AvgResponseTime period={period} id={appId} />
|
||||
)}
|
||||
<TokenPerSecond period={period} id={appId} />
|
||||
</div>
|
||||
<div className='grid gap-6 grid-cols-1 xl:grid-cols-2 w-full mb-6'>
|
||||
<UserSatisfactionRate period={period} id={appId} />
|
||||
<CostChart period={period} id={appId} />
|
||||
</div>
|
||||
{!isWorkflow && (
|
||||
<div className='grid gap-6 grid-cols-1 xl:grid-cols-2 w-full mb-6'>
|
||||
<ConversationsChart period={period} id={appId} />
|
||||
<EndUsersChart period={period} id={appId} />
|
||||
</div>
|
||||
)}
|
||||
{!isWorkflow && (
|
||||
<div className='grid gap-6 grid-cols-1 xl:grid-cols-2 w-full mb-6'>
|
||||
{isChatApp
|
||||
? (
|
||||
<AvgSessionInteractions period={period} id={appId} />
|
||||
)
|
||||
: (
|
||||
<AvgResponseTime period={period} id={appId} />
|
||||
)}
|
||||
<TokenPerSecond period={period} id={appId} />
|
||||
</div>
|
||||
)}
|
||||
{!isWorkflow && (
|
||||
<div className='grid gap-6 grid-cols-1 xl:grid-cols-2 w-full mb-6'>
|
||||
<UserSatisfactionRate period={period} id={appId} />
|
||||
<CostChart period={period} id={appId} />
|
||||
</div>
|
||||
)}
|
||||
{isWorkflow && (
|
||||
<div className='grid gap-6 grid-cols-1 xl:grid-cols-2 w-full mb-6'>
|
||||
<WorkflowMessagesChart period={period} id={appId} />
|
||||
<WorkflowDailyTerminalsChart period={period} id={appId} />
|
||||
</div>
|
||||
)}
|
||||
{isWorkflow && (
|
||||
<div className='grid gap-6 grid-cols-1 xl:grid-cols-2 w-full mb-6'>
|
||||
<WorkflowCostChart period={period} id={appId} />
|
||||
<AvgUserInteractions period={period} id={appId} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import { formatNumber } from '@/utils/format'
|
|||
import Basic from '@/app/components/app-sidebar/basic'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import type { AppDailyConversationsResponse, AppDailyEndUsersResponse, AppTokenCostsResponse } from '@/models/app'
|
||||
import { getAppDailyConversations, getAppDailyEndUsers, getAppStatistics, getAppTokenCosts } from '@/service/apps'
|
||||
import { getAppDailyConversations, getAppDailyEndUsers, getAppStatistics, getAppTokenCosts, getWorkflowDailyConversations } from '@/service/apps'
|
||||
const valueFormatter = (v: string | number) => v
|
||||
|
||||
const COLOR_TYPE_MAP = {
|
||||
|
|
@ -36,7 +36,7 @@ const COMMON_COLOR_MAP = {
|
|||
}
|
||||
|
||||
type IColorType = 'green' | 'orange' | 'blue'
|
||||
type IChartType = 'conversations' | 'endUsers' | 'costs'
|
||||
type IChartType = 'conversations' | 'endUsers' | 'costs' | 'workflowCosts'
|
||||
type IChartConfigType = { colorType: IColorType; showTokens?: boolean }
|
||||
|
||||
const commonDateFormat = 'MMM D, YYYY'
|
||||
|
|
@ -52,6 +52,9 @@ const CHART_TYPE_CONFIG: Record<string, IChartConfigType> = {
|
|||
colorType: 'blue',
|
||||
showTokens: true,
|
||||
},
|
||||
workflowCosts: {
|
||||
colorType: 'blue',
|
||||
},
|
||||
}
|
||||
|
||||
const sum = (arr: number[]): number => {
|
||||
|
|
@ -366,4 +369,65 @@ export const CostChart: FC<IBizChartProps> = ({ id, period }) => {
|
|||
/>
|
||||
}
|
||||
|
||||
export const WorkflowMessagesChart: FC<IBizChartProps> = ({ id, period }) => {
|
||||
const { t } = useTranslation()
|
||||
const { data: response } = useSWR({ url: `/apps/${id}/workflow/statistics/daily-conversations`, params: period.query }, getWorkflowDailyConversations)
|
||||
if (!response)
|
||||
return <Loading />
|
||||
const noDataFlag = !response.data || response.data.length === 0
|
||||
return <Chart
|
||||
basicInfo={{ title: t('appOverview.analysis.totalMessages.title'), explanation: t('appOverview.analysis.totalMessages.explanation'), timePeriod: period.name }}
|
||||
chartData={!noDataFlag ? response : { data: getDefaultChartData({ ...(period.query ?? defaultPeriod), key: 'runs' }) }}
|
||||
chartType='conversations'
|
||||
valueKey='runs'
|
||||
{...(noDataFlag && { yMax: 500 })}
|
||||
/>
|
||||
}
|
||||
|
||||
export const WorkflowDailyTerminalsChart: FC<IBizChartProps> = ({ id, period }) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { data: response } = useSWR({ url: `/apps/${id}/workflow/statistics/daily-terminals`, id, params: period.query }, getAppDailyEndUsers)
|
||||
if (!response)
|
||||
return <Loading />
|
||||
const noDataFlag = !response.data || response.data.length === 0
|
||||
return <Chart
|
||||
basicInfo={{ title: t('appOverview.analysis.activeUsers.title'), explanation: t('appOverview.analysis.activeUsers.explanation'), timePeriod: period.name }}
|
||||
chartData={!noDataFlag ? response : { data: getDefaultChartData(period.query ?? defaultPeriod) }}
|
||||
chartType='endUsers'
|
||||
{...(noDataFlag && { yMax: 500 })}
|
||||
/>
|
||||
}
|
||||
|
||||
export const WorkflowCostChart: FC<IBizChartProps> = ({ id, period }) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { data: response } = useSWR({ url: `/apps/${id}/workflow/statistics/token-costs`, params: period.query }, getAppTokenCosts)
|
||||
if (!response)
|
||||
return <Loading />
|
||||
const noDataFlag = !response.data || response.data.length === 0
|
||||
return <Chart
|
||||
basicInfo={{ title: t('appOverview.analysis.tokenUsage.title'), explanation: t('appOverview.analysis.tokenUsage.explanation'), timePeriod: period.name }}
|
||||
chartData={!noDataFlag ? response : { data: getDefaultChartData(period.query ?? defaultPeriod) }}
|
||||
chartType='workflowCosts'
|
||||
{...(noDataFlag && { yMax: 100 })}
|
||||
/>
|
||||
}
|
||||
|
||||
export const AvgUserInteractions: FC<IBizChartProps> = ({ id, period }) => {
|
||||
const { t } = useTranslation()
|
||||
const { data: response } = useSWR({ url: `/apps/${id}/workflow/statistics/average-app-interactions`, params: period.query }, getAppStatistics)
|
||||
if (!response)
|
||||
return <Loading />
|
||||
const noDataFlag = !response.data || response.data.length === 0
|
||||
return <Chart
|
||||
basicInfo={{ title: t('appOverview.analysis.avgUserInteractions.title'), explanation: t('appOverview.analysis.avgUserInteractions.explanation'), timePeriod: period.name }}
|
||||
chartData={!noDataFlag ? response : { data: getDefaultChartData({ ...(period.query ?? defaultPeriod), key: 'interactions' }) } as any}
|
||||
chartType='conversations'
|
||||
valueKey='interactions'
|
||||
isAvg
|
||||
{...(noDataFlag && { yMax: 500 })}
|
||||
/>
|
||||
}
|
||||
|
||||
export default Chart
|
||||
|
|
|
|||
|
|
@ -333,17 +333,23 @@ const TextGeneration: FC<IMainProps> = ({
|
|||
if (!isInstalledApp)
|
||||
await checkOrSetAccessToken()
|
||||
|
||||
return Promise.all([isInstalledApp
|
||||
? {
|
||||
app_id: installedAppInfo?.id,
|
||||
site: {
|
||||
title: installedAppInfo?.app.name,
|
||||
prompt_public: false,
|
||||
copyright: '',
|
||||
},
|
||||
plan: 'basic',
|
||||
}
|
||||
: fetchAppInfo(), fetchAppParams(isInstalledApp, installedAppInfo?.id), fetchSavedMessage()])
|
||||
return Promise.all([
|
||||
isInstalledApp
|
||||
? {
|
||||
app_id: installedAppInfo?.id,
|
||||
site: {
|
||||
title: installedAppInfo?.app.name,
|
||||
prompt_public: false,
|
||||
copyright: '',
|
||||
},
|
||||
plan: 'basic',
|
||||
}
|
||||
: fetchAppInfo(),
|
||||
fetchAppParams(isInstalledApp, installedAppInfo?.id),
|
||||
!isWorkflow
|
||||
? fetchSavedMessage()
|
||||
: {},
|
||||
])
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -360,6 +366,7 @@ const TextGeneration: FC<IMainProps> = ({
|
|||
...file_upload.image,
|
||||
image_file_size_limit: appParams?.system_parameters?.image_file_size_limit,
|
||||
})
|
||||
// ###TODO###
|
||||
const prompt_variables = userInputsFormToPromptVariables(user_input_form)
|
||||
setPromptConfig({
|
||||
prompt_template: '', // placeholder for feture
|
||||
|
|
|
|||
|
|
@ -121,6 +121,10 @@ const translation = {
|
|||
title: 'Avg. Session Interactions',
|
||||
explanation: 'Continuous user-AI communication count; for conversation-based apps.',
|
||||
},
|
||||
avgUserInteractions: {
|
||||
title: 'Avg. User Interactions',
|
||||
explanation: 'Reflects the daily usage frequency of users. This metric reflects user stickiness.',
|
||||
},
|
||||
userSatisfactionRate: {
|
||||
title: 'User Satisfaction Rate',
|
||||
explanation: 'The number of likes per 1,000 messages. This indicates the proportion of answers that users are highly satisfied with.',
|
||||
|
|
|
|||
|
|
@ -121,6 +121,10 @@ const translation = {
|
|||
title: '平均会话互动数',
|
||||
explanation: '反应每个会话用户的持续沟通次数,如果用户与 AI 问答了 10 轮,即为 10。该指标反映了用户粘性。仅在对话型应用提供。',
|
||||
},
|
||||
avgUserInteractions: {
|
||||
title: '平均用户调用次数',
|
||||
explanation: '反应每天用户的使用次数。该指标反映了用户粘性。',
|
||||
},
|
||||
userSatisfactionRate: {
|
||||
title: '用户满意度',
|
||||
explanation: '每 1000 条消息的点赞数。反应了用户对回答十分满意的比例。',
|
||||
|
|
|
|||
|
|
@ -79,6 +79,10 @@ export type AppDailyConversationsResponse = {
|
|||
data: Array<{ date: string; conversation_count: number }>
|
||||
}
|
||||
|
||||
export type WorkflowDailyConversationsResponse = {
|
||||
data: Array<{ date: string; runs: number }>
|
||||
}
|
||||
|
||||
export type AppStatisticsResponse = {
|
||||
data: Array<{ date: string }>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import type { Fetcher } from 'swr'
|
||||
import { del, get, post, put } from './base'
|
||||
import type { ApikeysListResponse, AppDailyConversationsResponse, AppDailyEndUsersResponse, AppDetailResponse, AppListResponse, AppStatisticsResponse, AppTemplatesResponse, AppTokenCostsResponse, AppVoicesListResponse, CreateApiKeyResponse, GenerationIntroductionResponse, UpdateAppModelConfigResponse, UpdateAppSiteCodeResponse, UpdateOpenAIKeyResponse, ValidateOpenAIKeyResponse } from '@/models/app'
|
||||
import type { ApikeysListResponse, AppDailyConversationsResponse, AppDailyEndUsersResponse, AppDetailResponse, AppListResponse, AppStatisticsResponse, AppTemplatesResponse, AppTokenCostsResponse, AppVoicesListResponse, CreateApiKeyResponse, GenerationIntroductionResponse, UpdateAppModelConfigResponse, UpdateAppSiteCodeResponse, UpdateOpenAIKeyResponse, ValidateOpenAIKeyResponse, WorkflowDailyConversationsResponse } from '@/models/app'
|
||||
import type { CommonResponse } from '@/models/common'
|
||||
import type { AppMode, ModelConfig } from '@/types/app'
|
||||
|
||||
|
|
@ -69,6 +69,10 @@ export const getAppDailyConversations: Fetcher<AppDailyConversationsResponse, {
|
|||
return get<AppDailyConversationsResponse>(url, { params })
|
||||
}
|
||||
|
||||
export const getWorkflowDailyConversations: Fetcher<WorkflowDailyConversationsResponse, { url: string; params: Record<string, any> }> = ({ url, params }) => {
|
||||
return get<WorkflowDailyConversationsResponse>(url, { params })
|
||||
}
|
||||
|
||||
export const getAppStatistics: Fetcher<AppStatisticsResponse, { url: string; params: Record<string, any> }> = ({ url, params }) => {
|
||||
return get<AppStatisticsResponse>(url, { params })
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
import type { UserInputFormItem } from '@/types/app'
|
||||
import type { PromptVariable } from '@/models/debug'
|
||||
|
||||
export const workflowUserInputsToPromptVariables = () => {
|
||||
// TODO
|
||||
}
|
||||
|
||||
export const userInputsFormToPromptVariables = (useInputs: UserInputFormItem[] | null, dataset_query_variable?: string) => {
|
||||
if (!useInputs)
|
||||
return []
|
||||
|
|
|
|||
Loading…
Reference in New Issue