Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
D
dify
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
ai-tech
dify
Commits
5239b2c7
Unverified
Commit
5239b2c7
authored
May 31, 2023
by
Joel
Committed by
GitHub
May 31, 2023
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Feat/dashboard more chart (#266)
parent
ae94b067
Changes
6
Show whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
99 additions
and
13 deletions
+99
-13
chartView.tsx
...out)/app/(appDetailLayout)/[appId]/overview/chartView.tsx
+23
-1
appChart.tsx
web/app/components/app/overview/appChart.tsx
+64
-10
app-overview.en.ts
web/i18n/lang/app-overview.en.ts
+1
-0
app-overview.zh.ts
web/i18n/lang/app-overview.zh.ts
+1
-0
app.ts
web/models/app.ts
+4
-0
apps.ts
web/service/apps.ts
+6
-2
No files found.
web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/chartView.tsx
View file @
5239b2c7
...
...
@@ -3,8 +3,10 @@ import React, { useState } from 'react'
import
dayjs
from
'dayjs'
import
quarterOfYear
from
'dayjs/plugin/quarterOfYear'
import
{
useTranslation
}
from
'react-i18next'
import
useSWR
from
'swr'
import
{
fetchAppDetail
}
from
'@/service/apps'
import
type
{
PeriodParams
}
from
'@/app/components/app/overview/appChart'
import
{
ConversationsChart
,
CostChart
,
EndUsersChart
}
from
'@/app/components/app/overview/appChart'
import
{
AvgResponseTime
,
AvgSessionInteractions
,
ConversationsChart
,
CostChart
,
EndUsersChart
,
UserSatisfactionRate
}
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'
...
...
@@ -20,6 +22,9 @@ export type IChartViewProps = {
}
export
default
function
ChartView
({
appId
}:
IChartViewProps
)
{
const
detailParams
=
{
url
:
'/apps'
,
id
:
appId
}
const
{
data
:
response
}
=
useSWR
(
detailParams
,
fetchAppDetail
)
const
isChatApp
=
response
?.
mode
===
'chat'
const
{
t
}
=
useTranslation
()
const
[
period
,
setPeriod
]
=
useState
<
PeriodParams
>
({
name
:
t
(
'appLog.filter.period.last7days'
),
query
:
{
start
:
today
.
subtract
(
7
,
'day'
).
format
(
queryDateFormat
),
end
:
today
.
format
(
queryDateFormat
)
}
})
...
...
@@ -27,6 +32,9 @@ export default function ChartView({ appId }: IChartViewProps) {
setPeriod
({
name
:
item
.
name
,
query
:
{
start
:
today
.
subtract
(
item
.
value
as
number
,
'day'
).
format
(
queryDateFormat
),
end
:
today
.
format
(
queryDateFormat
)
}
})
}
if
(
!
response
)
return
null
return
(
<
div
>
<
div
className=
'flex flex-row items-center mt-8 mb-4 text-gray-900 text-base'
>
...
...
@@ -46,6 +54,20 @@ export default function ChartView({ appId }: IChartViewProps) {
<
EndUsersChart
period=
{
period
}
id=
{
appId
}
/>
</
div
>
</
div
>
<
div
className=
'flex flex-row w-full mb-6'
>
<
div
className=
'flex-1 mr-3'
>
{
isChatApp
?
(
<
AvgSessionInteractions
period=
{
period
}
id=
{
appId
}
/>
)
:
(
<
AvgResponseTime
period=
{
period
}
id=
{
appId
}
/>
)
}
</
div
>
<
div
className=
'flex-1 ml-3'
>
<
UserSatisfactionRate
period=
{
period
}
id=
{
appId
}
/>
</
div
>
</
div
>
<
CostChart
period=
{
period
}
id=
{
appId
}
/>
</
div
>
)
...
...
web/app/components/app/overview/appChart.tsx
View file @
5239b2c7
...
...
@@ -6,12 +6,12 @@ import type { EChartsOption } from 'echarts'
import
useSWR
from
'swr'
import
dayjs
from
'dayjs'
import
{
get
}
from
'lodash-es'
import
{
formatNumber
}
from
'@/utils/format'
import
{
useTranslation
}
from
'react-i18next'
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
,
getAppTokenCosts
}
from
'@/service/apps'
import
{
getAppDailyConversations
,
getAppDailyEndUsers
,
getApp
Statistics
,
getApp
TokenCosts
}
from
'@/service/apps'
const
valueFormatter
=
(
v
:
string
|
number
)
=>
v
const
COLOR_TYPE_MAP
=
{
...
...
@@ -76,6 +76,9 @@ export type IBizChartProps = {
export
type
IChartProps
=
{
className
?:
string
basicInfo
:
{
title
:
string
;
explanation
:
string
;
timePeriod
:
string
}
valueKey
?:
string
isAvg
?:
boolean
unit
?:
string
yMax
?:
number
chartType
:
IChartType
chartData
:
AppDailyConversationsResponse
|
AppDailyEndUsersResponse
|
AppTokenCostsResponse
|
{
data
:
Array
<
{
date
:
string
;
count
:
number
}
>
}
...
...
@@ -85,6 +88,9 @@ const Chart: React.FC<IChartProps> = ({
basicInfo
:
{
title
,
explanation
,
timePeriod
},
chartType
=
'conversations'
,
chartData
,
valueKey
,
isAvg
,
unit
=
''
,
yMax
,
className
,
})
=>
{
...
...
@@ -96,7 +102,7 @@ const Chart: React.FC<IChartProps> = ({
extraDataForMarkLine
.
unshift
(
''
)
const
xData
=
statistics
.
map
(({
date
})
=>
date
)
const
yField
=
Object
.
keys
(
statistics
[
0
]).
find
(
name
=>
name
.
includes
(
'count'
))
||
''
const
yField
=
valueKey
||
Object
.
keys
(
statistics
[
0
]).
find
(
name
=>
name
.
includes
(
'count'
))
||
''
const
yData
=
statistics
.
map
((
item
)
=>
{
// @ts-expect-error field is valid
return
item
[
yField
]
||
0
...
...
@@ -211,8 +217,7 @@ const Chart: React.FC<IChartProps> = ({
},
],
}
const
sumData
=
sum
(
yData
)
const
sumData
=
isAvg
?
(
sum
(
yData
)
/
yData
.
length
)
:
sum
(
yData
)
return
(
<
div
className=
{
`flex flex-col w-full px-6 py-4 border-[0.5px] rounded-lg border-gray-200 shadow-sm ${className ?? ''}`
}
>
...
...
@@ -221,7 +226,7 @@ const Chart: React.FC<IChartProps> = ({
</
div
>
<
div
className=
'mb-4'
>
<
Basic
name=
{
chartType
!==
'costs'
?
sumData
.
toLocaleString
()
:
`${sumData < 1000 ? sumData : (formatNumber(Math.round(sumData / 1000)) + 'k'
)}`
}
name=
{
chartType
!==
'costs'
?
(
sumData
.
toLocaleString
()
+
unit
)
:
`${sumData < 1000 ? sumData : (`
$
{
formatNumber
(
Math
.
round
(
sumData
/
1000
))}
k
`
)}`
}
type=
{
!
CHART_TYPE_CONFIG
[
chartType
].
showTokens
?
''
:
<
span
>
{
t
(
'appOverview.analysis.tokenUsage.consumed'
)
}
Tokens
<
span
className=
'text-sm'
>
...
...
@@ -236,9 +241,9 @@ const Chart: React.FC<IChartProps> = ({
)
}
const
getDefaultChartData
=
({
start
,
end
}:
{
start
:
string
;
end
:
string
})
=>
{
const
getDefaultChartData
=
({
start
,
end
,
key
=
'count'
}:
{
start
:
string
;
end
:
string
;
key
?
:
string
})
=>
{
const
diffDays
=
dayjs
(
end
).
diff
(
dayjs
(
start
),
'day'
)
return
Array
.
from
({
length
:
diffDays
||
1
},
()
=>
({
date
:
''
,
count
:
0
})).
map
((
item
,
index
)
=>
{
return
Array
.
from
({
length
:
diffDays
||
1
},
()
=>
({
date
:
''
,
[
key
]
:
0
})).
map
((
item
,
index
)
=>
{
item
.
date
=
dayjs
(
start
).
add
(
index
,
'day'
).
format
(
commonDateFormat
)
return
item
})
...
...
@@ -273,6 +278,55 @@ export const EndUsersChart: FC<IBizChartProps> = ({ id, period }) => {
/>
}
export
const
AvgSessionInteractions
:
FC
<
IBizChartProps
>
=
({
id
,
period
})
=>
{
const
{
t
}
=
useTranslation
()
const
{
data
:
response
}
=
useSWR
({
url
:
`/apps/
${
id
}
/statistics/average-session-interactions`
,
params
:
period
.
query
},
getAppStatistics
)
if
(
!
response
)
return
<
Loading
/>
const
noDataFlag
=
!
response
.
data
||
response
.
data
.
length
===
0
return
<
Chart
basicInfo=
{
{
title
:
t
(
'appOverview.analysis.avgSessionInteractions.title'
),
explanation
:
t
(
'appOverview.analysis.avgSessionInteractions.explanation'
),
timePeriod
:
period
.
name
}
}
chartData=
{
!
noDataFlag
?
response
:
{
data
:
getDefaultChartData
({
...
period
.
query
,
key
:
'interactions'
})
}
as
any
}
chartType=
'conversations'
valueKey=
'interactions'
isAvg
{
...
(
noDataFlag
&&
{
yMax
:
500
})}
/>
}
export
const
AvgResponseTime
:
FC
<
IBizChartProps
>
=
({
id
,
period
})
=>
{
const
{
t
}
=
useTranslation
()
const
{
data
:
response
}
=
useSWR
({
url
:
`/apps/
${
id
}
/statistics/average-response-time`
,
params
:
period
.
query
},
getAppStatistics
)
if
(
!
response
)
return
<
Loading
/>
const
noDataFlag
=
!
response
.
data
||
response
.
data
.
length
===
0
return
<
Chart
basicInfo=
{
{
title
:
t
(
'appOverview.analysis.avgResponseTime.title'
),
explanation
:
t
(
'appOverview.analysis.avgResponseTime.explanation'
),
timePeriod
:
period
.
name
}
}
chartData=
{
!
noDataFlag
?
response
:
{
data
:
getDefaultChartData
({
...
period
.
query
,
key
:
'latency'
})
}
as
any
}
valueKey=
'latency'
chartType=
'conversations'
isAvg
unit=
{
t
(
'appOverview.analysis.ms'
)
as
string
}
{
...
(
noDataFlag
&&
{
yMax
:
500
})}
/>
}
export
const
UserSatisfactionRate
:
FC
<
IBizChartProps
>
=
({
id
,
period
})
=>
{
const
{
t
}
=
useTranslation
()
const
{
data
:
response
}
=
useSWR
({
url
:
`/apps/
${
id
}
/statistics/user-satisfaction-rate`
,
params
:
period
.
query
},
getAppStatistics
)
if
(
!
response
)
return
<
Loading
/>
const
noDataFlag
=
!
response
.
data
||
response
.
data
.
length
===
0
return
<
Chart
basicInfo=
{
{
title
:
t
(
'appOverview.analysis.userSatisfactionRate.title'
),
explanation
:
t
(
'appOverview.analysis.userSatisfactionRate.explanation'
),
timePeriod
:
period
.
name
}
}
chartData=
{
!
noDataFlag
?
response
:
{
data
:
getDefaultChartData
({
...
period
.
query
,
key
:
'rate'
})
}
as
any
}
valueKey=
'rate'
chartType=
'endUsers'
isAvg
{
...
(
noDataFlag
&&
{
yMax
:
1000
})}
/>
}
export
const
CostChart
:
FC
<
IBizChartProps
>
=
({
id
,
period
})
=>
{
const
{
t
}
=
useTranslation
()
...
...
web/i18n/lang/app-overview.en.ts
View file @
5239b2c7
...
...
@@ -71,6 +71,7 @@ const translation = {
},
analysis
:
{
title
:
'Analysis'
,
ms
:
'ms'
,
totalMessages
:
{
title
:
'Total Messages'
,
explanation
:
'Daily AI interactions count; prompt engineering/debugging excluded.'
,
...
...
web/i18n/lang/app-overview.zh.ts
View file @
5239b2c7
...
...
@@ -71,6 +71,7 @@ const translation = {
},
analysis
:
{
title
:
'分析'
,
ms
:
'毫秒'
,
totalMessages
:
{
title
:
'全部消息数'
,
explanation
:
'反映 AI 每天的互动总次数,每回答用户一个问题算一条 Message。提示词编排和调试的消息不计入。'
,
...
...
web/models/app.ts
View file @
5239b2c7
...
...
@@ -83,6 +83,10 @@ export type AppDailyConversationsResponse = {
data
:
Array
<
{
date
:
string
;
conversation_count
:
number
}
>
}
export
type
AppStatisticsResponse
=
{
data
:
Array
<
{
date
:
string
}
>
}
export
type
AppDailyEndUsersResponse
=
{
data
:
Array
<
{
date
:
string
;
terminal_count
:
number
}
>
}
...
...
web/service/apps.ts
View file @
5239b2c7
import
type
{
Fetcher
}
from
'swr'
import
{
del
,
get
,
post
}
from
'./base'
import
type
{
ApikeysListResponse
,
AppDailyConversationsResponse
,
AppDailyEndUsersResponse
,
AppDetailResponse
,
AppListResponse
,
AppTemplatesResponse
,
AppTokenCostsResponse
,
CreateApiKeyResponse
,
GenerationIntroductionResponse
,
UpdateAppModelConfigResponse
,
UpdateAppNameResponse
,
UpdateAppSiteCodeResponse
,
UpdateOpenAIKeyResponse
,
ValidateOpenAIKeyResponse
}
from
'@/models/app'
import
type
{
ApikeysListResponse
,
AppDailyConversationsResponse
,
AppDailyEndUsersResponse
,
AppDetailResponse
,
AppListResponse
,
App
StatisticsResponse
,
App
TemplatesResponse
,
AppTokenCostsResponse
,
CreateApiKeyResponse
,
GenerationIntroductionResponse
,
UpdateAppModelConfigResponse
,
UpdateAppNameResponse
,
UpdateAppSiteCodeResponse
,
UpdateOpenAIKeyResponse
,
ValidateOpenAIKeyResponse
}
from
'@/models/app'
import
type
{
CommonResponse
}
from
'@/models/common'
import
type
{
AppMode
,
ModelConfig
}
from
'@/types/app'
...
...
@@ -16,7 +16,7 @@ export const fetchAppTemplates: Fetcher<AppTemplatesResponse, { url: string }> =
return
get
(
url
)
as
Promise
<
AppTemplatesResponse
>
}
export
const
createApp
:
Fetcher
<
AppDetailResponse
,
{
name
:
string
;
icon
:
string
,
icon_background
:
string
,
mode
:
AppMode
;
config
?:
ModelConfig
}
>
=
({
name
,
icon
,
icon_background
,
mode
,
config
})
=>
{
export
const
createApp
:
Fetcher
<
AppDetailResponse
,
{
name
:
string
;
icon
:
string
;
icon_background
:
string
;
mode
:
AppMode
;
config
?:
ModelConfig
}
>
=
({
name
,
icon
,
icon_background
,
mode
,
config
})
=>
{
return
post
(
'apps'
,
{
body
:
{
name
,
icon
,
icon_background
,
mode
,
model_config
:
config
}
})
as
Promise
<
AppDetailResponse
>
}
...
...
@@ -54,6 +54,10 @@ export const getAppDailyConversations: Fetcher<AppDailyConversationsResponse, {
return
get
(
url
,
{
params
})
as
Promise
<
AppDailyConversationsResponse
>
}
export
const
getAppStatistics
:
Fetcher
<
AppStatisticsResponse
,
{
url
:
string
;
params
:
Record
<
string
,
any
>
}
>
=
({
url
,
params
})
=>
{
return
get
(
url
,
{
params
})
as
Promise
<
AppStatisticsResponse
>
}
export
const
getAppDailyEndUsers
:
Fetcher
<
AppDailyEndUsersResponse
,
{
url
:
string
;
params
:
Record
<
string
,
any
>
}
>
=
({
url
,
params
})
=>
{
return
get
(
url
,
{
params
})
as
Promise
<
AppDailyEndUsersResponse
>
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment