Commit 165b230d authored by nick zheng's avatar nick zheng

Merge branch 'beta' into 'master'

feat: agent应用分析&应用调试积分扣除规则设置

See merge request !108
parents eb33834d aa1cedcd
......@@ -8,7 +8,7 @@
name="viewport"
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
/>
<link rel="stylesheet" href="//at.alicdn.com/t/c/font_4711453_i03chzm1n0e.css" />
<link rel="stylesheet" href="//at.alicdn.com/t/c/font_4711453_n5pnpk71gp.css" />
<title>Model Link</title>
</head>
......
......@@ -27,6 +27,7 @@
"cropperjs": "^1.6.2",
"dayjs": "^1.11.13",
"dompurify": "^3.2.0",
"echarts": "^5.5.1",
"github-markdown-css": "^5.7.0",
"highlight.js": "^11.10.0",
"howler": "^2.2.4",
......
......@@ -41,6 +41,9 @@ importers:
dompurify:
specifier: ^3.2.0
version: 3.2.0
echarts:
specifier: ^5.5.1
version: 5.5.1
github-markdown-css:
specifier: ^5.7.0
version: 5.7.0
......@@ -1660,6 +1663,9 @@ packages:
duplexer@0.1.2:
resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==}
echarts@5.5.1:
resolution: {integrity: sha512-Fce8upazaAXUVUVsjgV6mBnGuqgO+JNDlcgF79Dksy4+wgGpQB2lmYoO4TSweFg/mZITdpGHomw/cNBJZj1icA==}
electron-to-chromium@1.5.25:
resolution: {integrity: sha512-kMb204zvK3PsSlgvvwzI3wBIcAw15tRkYk+NQdsjdDtcQWTp2RABbMQ9rUBy8KNEOM+/E6ep+XC3AykiWZld4g==}
......@@ -2941,6 +2947,9 @@ packages:
peerDependencies:
typescript: '>=4.2.0'
tslib@2.3.0:
resolution: {integrity: sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==}
tslib@2.7.0:
resolution: {integrity: sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==}
......@@ -3281,6 +3290,9 @@ packages:
resolution: {integrity: sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==}
engines: {node: '>=12.20'}
zrender@5.6.0:
resolution: {integrity: sha512-uzgraf4njmmHAbEUxMJ8Oxg+P3fT04O+9p7gY+wJRVxo8Ge+KmYv0WJev945EH4wFuc4OY2NLXz46FZrWS9xJg==}
snapshots:
'@ampproject/remapping@2.3.0':
......@@ -4803,6 +4815,11 @@ snapshots:
duplexer@0.1.2: {}
echarts@5.5.1:
dependencies:
tslib: 2.3.0
zrender: 5.6.0
electron-to-chromium@1.5.25: {}
emoji-regex@10.4.0: {}
......@@ -6020,6 +6037,8 @@ snapshots:
dependencies:
typescript: 5.6.2
tslib@2.3.0: {}
tslib@2.7.0: {}
tsx@4.19.1:
......@@ -6364,3 +6383,7 @@ snapshots:
yocto-queue@0.1.0: {}
yocto-queue@1.1.1: {}
zrender@5.6.0:
dependencies:
tslib: 2.3.0
import { request } from '@/utils/request'
/**
* @query agentId 应用Id channel 渠道
* @returns 应用用量数据
*/
export function fetchGetGeneralUsageData<T>(agentId: string, channel: string[]) {
return request.post<T>(`/agentDataAnalyzeRest/getGeneralUsageData.json?agentId=${agentId}&channel=${channel}`)
}
/**
* @params payload
* @returns 应用趋势图数据
*/
export function fetchGetAgentDataTrend<T>(payload: {
agentId: string
channel: string[]
timeRange: { rangType: string; startTime: string; endTime: string }
}) {
return request.post<T>('/agentDataAnalyzeRest/getAgentDataTrend.json', payload)
}
/**
* @query agentId 应用Id channel 渠道
* @returns 本月使用人数据
*/
export function fetchGetMonthUserCount<T>(agentId: string, channel: string[]) {
return request.post<T>(`/agentDataAnalyzeRest/getChannelUsersCount.json?agentId=${agentId}&channel=${channel}`)
}
/**
* @query agentId 应用Id channel 渠道
* @returns 本月发送消息数据
*/
export function fetchGetMonthSendMessageCount<T>(agentId: string, channel: string[]) {
return request.post<T>(`/agentDataAnalyzeRest/getSendMessageCount.json?agentId=${agentId}&channel=${channel}`)
}
/**
* @query agentId 应用Id channel 渠道
* @returns 本月消耗积分数据
*/
export function fetchGetMonthConsumePointCount<T>(agentId: string, channel: string[]) {
return request.post<T>(`/agentDataAnalyzeRest/getPointUsageCount.json?agentId=${agentId}&channel=${channel}`)
}
......@@ -58,7 +58,7 @@ export function fetchPublishApplication<T>(payload: object) {
* @returns 获取大模型列表
*/
export function fetchGetLargeModelList<T>() {
return request.post<T>('/agentApplicationInfoRest/getLargeModelList.json')
return request.post<T>('/agentApplicationInfoRest/getLargeModelListV2.json')
}
/**
......@@ -135,7 +135,7 @@ export function fetchCreateAgentTitleAndDesc<T>(payload: { input: string }, cont
* @returns 搜索模型信息
*/
export function fetchGetLargeModelInfo<T>(modelName: string) {
return request.post<T>(`/agentApplicationInfoRest/getLargeModelInfo.json?query=${modelName}`)
return request.post<T>(`/agentApplicationInfoRest/getLargeModelInfoV2.json?query=${modelName}`)
}
/**
......
<script setup lang="ts">
import { ref, watch, onMounted, onBeforeUnmount, ShallowRef, shallowRef } from 'vue'
import { useI18n } from 'vue-i18n'
import { debounce } from 'lodash-es'
import type { EChartsType } from 'echarts/types/dist/core'
import { echarts, type EChartsOption } from '@/utils/echarts'
interface Props {
width?: string
height?: string
option: EChartsOption
}
const props = withDefaults(defineProps<Props>(), {
width: '100%',
height: '100%',
option: () => ({}),
})
const { t } = useI18n()
const customChartRef = ref<HTMLDivElement>()
const customChart: ShallowRef<EChartsType | null | undefined> = shallowRef(null)
watch(
() => props,
() => {
drawEChart()
},
{
deep: true,
},
)
const chartResizeHandler = debounce(() => {
if (customChart.value) {
customChart.value.resize()
}
}, 100)
onMounted(() => {
drawEChart()
window.addEventListener('resize', chartResizeHandler)
})
onBeforeUnmount(() => {
window.removeEventListener('resize', chartResizeHandler)
customChart.value?.dispose()
})
function drawEChart() {
if (customChart.value) {
customChart.value.dispose()
}
customChart.value = echarts.init(customChartRef.value as HTMLDivElement)
customChart.value?.setOption(props.option, true)
}
function echartShowLoading() {
customChart.value?.showLoading({
text: t('common_module.loading'),
color: '#000DFF',
textColor: '#333',
lineWidth: 3,
})
}
function echartHideLoading() {
customChart.value?.hideLoading()
}
defineExpose({
echartShowLoading,
echartHideLoading,
})
</script>
<template>
<div ref="customChartRef" :style="{ height: height, width: width }" :option="option" />
</template>
import i18n from '@/locales'
const { t } = i18n.global
export interface DiversityModeItem {
label: string
value: string
topP: number
temperature: number
communicationTurn: number
}
export const diversityModeList: DiversityModeItem[] = [
......@@ -12,27 +15,42 @@ export const diversityModeList: DiversityModeItem[] = [
value: 'accurate',
topP: 0.1,
temperature: 0.1,
communicationTurn: 3,
},
{
label: 'common_module.balance_mode',
value: 'balance',
topP: 0.7,
temperature: 0.5,
communicationTurn: 3,
},
{
label: 'common_module.creative_mode',
value: 'creative',
topP: 0.7,
temperature: 0.95,
communicationTurn: 3,
},
{
label: 'common_module.custom',
value: 'custom',
topP: 0.5,
temperature: 0.8,
communicationTurn: 3,
},
]
export const communicationTurnOptions = [
{
label: t('common_module.count_session_rounds', { count: 1 }),
value: 1,
},
{
label: t('common_module.count_session_rounds', { count: 5 }),
value: 5,
},
{
label: t('common_module.count_session_rounds', { count: 10 }),
value: 10,
},
{
label: t('common_module.count_session_rounds', { count: 15 }),
value: 15,
},
]
export enum ChannelType {
preview = 'preview',
multi_preview = 'multi_preview',
mall = 'mall',
index = 'index',
link_share = 'link_share',
api = 'api',
}
export enum ChannelText {
preview = 'analysis_module.debug',
multi_preview = 'analysis_module.multi_debug',
mall = 'analysis_module.agent_square',
index = 'analysis_module.index',
link_share = 'analysis_module.link_share',
api = 'analysis_module.api',
}
......@@ -56,9 +56,9 @@ common_module:
retry: 'Retry'
currently: 'Current'
multi_model_debug: 'Multi-model debugging'
accurate_mode: 'Precision model'
balance_mode: 'Equilibrium mode'
creative_mode: 'Creative mode'
accurate_mode: 'Precise'
balance_mode: 'Balance'
creative_mode: 'Creative'
all: 'All'
collect: 'Collect'
listing_successfully: 'Shelf success'
......@@ -114,6 +114,12 @@ common_module:
month: ' Month'
alipay: 'Alipay'
weChat: 'WeChat'
analysis: 'Analysis'
last_week: 'Last week'
last_month: 'Last month'
free: 'free'
points_per_time: '{count} points per time'
count_session_rounds: '1 session round | {count} session rounds'
dialogue_module:
continue_question_message: 'You can keep asking questions'
......@@ -239,12 +245,9 @@ personal_space_module:
question_answer_model: 'Question and answer model'
question_answer_model_desc: 'Used to summarize and generate reply results'
generate_diversity: 'Generative diversity'
accurate_mode: 'Precision model'
balance_mode: 'Equilibrium mode'
creative_mode: 'Creative mode'
topP: 'Top P'
topP_popover_message: 'When the model generates the output, it starts with the words with the highest probability until the total probability of these words accumulates to a value of Top p. This limits the model to choosing only these high-probability terms, thereby controlling the diversity of output content.'
temperature: 'Generative randomness'
temperature: 'Temperature'
temperature_popover_message: 'Used to control the diversity of model outputs. The recommended value is 0, and the larger the value, the greater the difference in the output content of the model'
communication_turn: 'Refer to session rounds'
communication_turn_popover_message: 'The maximum number of session rounds passed into the large model context. The recommended value is 2, the higher the value, the stronger the context correlation in multiple rounds of conversations, but the more Tokens are consumed'
......@@ -455,7 +458,7 @@ multi_model_dialogue_module:
not_find_agent: 'Application not present'
add_model: 'Increment model'
quit_test: 'Exit test'
model_setting: 'Model setup'
model_setting: 'Model setting'
agent_system: 'System configuration'
remove_dialogue: 'Remove'
one_click_configuration: 'One-click configuration'
......@@ -513,3 +516,23 @@ order_manage_module:
flagship_edition: 'Flagship Edition '
professional_edition: 'Professional Edition '
gift_pack: 'Gift Pack'
analysis_module:
today_usage: 'Today usage'
current_week_usage: 'Current week usage'
current_month_usage: 'Current month usage'
agent_users: 'Agent users'
send_messages: 'Send messages'
consume_points: 'Consume points'
usage_trend_chart: 'Usage trend chart'
monthly_user_count_comparison_by_channel: 'Monthly agent users comparison by channel'
monthly_proportion_of_messages_sent_and_points_consumed_by_channel: 'Monthly proportion of messages sent and points consumed by channel'
all_channels: 'All'
agent_debug: 'Agent debug'
debug: 'Debug'
multi_debug: 'Multi debug'
usage_channel: 'Usage channel'
index: 'Index'
agent_square: 'Agent square'
api: 'Api'
link_share: 'Link share'
......@@ -113,6 +113,12 @@ common_module:
month: '个月'
alipay: '支付宝'
weChat: '微信'
analysis: '分析'
last_week: '最近一周'
last_month: '最近一月'
free: '免费'
points_per_time: '{count}积分/次'
count_session_rounds: '{count}轮对话'
dialogue_module:
continue_question_message: '你可以继续提问'
......@@ -237,9 +243,6 @@ personal_space_module:
question_answer_model: '问答模型'
question_answer_model_desc: '用于总结生成回复结果'
generate_diversity: '生成多样性'
accurate_mode: '精准模式'
balance_mode: '平衡模式'
creative_mode: '创意模式'
topP: 'Top P'
topP_popover_message: '模型在生成输出时会从概率最高的词汇开始选择,直到这些词汇的总概率累积达到Top p值。这样可以限制模型只选择这些高概率的词汇,从而控制输出内容的多样性。'
temperature: '生成随机性'
......@@ -511,3 +514,23 @@ order_manage_module:
flagship_edition: '旗舰版'
professional_edition: '专业版'
gift_pack: '礼包'
analysis_module:
today_usage: '今日用量'
current_week_usage: '本周用量'
current_month_usage: '本月用量'
agent_users: '应用使用人数'
send_messages: '发送消息'
consume_points: '消耗积分'
usage_trend_chart: '用量趋势图'
monthly_user_count_comparison_by_channel: '分渠道本月使用人数对比图'
monthly_proportion_of_messages_sent_and_points_consumed_by_channel: '分渠道本月发送消息和消耗积分占比'
all_channels: '全部统计渠道'
agent_debug: '应用调试'
debug: '调试'
multi_debug: '多模型调试'
usage_channel: '使用渠道'
index: '首页'
agent_square: '应用广场'
api: 'api调用'
link_share: '网页链接'
......@@ -113,6 +113,12 @@ common_module:
month: '個月'
alipay: '支付寶'
weChat: '微信'
analysis: '分析'
last_week: '最近一週'
last_month: '最近一月'
free: '免費'
points_per_time: '{count}積分/次'
count_session_rounds: '{count}輪對話'
dialogue_module:
continue_question_message: '你可以繼續提問'
......@@ -237,9 +243,6 @@ personal_space_module:
question_answer_model: '問答模型'
question_answer_model_desc: '用於總結生成回覆結果'
generate_diversity: '生成多樣性'
accurate_mode: '精準模式'
balance_mode: '平衡模式'
creative_mode: '創意模式'
topP: 'Top P'
topP_popover_message: '模型在生成輸出時會從概率最高的詞彙開始選擇,直到這些詞彙的總概率累積達到Top p值。這樣可以限制模型只選擇這些高概率的詞彙,從而控制輸出內容的多樣性'
temperature: '生成隨機性'
......@@ -511,3 +514,23 @@ order_manage_module:
flagship_edition: '旗艦版'
professional_edition: '專業版'
gift_pack: '禮包'
analysis_module:
today_usage: '今日用量'
current_week_usage: '本週用量'
current_month_usage: '本月用量'
agent_users: '應用使用人數'
send_messages: '發送消息'
consume_points: '消耗積分'
usage_trend_chart: '用量趨勢圖'
monthly_user_count_comparison_by_channel: '分渠道本月使用人數對比圖'
monthly_proportion_of_messages_sent_and_points_consumed_by_channel: '分渠道本月發送消息和消耗積分佔比'
all_channels: '全部統計渠道'
agent_debug: '應用調試'
debug: '調試'
multi_debug: '多模型調試'
usage_channel: '使用渠道'
index: '首頁'
agent_square: '應用廣場'
api: 'api調用'
link_share: '網頁鏈接'
......@@ -2,7 +2,7 @@ import { type RouteRecordRaw } from 'vue-router'
export default [
{
path: '/share/web_source/:agentId?',
path: '/share/web_source/:agentId?/:channel?',
name: 'ShareWebApplication',
meta: {
rank: 1001,
......@@ -13,7 +13,7 @@ export default [
component: () => import('@/views/share/share-application-web.vue'),
},
{
path: '/share/mobile_source/:agentId?',
path: '/share/mobile_source/:agentId?/:channel?',
name: 'ShareMobileApplication',
meta: {
rank: 1001,
......
......@@ -29,7 +29,7 @@ export function defaultPersonalAppConfigState(): PersonalAppConfigState {
commModelConfig: {
largeModel: '文心4.0 (8K)',
topP: 0.7,
communicationTurn: 3,
communicationTurn: 5,
temperature: 0.5,
},
voiceConfig: {
......
import * as Echarts from 'echarts/core'
import { BarChart, PieChart, LineChart } from 'echarts/charts'
import {
TitleComponent,
LegendComponent,
TooltipComponent,
GridComponent,
DatasetComponent,
ToolboxComponent,
} from 'echarts/components'
import { LabelLayout, UniversalTransition } from 'echarts/features'
import { CanvasRenderer } from 'echarts/renderers'
import type { BarSeriesOption, PieSeriesOption, LineSeriesOption } from 'echarts/charts'
import type {
TitleComponentOption,
TooltipComponentOption,
GridComponentOption,
DatasetComponentOption,
ToolboxComponentOption,
DataZoomComponentOption,
LegendComponentOption,
} from 'echarts/components'
import type { ComposeOption } from 'echarts/core'
Echarts.use([
TitleComponent,
LegendComponent,
TooltipComponent,
GridComponent,
DatasetComponent,
ToolboxComponent,
LabelLayout,
UniversalTransition,
CanvasRenderer,
BarChart,
PieChart,
LineChart,
])
export type EChartsOption = ComposeOption<
| BarSeriesOption
| PieSeriesOption
| LineSeriesOption
| TitleComponentOption
| TooltipComponentOption
| GridComponentOption
| DatasetComponentOption
| ToolboxComponentOption
| DataZoomComponentOption
| LegendComponentOption
>
export const echarts = Echarts
......@@ -13,6 +13,7 @@ import { useScroll } from '@vueuse/core'
import { router } from '@/router'
import searchEmptyImage from '@/assets/images/search-empty.png'
import applicationEmptyImage from '@/assets/images/application-empty.png'
import { ChannelType } from '@/enums/channel'
interface MallCategory {
id: number
......@@ -145,7 +146,7 @@ function handleGetMallCategoryList() {
}
function handleToUseAgentApplication(agentId: string) {
router.push({ name: 'ShareWebApplication', params: { agentId: agentId } })
router.push({ name: 'ShareWebApplication', params: { agentId: agentId }, query: { channel: ChannelType.mall } })
}
function handleAddAgentApplications() {
......
......@@ -7,6 +7,7 @@ import CMessage from './c-message'
import { MessageItemInterface, MultiModelDialogueItem, QuestionMessageItem } from '../types'
import { fetchEventStreamSource } from '../utils/fetch-event-stream-source'
import { UploadStatus } from '@/enums/upload-status'
import { ChannelType } from '@/enums/channel'
import { useDialogueFile } from '@/composables/useDialogueFile'
const { t } = useI18n()
......@@ -139,6 +140,7 @@ function handleQuestionSubmit() {
temperature,
agentSystem,
modelNickName,
channel: ChannelType.multi_preview,
},
controller: modelItem.controller,
onMessage: (data: any) => {
......
......@@ -3,6 +3,7 @@ import { computed, h, nextTick, ref, useTemplateRef } from 'vue'
import { useI18n } from 'vue-i18n'
import { SelectOption } from 'naive-ui'
import { useVModel } from '@vueuse/core'
import { useSystemLanguageStore } from '@/store/modules/system-language'
import { MultiModelDialogueItem } from '../types'
import ModelSetting from './model-setting.vue'
import MessageList from './message-list.vue'
......@@ -12,6 +13,7 @@ interface Props {
modelListOptions: SelectOption[]
totalNum: number
isCurrent: boolean
modelPoints: number
}
const { t } = useI18n()
......@@ -25,6 +27,8 @@ const emit = defineEmits<{
resetConversation: []
}>()
const systemLanguageStore = useSystemLanguageStore()
const modelConfig = useVModel(props, 'modelDialogueItem', emit)
const messageListRef = useTemplateRef<InstanceType<typeof MessageList>>('messageListRef')
......@@ -60,6 +64,10 @@ const currentMoreOptions = computed(() => {
return currentOptions
})
const isEnLanguage = computed(() => {
return systemLanguageStore.currentLanguageInfo.key === 'en'
})
// 选择更多操作
function handleSelectMoreOption(key: string) {
switch (key) {
......@@ -100,7 +108,8 @@ function scrollToBottom() {
trigger="click"
:show-arrow="false"
:flip="false"
style="width: 386px; border-radius: 10px; box-shadow: 0 4px 13px 0 hsla(0deg, 0%, 0%, 0.08)"
style="border-radius: 10px; box-shadow: 0 4px 13px 0 hsla(0deg, 0%, 0%, 0.08)"
:class="isEnLanguage ? 'w-[440px]' : 'w-[386px]'"
@update:show="handleUpdateModelConfigShow"
>
<template #trigger>
......@@ -130,6 +139,7 @@ function scrollToBottom() {
v-model:model-config="modelConfig"
:model-list-options="modelListOptions"
:is-current="isCurrent"
:model-points="modelPoints"
@update-config="(modelConfig) => emit('updateConfig', modelConfig)"
@reset-conversation="emit('resetConversation')"
/>
......
......@@ -3,11 +3,13 @@ import { computed, h, ref, VNodeChild, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { SelectOption } from 'naive-ui'
import { MultiModelDialogueItem } from '../types'
import { DiversityModeItem, diversityModeList } from '@/data/agent-setting-data'
import { DiversityModeItem, diversityModeList, communicationTurnOptions } from '@/data/agent-setting-data'
import { useSystemLanguageStore } from '@/store/modules/system-language'
interface Props {
modelListOptions: SelectOption[]
isCurrent: boolean
modelPoints: number
}
const { t } = useI18n()
......@@ -19,9 +21,12 @@ const emit = defineEmits<{
resetConversation: []
}>()
const systemLanguageStore = useSystemLanguageStore()
const modelConfig = defineModel<MultiModelDialogueItem>('modelConfig', { required: true })
const currentDiversityMode = ref('balance')
const modelConsumePoints = ref(props.modelPoints || 0)
const currentModelNickName = computed({
get: () => (modelConfig.value.modelNickName === '' ? undefined : modelConfig.value.modelNickName),
......@@ -34,6 +39,29 @@ const isDisabledModelConfig = computed(() => {
return currentDiversityMode.value !== 'custom'
})
const isEnLanguage = computed(() => {
return systemLanguageStore.currentLanguageInfo.key === 'en'
})
const totalConsumePoints = computed(() => (communicationTurn: number) => {
if (!modelConsumePoints.value) {
return t('common_module.free')
}
switch (communicationTurn) {
case 1:
return t('common_module.points_per_time', { count: (modelConsumePoints.value * 1).toFixed(1) })
case 5:
return t('common_module.points_per_time', { count: (modelConsumePoints.value * 1).toFixed(1) })
case 10:
return t('common_module.points_per_time', { count: (modelConsumePoints.value * 2).toFixed(1) })
case 15:
return t('common_module.points_per_time', { count: (modelConsumePoints.value * 3).toFixed(1) })
}
return t('common_module.points_per_time', { count: modelConsumePoints.value.toFixed(1) })
})
watch(
() => modelConfig.value,
(newVal) => {
......@@ -60,6 +88,37 @@ watch(
function modelListRenderLabel(option: SelectOption): VNodeChild {
if (option.type === 'group') return `${option.label}`
return [
h('div', { class: 'flex items-center justify-between w-full' }, [
h('div', { class: 'flex items-center' }, [
h('div', {
style: {
width: '16px',
height: '16px',
marginRight: '6px',
flexShrink: 0,
background: `url(${option.icon})`,
backgroundSize: '100% 100%',
},
}),
h('span', {}, { default: () => option.label as string }),
]),
h(
'span',
{ class: 'text-xs text-[#0B7DFF]' },
{
default: () =>
option.points
? t('common_module.points_per_time', { count: (option.points as number).toFixed(1) })
: t('common_module.free'),
},
),
]),
]
}
// 模型选择项渲染
function modelListRenderTag({ option }: { option: SelectOption }) {
return [
h('div', { class: 'flex items-center' }, [
h('div', {
......@@ -80,6 +139,7 @@ function modelListRenderLabel(option: SelectOption): VNodeChild {
// 更新大模型
function handleUpdateLargeModel(_value: string, option: SelectOption) {
modelConfig.value.icon = option.icon as string
modelConsumePoints.value = (option.points || 0) as number
emit('resetConversation')
handleUpdateAgentConfig()
}
......@@ -93,6 +153,19 @@ function handleDiversityModeChange(diversityModeItem: DiversityModeItem) {
handleUpdateAgentConfig()
}
// 轮次列表项渲染
function renderCommunicationTurnLabel(option: SelectOption) {
return h('div', { class: 'flex justify-between w-full text-xs' }, [
h('span', {}, option.label as string),
h('span', { class: 'text-[#0B7DFF]' }, totalConsumePoints.value(option.value as number)),
])
}
// 轮次选择项渲染
function renderSelectCommunicationTurn({ option }: { option: SelectOption }) {
return h('span', {}, { default: () => option.label })
}
// 更新保存应用配置
function handleUpdateAgentConfig() {
props.isCurrent && emit('updateConfig', modelConfig.value)
......@@ -107,7 +180,9 @@ function handleUpdateAgentConfig() {
<n-select
v-model:value="currentModelNickName"
:options="modelListOptions"
:show-checkmark="false"
:render-label="modelListRenderLabel"
:render-tag="modelListRenderTag"
:placeholder="t('multi_model_dialogue_module.please_select_model')"
@update:value="handleUpdateLargeModel"
/>
......@@ -132,7 +207,7 @@ function handleUpdateAgentConfig() {
</ul>
<div class="mb-3 mt-[14px] flex h-[34px] items-center justify-between">
<div class="flex w-[70px] items-center">
<div class="flex items-center" :class="isEnLanguage ? 'w-[90px]' : 'w-[84px]'">
<span class="text-font-color font-600 select-none">
{{ t('personal_space_module.agent_module.agent_setting_module.agent_config_module.temperature') }}
</span>
......@@ -179,9 +254,9 @@ function handleUpdateAgentConfig() {
</n-input-number>
</div>
<div class="mt-4 text-xs">
<div class="mt-4">
<div class="mb-3 flex h-[34px] items-center justify-between">
<div class="flex w-[70px] items-center">
<div class="flex items-center" :class="isEnLanguage ? 'w-[90px]' : 'w-[84px]'">
<span class="text-font-color font-600 select-none">
{{ t('personal_space_module.agent_module.agent_setting_module.agent_config_module.topP') }}
</span>
......@@ -229,6 +304,28 @@ function handleUpdateAgentConfig() {
</div>
</div>
<div class="mt-4">
<div class="mb-3 flex h-[34px] items-center justify-between">
<div class="flex items-center" :class="isEnLanguage ? 'w-[152px]' : 'w-[84px]'">
<span class="text-font-color font-600 select-none">
{{ t('personal_space_module.agent_module.agent_setting_module.agent_config_module.communication_turn') }}
</span>
</div>
<div class="ml-3 flex flex-1">
<n-select
v-model:value="modelConfig.communicationTurn"
:show-checkmark="false"
:disabled="!modelConfig.modelNickName"
:options="communicationTurnOptions"
:render-label="renderCommunicationTurnLabel"
:render-tag="renderSelectCommunicationTurn"
@update:value="handleUpdateAgentConfig"
/>
</div>
</div>
</div>
<p class="text-font-color font-600 mb-1.5 select-none">
{{ t('multi_model_dialogue_module.agent_system') }}
</p>
......@@ -242,3 +339,11 @@ function handleUpdateAgentConfig() {
/>
</div>
</template>
<style lang="scss">
.n-base-select-option__content {
display: flex;
align-items: center;
width: 100%;
}
</style>
......@@ -9,7 +9,7 @@ import type { ValueOf } from 'type-fest'
import PageHeader from './components/page-header.vue'
import ModelDialogueItem from './components/model-dialogue-item.vue'
import FooterOperation from './components/footer-operation.vue'
import { MessageItemInterface, MultiModelDialogueItem } from './types'
import { MessageItemInterface, MultiModelDialogueItem, LargeModelItem } from './types'
import {
fetchGetDebugApplicationInfo,
fetchGetLargeModelInfo,
......@@ -23,8 +23,8 @@ import { useUserStore } from '@/store/modules/user'
const { t } = useI18n()
const currentRoute = useRoute()
const router = useRouter()
const currentRoute = useRoute()
const userStore = useUserStore()
......@@ -34,6 +34,7 @@ const isFullPageLoading = ref(false)
const agentId = ref('')
const agentApplicationConfig = ref<PersonalAppConfigState>(defaultPersonalAppConfigState())
const multiModelDialogueList = ref<MultiModelDialogueItem[]>([])
const currentModelPoints = ref(0)
let modelListOptions = reactive<SelectOption[]>([])
......@@ -63,7 +64,7 @@ onMounted(async () => {
const handleSavePersonalAppConfig = useThrottleFn(
async (modelDialogueItem: MultiModelDialogueItem) => {
const { modelNickName, topP, temperature, agentSystem } = modelDialogueItem
const { modelNickName, topP, temperature, agentSystem, communicationTurn } = modelDialogueItem
agentApplicationConfig.value = {
...agentApplicationConfig.value,
......@@ -73,6 +74,7 @@ const handleSavePersonalAppConfig = useThrottleFn(
topP,
temperature,
largeModel: modelNickName,
communicationTurn,
},
}
......@@ -89,6 +91,7 @@ function modelDialogueFactory() {
modelNickName: '',
topP: 0.1,
temperature: 1,
communicationTurn: 5,
agentSystem: '',
controller: null,
isAnswerResponseWait: false,
......@@ -109,6 +112,7 @@ async function handleGetApplicationDetail() {
currentModelConfig.temperature = agentApplicationConfig.value.commModelConfig.temperature
currentModelConfig.modelNickName = agentApplicationConfig.value.commModelConfig.largeModel
currentModelConfig.agentSystem = agentApplicationConfig.value.baseInfo.agentSystem
currentModelConfig.communicationTurn = agentApplicationConfig.value.commModelConfig.communicationTurn
multiModelDialogueList.value = [
currentModelConfig,
......@@ -126,7 +130,7 @@ async function handleGetApplicationDetail() {
// 获取大模型列表
async function handleGetLargeModelList() {
modelListOptions = []
const res = await fetchGetLargeModelList<{ owner: string; models: string[]; icon: string }[]>()
const res = await fetchGetLargeModelList<LargeModelItem[]>()
res.data.forEach((item) => {
modelListOptions.push({
......@@ -134,8 +138,9 @@ async function handleGetLargeModelList() {
label: item.owner,
key: item.owner,
children: item.models.map((model) => ({
label: model,
value: model,
label: model.modelNickName,
value: model.modelNickName,
points: model.points || 0,
style: { fontSize: '12px' },
icon: item.icon,
})),
......@@ -145,10 +150,11 @@ async function handleGetLargeModelList() {
// 根据大模型名称获取模型信息
async function handleGetLargeModelInfo() {
const res = await fetchGetLargeModelInfo<{ icon: string }>(multiModelDialogueList.value[0].modelNickName)
const res = await fetchGetLargeModelInfo<LargeModelItem>(multiModelDialogueList.value[0].modelNickName)
if (res.code === 0) {
multiModelDialogueList.value[0].icon = res.data.icon
currentModelPoints.value = res.data.models?.[0].points || 0
isFullPageLoading.value = false
}
}
......@@ -310,6 +316,7 @@ function handleBlockMessageResponse() {
:key="modelDialogueItem.id"
:model-dialogue-item="modelDialogueItem"
:is-current="modelDialogueItem.id === multiModelDialogueList[0].id"
:model-points="currentModelPoints"
:model-list-options="modelListOptions"
:total-num="multiModelDialogueList.length"
@update-config="handleSavePersonalAppConfig"
......
......@@ -18,6 +18,7 @@ export interface MultiModelDialogueItem {
topP: number
temperature: number
agentSystem: string
communicationTurn: number
controller: AbortController | null
isAnswerResponseWait: boolean
messageList: Map<string, MessageItemInterface>
......@@ -32,3 +33,9 @@ export interface MessageItemInterface {
isTextContentLoading: boolean
isAnswerResponseLoading: boolean
}
export interface LargeModelItem {
owner: string
models: { modelNickName: string; points: number }[]
icon: string
}
<script setup lang="ts">
import { computed, h, onMounted, readonly, ref, watch } from 'vue'
import { NTag, TreeSelectOption } from 'naive-ui'
import { useI18n } from 'vue-i18n'
import { useRoute } from 'vue-router'
import { fetchGetGeneralUsageData } from '@/apis/agent-analyze'
import { ChannelType } from '@/enums/channel'
import { useSystemLanguageStore } from '@/store/modules/system-language'
import UsagePanelItem from './components/usage-panel-item.vue'
import UsageTrendChart from './components/usage-trend-chart.vue'
import ChannelUserComparisonChart from './components/channel-user-comparison-chart.vue'
import ChannelUsageComparisonChart from './components/channel-usage-comparison-chart.vue'
import { UsageItemInfo } from './types'
const { t } = useI18n()
const { params } = useRoute()
const systemLanguageStore = useSystemLanguageStore()
const channelOptionList = [
{
label: t('analysis_module.agent_debug'),
key: 'agent_preview',
children: [
{
label: t('analysis_module.debug'),
key: ChannelType.preview,
},
{
label: t('analysis_module.multi_debug'),
key: ChannelType.multi_preview,
},
],
},
{
label: t('analysis_module.usage_channel'),
key: 'usage_channel',
children: [
{
label: t('analysis_module.index'),
key: ChannelType.index,
},
{
label: t('analysis_module.agent_square'),
key: ChannelType.mall,
},
{
label: t('analysis_module.link_share'),
key: ChannelType.link_share,
},
{
label: t('analysis_module.api'),
key: ChannelType.api,
},
],
},
]
const channelGroupList = readonly(channelOptionList.map((channelItem) => channelItem.key))
const allChannelList = handleGetAllChannelList(channelOptionList)
const selectChannelList = ref<string[]>([])
const generalUsageList = ref<UsageItemInfo[]>([])
const isEnLanguage = computed(() => {
return systemLanguageStore.currentLanguageInfo.key === 'en'
})
watch(
() => selectChannelList.value,
() => {
handleGetGeneralUsageData()
},
)
onMounted(() => {
selectChannelList.value = allChannelList
})
async function handleGetGeneralUsageData() {
const res = await fetchGetGeneralUsageData<UsageItemInfo[]>(params.agentId as string, selectChannelList.value)
if (res.code === 0) {
generalUsageList.value = res.data
}
}
function handleUpdateChannelList(channelList: string[]) {
if (channelList.length === 0) {
selectChannelList.value = allChannelList
return
}
selectChannelList.value = channelList
}
function handleRenderChannelLabel({ option }: { option: TreeSelectOption }) {
return h('span', { style: { marginRight: '24px' } }, { default: () => option.label as string })
}
function handleRenderSelectChannel({ option }: { option: TreeSelectOption }) {
if (
selectChannelList.value.length === allChannelList.length &&
selectChannelList.value.every((item) => allChannelList.includes(item))
) {
return option.key === allChannelList?.[0]
? h('span', { class: 'mb-[3px]' }, t('analysis_module.all_channels'))
: null
}
return h(NTag, { class: 'mr-[7px] mb-[3px]' }, { default: () => option.label as string })
}
function handleGetAllChannelList(options: TreeSelectOption[]) {
let channelList: string[] = []
function recurseChannelList(channels: TreeSelectOption[]) {
if (Array.isArray(channels)) {
channels.forEach((channel) => {
if (!channel.children || channel.children.length === 0) {
channelList.push(channel.key as string)
} else {
recurseChannelList(channel.children)
}
})
}
}
recurseChannelList(options)
return channelList
}
</script>
<template>
<div class="mx-auto flex flex-1 justify-center bg-[#F3F5F8]">
<div class="w-full max-w-[1920px] overflow-y-auto bg-[#F3F5F8] p-6">
<div class="mb-5 flex justify-end pr-[34px]">
<div class="max-w-[300px]" :class="isEnLanguage ? 'min-w-[124px]' : 'min-w-[154px]'">
<n-tree-select
v-model:value="selectChannelList"
multiple
cascade
checkable
check-strategy="child"
placement="bottom-end"
max-tag-count="responsive"
class="tree-tag"
:options="channelOptionList"
:menu-props="{ style: { marginTop: '6px', borderRadius: '5px' } }"
:consistent-menu-width="false"
:ellipsis-tag-popover-props="{ show: false }"
:default-expanded-keys="channelGroupList"
:render-label="handleRenderChannelLabel"
:render-tag="handleRenderSelectChannel"
@update:value="handleUpdateChannelList"
>
</n-tree-select>
</div>
</div>
<div class="gap-4.5 flex h-[168px] w-full">
<div v-for="(usageItem, index) in generalUsageList" :key="index" class="h-full flex-1 rounded-[20px] bg-white">
<UsagePanelItem :usage-item="usageItem" />
</div>
</div>
<div class="mt-5 w-full">
<UsageTrendChart :channel="selectChannelList" />
</div>
<div class="mt-5 grid h-[330px] w-full grid-cols-2 gap-5">
<div class="h-full">
<ChannelUserComparisonChart />
</div>
<div class="h-full">
<ChannelUsageComparisonChart />
</div>
</div>
</div>
</div>
</template>
<style scoped lang="scss">
@include custom-scrollbar(6px);
:deep(.tree-tag .n-base-selection) {
--n-height: 34px !important;
.n-base-selection-tag-wrapper .n-tag {
background: #f3f5f8 !important;
--n-border: none !important;
border-radius: 5px !important;
}
.n-base-selection-tag-wrapper {
padding: 0;
}
}
</style>
<script setup lang="ts">
interface Props {
title: string
}
defineProps<Props>()
</script>
<template>
<div class="flex items-start">
<span class="bg-theme-color mr-[5px] mt-1.5 inline-block h-[14px] w-1 flex-shrink-0 rounded-sm" />
<span class="text-[16px]">{{ title }}</span>
</div>
</template>
<script setup lang="ts">
import { onMounted, reactive, ref, useTemplateRef } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRoute } from 'vue-router'
import { fetchGetMonthConsumePointCount, fetchGetMonthSendMessageCount } from '@/apis/agent-analyze'
import CustomEchart from '@/components/custom-echart/custom-echart.vue'
import { ChannelText, ChannelType } from '@/enums/channel'
import { type EChartsOption } from '@/utils/echarts'
import AnalysisContentTitle from './analysis-content-title.vue'
import { ChannelCountItem } from '../types'
const { t } = useI18n()
const { params } = useRoute()
const channelUsageChartRef = useTemplateRef<InstanceType<typeof CustomEchart> | null>('channelUsageChartRef')
const defaultEchartOption: EChartsOption = {
title: [
{
subtext: t('analysis_module.send_messages'),
left: '24.5%',
top: '190px',
subtextStyle: {
fontSize: 14,
color: '#333333',
},
textAlign: 'center',
},
{
subtext: t('analysis_module.consume_points'),
left: '74.5%',
top: '190px',
subtextStyle: {
fontSize: 14,
color: '#333333',
},
textAlign: 'center',
},
],
legend: {
top: '20px',
right: '54px',
data: [{ name: t(ChannelText.link_share) }, { name: t(ChannelText.mall) }],
},
color: ['#00C2FF', '#454FFF'],
tooltip: {
trigger: 'item',
textStyle: {
fontWeight: 'normal',
},
},
series: [
{
type: 'pie',
top: '90px',
radius: ['50%', '70%'],
center: ['50%', '50%'],
encode: { value: 'count1', itemName: 'channel' },
label: {
position: 'outer',
alignTo: 'labelLine',
formatter: '{d}%',
bleedMargin: 5,
},
left: 0,
right: '50%',
},
{
type: 'pie',
top: '90px',
radius: ['50%', '70%'],
center: ['50%', '50%'],
encode: { value: 'count2', itemName: 'channel' },
label: {
position: 'outer',
alignTo: 'labelLine',
formatter: '{d}%',
bleedMargin: 5,
},
left: '50%',
right: 0,
},
],
}
const channelUsageChartOption = reactive<EChartsOption>({ ...defaultEchartOption })
const sendMessageChannelCountList = ref<ChannelCountItem[]>([])
const consumePointChannelCountList = ref<ChannelCountItem[]>([])
onMounted(() => {
handleGetMonthChannelProportion()
})
async function handleGetMonthChannelProportion() {
channelUsageChartRef.value?.echartShowLoading()
await handleGetMonthSendMessageCount()
await handleGetMonthConsumePointCount()
const channelMap = new Map(sendMessageChannelCountList.value.map((item) => [item.channel, item]))
const dataSource = consumePointChannelCountList.value.map((consumePointChannelCountItem) => {
const sendMessageChannelCountItem = channelMap.get(consumePointChannelCountItem.channel)
return sendMessageChannelCountItem
? {
channel: t(ChannelText[consumePointChannelCountItem.channel]),
count1: sendMessageChannelCountItem.count,
count2: consumePointChannelCountItem.count,
}
: consumePointChannelCountItem
})
channelUsageChartOption.dataset = {
source: dataSource,
}
channelUsageChartRef.value?.echartHideLoading()
}
async function handleGetMonthSendMessageCount() {
const res = await fetchGetMonthSendMessageCount<ChannelCountItem[]>(params.agentId as string, [
ChannelType.link_share,
ChannelType.mall,
])
if (res.code === 0) {
sendMessageChannelCountList.value = res.data
}
}
async function handleGetMonthConsumePointCount() {
const res = await fetchGetMonthConsumePointCount<ChannelCountItem[]>(params.agentId as string, [
ChannelType.link_share,
ChannelType.mall,
])
if (res.code === 0) {
consumePointChannelCountList.value = res.data
}
}
</script>
<template>
<div class="relative h-full w-full rounded-[20px] bg-white">
<div class="z-99 absolute left-[34px] top-5 mr-[270px]">
<AnalysisContentTitle
:title="t('analysis_module.monthly_proportion_of_messages_sent_and_points_consumed_by_channel')"
/>
</div>
<div class="h-full w-full">
<CustomEchart ref="channelUsageChartRef" :option="channelUsageChartOption" />
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted, reactive, useTemplateRef } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRoute } from 'vue-router'
import { fetchGetMonthUserCount } from '@/apis/agent-analyze'
import CustomEchart from '@/components/custom-echart/custom-echart.vue'
import { ChannelType } from '@/enums/channel'
import { type EChartsOption } from '@/utils/echarts'
import AnalysisContentTitle from './analysis-content-title.vue'
import { ChannelCountItem } from '../types'
interface ChannelUsersItem {
date: string
channelDataCount: ChannelCountItem[]
}
const { t } = useI18n()
const { params } = useRoute()
const channelUserChartRef = useTemplateRef<InstanceType<typeof CustomEchart> | null>('channelUserChartRef')
const defaultEchartOption: EChartsOption = {
legend: {
show: false,
right: '54px',
top: '20px',
},
xAxis: [
{
type: 'category',
},
],
yAxis: [{}],
tooltip: {
trigger: 'axis',
textStyle: {
fontWeight: 'normal',
},
},
series: [
{
name: t('analysis_module.link_share'),
type: 'bar',
encode: { x: 'date', y: 'linkShareCount' },
itemStyle: {
color: '#454FFF',
borderRadius: [5, 5, 0, 0],
},
},
{
name: t('analysis_module.agent_square'),
type: 'bar',
encode: { x: 'date', y: 'mallCount' },
itemStyle: {
color: '#00C2FF',
borderRadius: [5, 5, 0, 0],
},
},
],
grid: {
left: '58px',
right: '54px',
bottom: '46px',
top: '100px',
},
}
const channelUserChartOption = reactive({ ...defaultEchartOption })
onMounted(() => {
handleGetMonthUserCount()
})
async function handleGetMonthUserCount() {
channelUserChartRef.value?.echartShowLoading()
const res = await fetchGetMonthUserCount<ChannelUsersItem[]>(params.agentId as string, [
ChannelType.link_share,
ChannelType.mall,
])
if (res.code === 0) {
const dataSource = res.data.map((item) => {
const linkShareCount =
item.channelDataCount.find((channelItem) => channelItem.channel === ChannelType.link_share)?.count || 0
const mallCount =
item.channelDataCount.find((channelItem) => channelItem.channel === ChannelType.mall)?.count || 0
return {
date: item.date,
linkShareCount,
mallCount,
}
})
channelUserChartOption.dataset = {
source: dataSource,
}
channelUserChartOption.legend = {
...channelUserChartOption.legend,
show: true,
}
channelUserChartRef.value?.echartHideLoading()
}
}
</script>
<template>
<div class="relative h-full w-full rounded-[20px] bg-white">
<div class="z-99 absolute left-[34px] top-5 mr-[270px]">
<AnalysisContentTitle :title="t('analysis_module.monthly_user_count_comparison_by_channel')" />
</div>
<div class="h-full w-full">
<CustomEchart ref="channelUserChartRef" :option="channelUserChartOption" />
</div>
</div>
</template>
<script setup lang="ts">
import { useI18n } from 'vue-i18n'
import AnalysisContentTitle from './analysis-content-title.vue'
import { UsageItemInfo } from '../types'
enum UsageTimeDimension {
day = 'analysis_module.today_usage',
week = 'analysis_module.current_week_usage',
month = 'analysis_module.current_month_usage',
}
interface Props {
usageItem: UsageItemInfo
}
const { t } = useI18n()
defineProps<Props>()
const getUsageTimeDimension = (timeDimension: string): string => {
switch (timeDimension) {
case 'today':
return UsageTimeDimension.day
case 'week':
return UsageTimeDimension.week
case 'month':
return UsageTimeDimension.month
default:
return ''
}
}
</script>
<template>
<div class="py-5">
<AnalysisContentTitle :title="t(getUsageTimeDimension(usageItem.timeDimension))" class="ml-[34px]" />
<div class="text-font-color mt-4.5 flex w-full gap-2">
<div class="flex flex-1 flex-col items-center">
<div class="relative">
<div class="font-600 text-[30px]">
<n-number-animation :from="0" :to="usageItem.dataAnalyzeInfo?.usersCount?.num || 0" />
</div>
<div class="absolute right-[-23px] top-0">
<i
v-show="usageItem.dataAnalyzeInfo?.usersCount?.fluctuate === 1"
class="iconfont icon-increase text-[#2cb755]"
/>
<i
v-show="usageItem.dataAnalyzeInfo?.usersCount?.fluctuate === -1"
class="iconfont icon-decrease text-error-font-color"
/>
<i
v-show="usageItem.dataAnalyzeInfo?.usersCount?.fluctuate === 0"
class="iconfont icon-stable text-gray-font-color"
/>
</div>
</div>
<span class="mt-1.5">{{ t('analysis_module.agent_users') }}</span>
</div>
<div class="flex flex-1 flex-col items-center">
<div class="relative">
<div class="font-600 text-[30px]">
<n-number-animation :from="0" :to="usageItem.dataAnalyzeInfo?.usageCount?.num" />
</div>
<div class="absolute right-[-23px] top-0">
<i
v-show="usageItem.dataAnalyzeInfo?.usageCount?.fluctuate === 1"
class="iconfont icon-increase text-[#2cb755]"
/>
<i
v-show="usageItem.dataAnalyzeInfo?.usageCount?.fluctuate === -1"
class="iconfont icon-decrease text-error-font-color"
/>
<i
v-show="usageItem.dataAnalyzeInfo?.usageCount?.fluctuate === 0"
class="iconfont icon-stable text-gray-font-color"
/>
</div>
</div>
<span class="mt-1.5">{{ t('analysis_module.send_messages') }}</span>
</div>
<div class="flex flex-1 flex-col items-center">
<div class="relative">
<div class="font-600 text-[30px]">
<n-number-animation :from="0" :to="usageItem.dataAnalyzeInfo?.pointsCount?.num || 0" :precision="1" />
</div>
<div class="absolute right-[-23px] top-0">
<i
v-show="usageItem.dataAnalyzeInfo?.pointsCount?.fluctuate === 1"
class="iconfont icon-increase text-[#2cb755]"
/>
<i
v-show="usageItem.dataAnalyzeInfo?.pointsCount?.fluctuate === -1"
class="iconfont icon-decrease text-error-font-color"
/>
<i
v-show="usageItem.dataAnalyzeInfo?.pointsCount?.fluctuate === 0"
class="iconfont icon-stable text-gray-font-color"
/>
</div>
</div>
<span class="mt-1.5">{{ t('analysis_module.consume_points') }}</span>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { reactive, ref, useTemplateRef, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRoute } from 'vue-router'
import { fetchGetAgentDataTrend } from '@/apis/agent-analyze'
import CustomEchart from '@/components/custom-echart/custom-echart.vue'
import { type EChartsOption } from '@/utils/echarts'
import AnalysisContentTitle from './analysis-content-title.vue'
enum RangType {
week = 'week',
month = 'month',
customize = 'customize',
}
interface Props {
channel: string[]
}
interface AgentTrendDateItem {
date: string
usersCount: number
usageCount: number
totalPoints: number
}
const { t } = useI18n()
const { params } = useRoute()
const props = defineProps<Props>()
const usageEchartRef = useTemplateRef<InstanceType<typeof CustomEchart> | null>('usageEchartRef')
const sendMessagesEchartRef = useTemplateRef<InstanceType<typeof CustomEchart> | null>('sendMessagesEchartRef')
const consumePointsEchartRef = useTemplateRef<InstanceType<typeof CustomEchart> | null>('consumePointsEchartRef')
const currentRangType = ref(RangType.week)
const selectDateRange = ref<[string, string]>(['', ''])
const rangTypeOptionList = [
{
label: t('common_module.last_week'),
value: RangType.week,
},
{
label: t('common_module.last_month'),
value: RangType.month,
},
{
label: t('common_module.custom'),
value: RangType.customize,
},
]
const usageEchartOption = reactive<EChartsOption>({
...defaultEchartOption(t('analysis_module.agent_users'), { x: 'date', y: 'usersCount' }),
})
const sendTimesEchartOption = reactive<EChartsOption>({
...defaultEchartOption(t('analysis_module.send_messages'), { x: 'date', y: 'usageCount' }),
})
const consumePointsEchartOption = reactive<EChartsOption>({
...defaultEchartOption(t('analysis_module.consume_points'), { x: 'date', y: 'totalPoints' }),
})
const disableDateRange = (ts: number, type: 'start' | 'end', range: [number, number] | null) => {
if (type === 'start' && range !== null) {
return range[1] - ts >= 86400000 * 365 || ts > Date.now()
}
if (type === 'end' && range !== null) {
return ts - range[0] >= 86400000 * 365 || ts > Date.now()
}
return ts > Date.now()
}
watch(
() => props.channel,
() => {
handleGetAgentDataTrend()
},
)
watch(
() => currentRangType.value,
() => {
currentRangType.value !== RangType.customize && handleGetAgentDataTrend()
},
)
function defaultEchartOption(
seriesName: string = '',
encode: { x: string; y: string } = { x: 'date', y: 'usersCount' },
): EChartsOption {
return {
xAxis: {
type: 'category',
},
yAxis: {},
series: [
{
name: seriesName,
encode,
type: 'line',
lineStyle: {
color: '#000DFF',
},
label: {
show: true,
position: 'top',
},
},
],
tooltip: {
trigger: 'axis',
textStyle: {
fontWeight: 'normal',
},
},
grid: {
left: '58px',
right: '40px',
bottom: '20px',
},
color: ['#000DFF'],
}
}
async function handleGetAgentDataTrend() {
usageEchartRef.value?.echartShowLoading()
sendMessagesEchartRef.value?.echartShowLoading()
consumePointsEchartRef.value?.echartShowLoading()
const res = await fetchGetAgentDataTrend<AgentTrendDateItem[]>({
agentId: params.agentId as string,
channel: props.channel,
timeRange: {
rangType: currentRangType.value,
startTime: selectDateRange.value[0],
endTime: selectDateRange.value[1],
},
})
if (res.code === 0) {
const dataSource = res.data.map((item) => ({
date: item.date,
usersCount: item.usersCount,
usageCount: item.usageCount,
totalPoints: item.totalPoints,
}))
usageEchartOption.dataset = {
source: dataSource,
}
sendTimesEchartOption.dataset = {
source: dataSource,
}
consumePointsEchartOption.dataset = {
source: dataSource,
}
usageEchartRef.value?.echartHideLoading()
sendMessagesEchartRef.value?.echartHideLoading()
consumePointsEchartRef.value?.echartHideLoading()
}
}
function handleUpdateRangType(rangType: string) {
if (rangType !== RangType.customize) {
selectDateRange.value = ['', '']
}
}
async function handleUpdateDateRange(_value: [number, number], formattedValue: [string, string]) {
if (formattedValue[0] && formattedValue[1]) {
selectDateRange.value = formattedValue
await handleGetAgentDataTrend()
}
}
</script>
<template>
<div class="h-[356px] w-full rounded-[20px] bg-white">
<div class="mx-[34px] flex justify-between py-5">
<AnalysisContentTitle :title="t('analysis_module.usage_trend_chart')" />
<div class="flex gap-3">
<n-select
v-model:value="currentRangType"
:options="rangTypeOptionList"
class="w-[124px]! text-[#84868c]!"
@update:value="handleUpdateRangType"
/>
<n-date-picker
v-show="currentRangType === 'customize'"
:is-date-disabled="disableDateRange"
type="daterange"
clearable
class="w-[250px]!"
@update:value="handleUpdateDateRange"
/>
</div>
</div>
<div class="flex h-[240px]">
<div class="text-font-color relative h-full flex-1 border-r border-r-[#E2E2E2]">
<div class="absolute left-1/2 flex translate-x-[-50%] items-center gap-[5px]">
<i class="iconfont icon-user text-[14px]" />
<span>{{ t('analysis_module.agent_users') }}</span>
</div>
<CustomEchart ref="usageEchartRef" :option="usageEchartOption" />
</div>
<div class="text-font-color relative h-full flex-1 border-r border-r-[#E2E2E2]">
<div class="absolute left-1/2 flex translate-x-[-50%] items-center gap-[5px]">
<i class="iconfont icon-send-times text-[14px]" />
<span>{{ t('analysis_module.send_messages') }}</span>
</div>
<CustomEchart ref="sendMessagesEchartRef" :option="sendTimesEchartOption" />
</div>
<div class="relative h-full flex-1">
<div class="absolute left-1/2 flex translate-x-[-50%] items-center gap-[5px]">
<i class="iconfont icon-consume text-[14px]" />
<span>{{ t('analysis_module.consume_points') }}</span>
</div>
<CustomEchart ref="consumePointsEchartRef" :option="consumePointsEchartOption" />
</div>
</div>
</div>
</template>
import { ChannelType } from '@/enums/channel'
export interface UsageItemInfo {
timeDimension: 'today' | 'week' | 'month'
dataAnalyzeInfo: {
usersCount: {
num: number
fluctuate: 1 | -1 | 0 // 1 上涨 -1 下跌 0 无变化
}
usageCount: {
num: number
fluctuate: 1 | -1 | 0
}
pointsCount: {
num: number
fluctuate: 1 | -1 | 0
}
}
}
export interface ChannelCountItem {
channel: ChannelType
count: number
}
......@@ -5,7 +5,7 @@ import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router'
import { Emitter } from 'mitt'
import { Howl } from 'howler'
import { ValueOf } from 'type-fest'
import type { ValueOf } from 'type-fest'
import MessageList from './components/message-list.vue'
import FooterInput from './components/footer-input.vue'
import MemoryPreviewModal from './components/memory-preview-modal.vue'
......
......@@ -10,6 +10,7 @@ import { UploadStatus } from '@/enums/upload-status'
import { useDialogueFile } from '@/composables/useDialogueFile'
import { TEXTTOSPEECH_WS_URL } from '@/config/base-url'
import WebSocketCtr from '@/utils/web-socket-ctr'
import { ChannelType } from '@/enums/channel'
interface Props {
messageList: Map<string, ConversationMessageItem>
......@@ -206,6 +207,7 @@ function handleMessageSend() {
payload: {
agentId: agentId.value,
fileUrls: uploadFileList.value.map((item) => item.url),
channel: ChannelType.preview,
messages,
},
controller,
......
<script setup lang="ts">
import { computed, h, onMounted, reactive, ref, VNodeChild } from 'vue'
import { computed, h, onMounted, reactive, ref } from 'vue'
import { SelectOption } from 'naive-ui'
import { useI18n } from 'vue-i18n'
import { Help, Down } from '@icon-park/vue-next'
import { PersonalAppConfigState } from '@/store/types/personal-app-config'
import { useSystemLanguageStore } from '@/store/modules/system-language'
import { DiversityModeItem, diversityModeList } from '@/data/agent-setting-data'
import { DiversityModeItem, diversityModeList, communicationTurnOptions } from '@/data/agent-setting-data'
import { fetchGetLargeModelInfo, fetchGetLargeModelList } from '@/apis/agent-application'
interface LargeModelItem {
owner: string
models: { modelNickName: string; points: number }[]
icon: string
}
const { t } = useI18n()
const systemLanguageStore = useSystemLanguageStore()
let modalListOptions = reactive<SelectOption[]>([])
let modalListRenderLabel: (option: SelectOption) => VNodeChild
const commModelConfig = defineModel<PersonalAppConfigState['commModelConfig']>('commModelConfig', { required: true })
const currentLargeModelIcon = ref('')
const currentDiversityMode = ref('balance')
const modelConsumePoints = ref(0)
const modelSettingWidth = ref(systemLanguageStore.currentLanguageInfo.key === 'en' ? '508px' : '420px')
const sliderLabelWidth = ref(systemLanguageStore.currentLanguageInfo.key === 'en' ? '158px' : '105px')
......@@ -31,6 +37,25 @@ const isDisabledCommModelConfig = computed(() => {
return currentDiversityMode.value !== 'custom'
})
const totalConsumePoints = computed(() => (communicationTurn: number) => {
if (!modelConsumePoints.value) {
return t('common_module.free')
}
switch (communicationTurn) {
case 1:
return t('common_module.points_per_time', { count: (modelConsumePoints.value * 1).toFixed(1) })
case 5:
return t('common_module.points_per_time', { count: (modelConsumePoints.value * 1).toFixed(1) })
case 10:
return t('common_module.points_per_time', { count: (modelConsumePoints.value * 2).toFixed(1) })
case 15:
return t('common_module.points_per_time', { count: (modelConsumePoints.value * 3).toFixed(1) })
}
return t('common_module.points_per_time', { count: modelConsumePoints.value.toFixed(1) })
})
onMounted(() => {
handleGetLargeModelList()
handleGetLargeModelInfo()
......@@ -55,7 +80,7 @@ onMounted(() => {
// 获取大模型列表
async function handleGetLargeModelList() {
modalListOptions = []
const res = await fetchGetLargeModelList<{ owner: string; models: string[]; icon: string }[]>()
const res = await fetchGetLargeModelList<LargeModelItem[]>()
res.data.forEach((item) => {
modalListOptions.push({
......@@ -63,55 +88,100 @@ async function handleGetLargeModelList() {
label: item.owner,
key: item.owner,
children: item.models.map((model) => ({
label: model,
value: model,
label: model.modelNickName,
value: model.modelNickName,
points: model.points || 0,
style: { fontSize: '12px' },
icon: item.icon,
})),
})
})
modalListRenderLabel = (option: SelectOption): VNodeChild => {
if (option.type === 'group') return `${option.label}`
return [
h('div', { class: 'flex items-center' }, [
h('div', {
style: {
width: '16px',
height: '16px',
marginRight: '6px',
flexShrink: 0,
background: `url(${option.icon})`,
backgroundSize: '100% 100%',
},
}),
h('span', {}, { default: () => option.label as string }),
]),
]
}
}
// 获取当前大模型信息
async function handleGetLargeModelInfo() {
const res = await fetchGetLargeModelInfo<{ icon: string }>(commModelConfig.value.largeModel)
const res = await fetchGetLargeModelInfo<LargeModelItem>(commModelConfig.value.largeModel)
if (res.code === 0) {
currentLargeModelIcon.value = res.data.icon
modelConsumePoints.value = res.data.models?.[0].points || 0
}
}
// 更换大模型
function handleUpdateLargeModel(_value: string, option: SelectOption) {
currentLargeModelIcon.value = option.icon as string
modelConsumePoints.value = (option.points || 0) as number
}
function handleDiversityModeChange(diversityModeItem: DiversityModeItem) {
const { value, topP, temperature, communicationTurn } = diversityModeItem
const { value, topP, temperature } = diversityModeItem
currentDiversityMode.value = value
commModelConfig.value.topP = topP
commModelConfig.value.temperature = temperature
commModelConfig.value.communicationTurn = communicationTurn
}
// 模型列表项渲染
function modalListRenderLabel(option: SelectOption) {
if (option.type === 'group') return `${option.label}`
return [
h('div', { class: 'flex items-center w-full' }, [
h('div', {
style: {
width: '16px',
height: '16px',
marginRight: '6px',
flexShrink: 0,
background: `url(${option.icon})`,
backgroundSize: '100% 100%',
},
}),
h('span', {}, { default: () => option.label as string }),
]),
h(
'span',
{ class: 'text-[12px] text-[#0B7DFF]' },
{
default: () =>
option.points
? t('common_module.points_per_time', { count: (option.points as number).toFixed(1) })
: t('common_module.free'),
},
),
]
}
// 模型选择项渲染
function modalListRenderTag({ option }: { option: SelectOption }) {
return h('div', { class: 'flex justify-between' }, [
h('div', { class: 'flex items-center' }, [
h('div', {
style: {
width: '16px',
height: '16px',
marginRight: '6px',
flexShrink: 0,
background: `url(${option.icon})`,
backgroundSize: '100% 100%',
},
}),
h('span', {}, { default: () => option.label as string }),
]),
])
}
// 轮次列表项渲染
function renderCommunicationTurnLabel(option: SelectOption) {
return h('div', { class: 'flex justify-between w-full text-xs' }, [
h('span', {}, option.label as string),
h('span', { class: 'text-[#0B7DFF]' }, totalConsumePoints.value(option.value as number)),
])
}
// 轮次选择项渲染
function renderSelectCommunicationTurn({ option }: { option: SelectOption }) {
return h('span', { class: 'text-xs' }, { default: () => option.label })
}
</script>
......@@ -146,7 +216,9 @@ function handleDiversityModeChange(diversityModeItem: DiversityModeItem) {
v-model:value="commModelConfig.largeModel"
class="model-select"
:options="modalListOptions"
:show-checkmark="false"
:render-label="modalListRenderLabel"
:render-tag="modalListRenderTag"
@update:value="handleUpdateLargeModel"
/>
......@@ -293,25 +365,15 @@ function handleDiversityModeChange(diversityModeItem: DiversityModeItem) {
</NPopover>
</div>
<div class="mx-5 flex flex-1">
<NSlider
<div class="ml-5 flex flex-1">
<n-select
v-model:value="commModelConfig.communicationTurn"
:default-value="3"
:step="1"
:min="0"
:max="100"
:show-checkmark="false"
:options="communicationTurnOptions"
:render-label="renderCommunicationTurnLabel"
:render-tag="renderSelectCommunicationTurn"
/>
<span class="ml-4 w-8">{{ commModelConfig.communicationTurn }}</span>
</div>
<NInputNumber
v-model:value="commModelConfig.communicationTurn"
:step="1"
:min="0"
:max="100"
size="small"
class="common-model-config-input-number w-[90px]!"
placeholder=""
/>
</div>
</div>
</NPopover>
......@@ -332,3 +394,11 @@ function handleDiversityModeChange(diversityModeItem: DiversityModeItem) {
}
}
</style>
<style lang="scss">
.n-base-select-option__content {
display: flex;
align-items: center;
width: 100%;
}
</style>
......@@ -173,7 +173,7 @@ async function handleCreateKnowledgeNextStep(createKnowledgeData: KnowledgeFormD
<div class="flex w-full justify-end px-3">
<div class="gap-4.5 flex items-center">
<i
class="iconfont icon-shuaxin text-gray-font-color hover:text-font-color cursor-pointer"
class="iconfont icon-huanyihuan text-gray-font-color hover:text-font-color cursor-pointer"
@click="handleGetKnowledgeList"
/>
......
......@@ -53,6 +53,10 @@ const agentAppOptionList = [
value: 'publish',
label: 'common_module.publish',
},
{
value: 'analysis',
label: 'common_module.analysis',
},
]
const isShowModifiedTime = computed(() => {
......@@ -155,7 +159,7 @@ async function handlePublishApplication() {
</script>
<template>
<header class="h-navbar flex w-full items-center justify-between bg-[#f2f5f9] px-5 shadow-[inset_0_-1px_#e8e9eb]">
<header class="h-navbar flex w-full items-center justify-between bg-[#F3F5F8] px-5 shadow-[inset_0_-1px_#e8e9eb]">
<div class="flex flex-1 items-center">
<CustomIcon
icon="weui:back-outlined"
......
......@@ -6,6 +6,7 @@ import { useI18n } from 'vue-i18n'
import PageNarBar from './components/page-narbar.vue'
import AgentConfig from './components/agent-config/agent-config.vue'
import AgentPublish from './components/agent-publish.vue'
import AgentAnalysis from './components/agent-analysis/agent-analysis.vue'
import { usePersonalAppConfigStore } from '@/store/modules/personal-app-config'
import { PersonalAppConfigState } from '@/store/types/personal-app-config'
import { fetchGetDebugApplicationInfo } from '@/apis/agent-application'
......@@ -84,6 +85,10 @@ function handleChangeAgentAppTabKey(currentTabKey: string) {
<div v-if="currentAgentAppTabKey === 'publish'" class="flex h-full w-full flex-1">
<AgentPublish />
</div>
<div v-if="currentAgentAppTabKey === 'analysis'" class="flex h-full w-full flex-1">
<AgentAnalysis />
</div>
</div>
<Transition name="mask" mode="out-in">
......
......@@ -3,6 +3,7 @@ import { computed, inject, onMounted, onUnmounted, ref } from 'vue'
import { Emitter } from 'mitt'
import { useI18n } from 'vue-i18n'
import { nanoid } from 'nanoid'
import { useRoute } from 'vue-router'
import { fetchCustomEventSource } from '@/composables/useEventSource'
import { useUserStore } from '@/store/modules/user'
import { UploadStatus } from '@/enums/upload-status'
......@@ -10,6 +11,7 @@ import { useDialogueFile } from '@/composables/useDialogueFile'
import { useLayoutConfig } from '@/composables/useLayoutConfig'
import { TEXTTOSPEECH_WS_URL } from '@/config/base-url'
import WebSocketCtr from '@/utils/web-socket-ctr'
import { ChannelType } from '@/enums/channel'
interface Props {
agentId: string
......@@ -25,6 +27,8 @@ interface Props {
const { t } = useI18n()
const { query } = useRoute()
const props = defineProps<Props>()
const emit = defineEmits<{
......@@ -182,6 +186,7 @@ function handleMessageSend() {
dialogsId: props.dialogsId,
fileUrls: uploadFileList.value.map((item) => item.url),
input,
channel: query.channel || ChannelType.link_share,
},
controller,
onMessage: (data: any) => {
......
......@@ -3,7 +3,7 @@ import { computed, onMounted, onUnmounted, ref, shallowRef } from 'vue'
import { useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n'
import { Howl } from 'howler'
import { ValueOf } from 'type-fest'
import type { ValueOf } from 'type-fest'
import PageHeader from './components/mobile-page-header.vue'
import Preamble from './components/preamble.vue'
import MessageList from './components/message-list.vue'
......@@ -66,6 +66,7 @@ onMounted(async () => {
router.replace({
name: 'ShareWebApplication',
params: { agentId: agentId.value },
query: router.currentRoute.value.query,
})
}
......
......@@ -3,7 +3,7 @@ import { computed, onMounted, onUnmounted, ref, shallowRef } from 'vue'
import { useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n'
import { Howl } from 'howler'
import { ValueOf } from 'type-fest'
import type { ValueOf } from 'type-fest'
import PageHeader from './components/web-page-header.vue'
import Preamble from './components/preamble.vue'
import MessageList from './components/message-list.vue'
......@@ -69,6 +69,7 @@ onMounted(async () => {
router.replace({
name: 'ShareMobileApplication',
params: { agentId: agentId.value },
query: router.currentRoute.value.query,
})
}
......
......@@ -114,6 +114,12 @@ declare namespace I18n {
month: string
alipay: string
weChat: string
analysis: string
last_week: string
last_month: string
free: string
points_per_time: string
count_session_rounds: string
dialogue_module: {
continue_question_message: string
......@@ -248,9 +254,6 @@ declare namespace I18n {
question_answer_model: string
question_answer_model_desc: string
generate_diversity: string
accurate_mode: string
balance_mode: string
creative_mode: string
topP: string
topP_popover_message: string
temperature: string
......@@ -530,5 +533,26 @@ declare namespace I18n {
professional_edition: string
gift_pack: string
}
analysis_module: {
today_usage: string
current_week_usage: string
current_month_usage: string
agent_users: string
send_messages: string
consume_points: string
usage_trend_chart: string
monthly_user_count_comparison_by_channel: string
monthly_proportion_of_messages_sent_and_points_consumed_by_channel: string
all_channels: string
agent_debug: string
debug: string
multi_debug: string
usage_channel: string
index: string
agent_square: string
api: string
link_share: string
}
}
}
......@@ -45,6 +45,7 @@ export default defineConfig(({ command, mode }) => {
marked: ['marked'],
'github-markdown-css': ['github-markdown-css'],
'marked-highlight': ['marked-highlight'],
echarts: ['echarts'],
},
},
},
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment