Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions ui/src/api/API_README.md
Original file line number Diff line number Diff line change
Expand Up @@ -332,3 +332,13 @@ API 对象和工作空间上下文,作为该抽屉的范围选择例外;用
`putKnowledgeWorkflow` 接受互斥的 `work_flow` 保存载荷或 `work_flow_template` 覆盖载荷,覆盖成功后由 View 重载详情。
`exportKnowledgeWorkflow(knowledgeId, name)` 通过 GET `/<knowledgeId>/workflow/export` 下载 `.kbwf` 文件,
只导出工作流,不调用包含文档的知识库包导出接口。

### 工作空间首页

`admin/workspace/homepage.ts` 维护 `/workspace/<workspaceId>/homepage` 的四类资源汇总、
每日趋势、三类排行分页、Tokens/对话总量及三类导出。每个接口均以必填的 `workspaceId` 为首个参数,
由调用方显式传入,API 文件不再自行读取当前路由。
资源汇总、日期范围、趋势及排行记录类型在 `types/homepage.ts`,统一从 `@/api/types` 导入。
`getRanking` 与 `exportRanking` 通过 `HomeRankingKind` 选择后端排行路径,名称及起止日期
筛选保持一致;分页使用 `ParamsPage` 与 `ResponsePage`。导出沿用 `getExportFile`。
工作空间总量接口返回数值,不与 System 首页的对象响应混用。
57 changes: 57 additions & 0 deletions ui/src/api/admin/workspace/homepage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { get, getExportFile } from '../core/request'
import type { ParamsPage, ResponsePage } from '../core/types'
import type {
HomeApplicationAggregation,
HomeKnowledgeAggregation,
HomeToolAggregation,
HomeModelAggregation,
HomeDateRange,
HomeMonitoringDay,
HomeRankingKind,
HomeRankingRecord,
} from '@/api/types'

const getPrefix = (workspaceId: string) => `/workspace/${workspaceId}/homepage`
/** 获取智能体数量与发布状态。 */
const getApplicationAggregation = (workspaceId: string) => get<HomeApplicationAggregation>(`${getPrefix(workspaceId)}/application/aggregation`)
/** 获取知识库与文档数量。 */
const getKnowledgeAggregation = (workspaceId: string) => get<HomeKnowledgeAggregation>(`${getPrefix(workspaceId)}/knowledge/aggregation`)
/** 获取工具数量与类型分布。 */
const getToolAggregation = (workspaceId: string) => get<HomeToolAggregation>(`${getPrefix(workspaceId)}/tool/aggregation`)
/** 获取模型数量与类型分布。 */
const getModelAggregation = (workspaceId: string) => get<HomeModelAggregation>(`${getPrefix(workspaceId)}/model/aggregation`)
/** 获取指定日期及智能体范围内的每日使用趋势。 */
const getMonitoring = (workspaceId: string, range: HomeDateRange, applicationId?: string) =>
get<HomeMonitoringDay[]>(`${getPrefix(workspaceId)}/monitoring/aggregation`, {
...range,
...(applicationId ? { application_id: applicationId } : {}),
})
/** 获取工作空间日期范围内的 Tokens 总量。 */
const getTokensAggregation = (workspaceId: string, range: HomeDateRange) => get<number>(`${getPrefix(workspaceId)}/tokens/aggregation`, { ...range })
/** 获取工作空间日期范围内的对话轮次。 */
const getChatRecordAggregation = (workspaceId: string, range: HomeDateRange) =>
get<number>(`${getPrefix(workspaceId)}/chat_record/aggregation`, { ...range })

const rankingPaths = { tokens: 'tokens_ranking', questions: 'question_ranking', userTokens: 'user_tokens_ranking' }

/** 分页查询智能体或用户使用排行。 */
const getRanking = (workspaceId: string, kind: HomeRankingKind, page: ParamsPage, range: HomeDateRange, name?: string) =>
get<ResponsePage<HomeRankingRecord>>(`${getPrefix(workspaceId)}/application/${rankingPaths[kind]}/${page.currentPage}/${page.pageSize}`, {
...range,
...(name ? { name } : {}),
})
/** 按当前日期与名称筛选导出完整排行。 */
const exportRanking = (workspaceId: string, kind: HomeRankingKind, range: HomeDateRange, name?: string) =>
getExportFile(`${rankingPaths[kind]}.xlsx`, `${getPrefix(workspaceId)}/${rankingPaths[kind]}/export`, { ...range, ...(name ? { name } : {}) })

export default {
getApplicationAggregation,
getKnowledgeAggregation,
getToolAggregation,
getModelAggregation,
getMonitoring,
getTokensAggregation,
getChatRecordAggregation,
getRanking,
exportRanking,
}
54 changes: 54 additions & 0 deletions ui/src/api/types/homepage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/** 首页资源汇总,由后端按当前用户可见范围统计。 */
export interface HomeApplicationAggregation {
total: number
publish_count: number
un_publish_count: number
}
export interface HomeKnowledgeAggregation {
total: number
document_count: number
failure_count: number
}
export interface HomeToolAggregation {
total: number
custom_count: number
workflow_count: number
skill_count: number
mcp_count: number
data_source_count: number
}
export interface HomeModelAggregation {
total: number
llm_count: number
embedding_count: number
}
export interface HomeDateRange {
start_time: string
end_time: string
}
export interface HomeMonitoringDay {
day: string
customer_num: number
customer_added_count: number
chat_record_count: number
tokens_num: number
star_num: number
trample_num: number
}
/**
* 首页排行类型:
* - tokens:按智能体的 Tokens 消耗排行,数值取 total_tokens。
* - questions:按智能体的对话次数排行,数值取 chat_record_count。
* - userTokens:按用户的 Tokens 消耗排行,数值取 total_tokens,名称取 asker.username。
*/
export type HomeRankingKind = 'tokens' | 'questions' | 'userTokens'
export interface HomeRankingRecord {
id?: string
name?: string
chat_user_id?: string
chat_user_type?: string
asker?: { username?: string } | null
total_tokens?: number
chat_record_count: number
chat_user_count?: number
}
2 changes: 2 additions & 0 deletions ui/src/api/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,5 @@ export type * from './trigger'
export type * from './related-resources'
export type * from './workflow-version'
export type * from './workflow-template'

export type * from './homepage'
2 changes: 1 addition & 1 deletion ui/src/assets/iconfont.js

Large diffs are not rendered by default.

25 changes: 24 additions & 1 deletion ui/src/components/COMPONENT_README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ import { MkDynamicsForm, MkDynamicsFormConstructor } from '@/components/mk-dynam
- Props、Emits、Slots 保持类型化。类型归属遵循 [API_README.md](../api/API_README.md),
API 与组件共用类型从 `@/api/types` 导入。
- 样式默认 `scoped`;组件专属样式留在组件目录,应用级规则放在 `src/styles`。
- 今后新增 Card、`div` 等展示组件或布局元素的循环时,统一使用外层
`<template v-for="..." :key="...">` 承载循环与唯一键,内部实际标签保留样式、Props、
事件及内容,避免将循环指令与展示属性混写。此规则适用于页面、公共组件、对话面板、
布局和工作流画布;已有循环暂不批量改写。
- 不编写 `aria-label`;按钮前添加简短中文用途注释,已有注释不重复添加。
- `global` 不创建仅用于二次导出的聚合入口。
- `el-input-number` 设置 `controls-position="right"` 时,同时设置 `align="left"`。
Expand Down Expand Up @@ -384,12 +388,31 @@ const avatar = ref('')
还会优先展示 Theme Store 中配置的 `loginLogo`。两个组件都只接收可选的 `height`,其余主题与
Logo 数据统一从 Theme Store 获取。

### MkEchart、MkLineChart

手动导入 `mk-echart/index.vue` 的 `MkEchart` 接收原生 ECharts `option`,负责实例创建、
深层配置更新、容器尺寸监听和卸载销毁。通过元素 Ref 定位,不需要唯一 DOM ID;隐藏容器
有实际尺寸后才初始化。`width` 默认 `100%`,`height` 默认 `200px`,公开 `resize()`。
基础容器注册 Canvas 渲染器,具体图表由对应封装按需注册。

折线图从 `mk-echart/LineCharts.vue` 导入为 `MkLineChart`,接收
`option: { xData, yData }`,类型由同目录 `types.ts` 提供。
`yData` 使用 ECharts 折线系列配置,可传 `name`、`data`、`smooth` 等。默认开启面积填充,
透明度为 `0.05`;`area: false` 可关闭默认填充,显式 `areaStyle` 可覆盖默认配置。尺寸属性与基础容器一致。
标题由页面展示;内置图例、坐标轴与提示,两条及以上系列显示图例,单条隐藏。`valueFormatter` 默认使用 `numberFormat`,Tokens 图表传入 `formatTokenNumber`,当前图表颜色直接使用固定颜色值,不跟随主题切换。

```vue
<MkLineChart :option="{ xData: dates, yData: [{ name: '对话轮次', data: counts, area: true }] }" />
```

### MkDateRange

组合日期预设下拉框和自定义日期区间选择器。默认显示“过去 7 天”,仅在用户修改筛选条件时通过
`change` 返回 `{ startTime, endTime }`;组件挂载时不主动触发 `change`。预设日期的 `endTime` 为
空字符串,自定义日期清空时两个字段均为空字符串。组件不绑定具体接口字段,使用方负责初始化
当前日期(`YYYY-MM-DD`);自定义日期缺少结束日期时同样使用当前日期,清空时开始日期为空。组件不绑定具体接口字段,使用方负责初始化
默认查询参数,并将筛选结果映射为业务查询参数。
`defaultValue` 接收 `{ startTime, endTime }`,仅在挂载时回填:匹配预设范围时选中对应预设,
否则显示自定义日期。切换到自定义时保留当前范围,不立即触发查询,选定日期后触发 `change`。

### MkDragUpload

Expand Down
19 changes: 15 additions & 4 deletions ui/src/components/mk-date-range/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ defineOptions({ name: 'MkDateRange' })

type DatePreset = 7 | 30 | 90 | 183 | 'custom'

const props = defineProps<{ defaultValue?: MkDateRangeValue }>()
const emit = defineEmits<{ change: [value: MkDateRangeValue] }>()

const datePresetOptions: OptionItem<DatePreset>[] = [
Expand All @@ -18,15 +19,25 @@ const datePresetOptions: OptionItem<DatePreset>[] = [
{ label: '自定义', value: 'custom' },
]

const datePreset = ref<DatePreset>(7)
const customDateRange = ref<string[]>([])
/* 默认范围只在挂载时回填,不触发查询。 */
const initialRange = props.defaultValue ?? { startTime: beforeDay(7), endTime: beforeDay(0) }
const initialEndTime = initialRange.endTime || beforeDay(0)
const datePreset = ref<DatePreset>(
datePresetOptions.find(({ value }) => value !== 'custom' && initialRange.startTime === beforeDay(value) && initialEndTime === beforeDay(0))
?.value ?? 'custom',
)
const customDateRange = ref<string[] | null>(initialRange.startTime ? [initialRange.startTime, initialEndTime] : [])

function handleDatePresetChange(preset: DatePreset) {
emit('change', { startTime: beforeDay(preset), endTime: '' })
if (preset === 'custom') return
const startTime = beforeDay(preset)
const endTime = beforeDay(0)
customDateRange.value = [startTime, endTime]
emit('change', { startTime, endTime })
}

function handleCustomDateRangeChange() {
emit('change', { startTime: customDateRange.value[0] ?? '', endTime: customDateRange.value[1] ?? '' })
emit('change', { startTime: customDateRange.value?.[0] ?? '', endTime: customDateRange.value?.[1] || beforeDay(0) })
}
</script>

Expand Down
73 changes: 73 additions & 0 deletions ui/src/components/mk-echart/LineCharts.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<script setup lang="ts">
import { computed } from 'vue'
import { use, type ComposeOption } from 'echarts/core'
import { LineChart, type LineSeriesOption } from 'echarts/charts'
import {
GridComponent,
LegendComponent,
TooltipComponent,
type GridComponentOption,
type LegendComponentOption,
type TooltipComponentOption,
} from 'echarts/components'
import { numberFormat } from '@/utils/number'
import MkEchart from './index.vue'
import type { MkLineChartOption } from './types'

use([LineChart, GridComponent, LegendComponent, TooltipComponent])
defineOptions({ name: 'MkLineChart' })
const props = withDefaults(
defineProps<{
option: MkLineChartOption
width?: string
height?: string
valueFormatter?: (value: number) => string
}>(),
{ width: '100%', height: '200px', valueFormatter: numberFormat },
)
type LineOption = ComposeOption<LineSeriesOption | GridComponentOption | LegendComponentOption | TooltipComponentOption>

/* 折线图默认配置 */
const chartOption = computed<LineOption>(() => ({
color: ['#5285FF', '#FFCF2F', '#7F3BF5', '#2CA91F', '#14C0FF'],
tooltip: {
trigger: 'axis',
renderMode: 'richText',
valueFormatter: (value) => (typeof value === 'number' ? props.valueFormatter(value) : String(value ?? '—')),
},
legend: {
show: props.option.yData.length > 1,
top: 4,
right: 0,
icon: 'circle',
itemWidth: 8,
itemHeight: 8,
textStyle: { color: '#646A73' },
},
grid: { left: 12, right: 16, top: 56, bottom: 0, containLabel: true },
xAxis: {
type: 'category',
data: props.option.xData,
boundaryGap: false,
axisLine: { lineStyle: { color: '#EFF0F1' } },
axisTick: { show: false },
axisLabel: { color: '#646A73', margin: 16 },
},
yAxis: {
type: 'value',
axisLabel: { color: '#646A73', formatter: (value: number) => props.valueFormatter(value) },
splitLine: { lineStyle: { color: '#EFF0F1' } },
},
series: props.option.yData.map(({ area = true, ...series }) => ({
type: 'line',
smooth: true,
showSymbol: false,
lineStyle: { width: 2 },
...(area ? { areaStyle: { opacity: 0.05 } } : {}),
...series,
})),
}))
</script>
<template>
<MkEchart :option="chartOption" :width="width" :height="height" />
</template>
45 changes: 45 additions & 0 deletions ui/src/components/mk-echart/index.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<script setup lang="ts">
import { onBeforeUnmount, onMounted, useTemplateRef, watch } from 'vue'
import { init, use, type EChartsCoreOption, type EChartsType } from 'echarts/core'
import { CanvasRenderer } from 'echarts/renderers'

use([CanvasRenderer])
defineOptions({ name: 'MkEchart' })
const props = withDefaults(
defineProps<{
option: EChartsCoreOption
width?: string
height?: string
}>(),
{ width: '100%', height: '200px' },
)

/* 图表实例与容器尺寸 */
const container = useTemplateRef<HTMLDivElement>('container')
let chart: EChartsType | undefined
let resizeObserver: ResizeObserver | undefined
function renderChart() {
if (!container.value?.clientWidth || !container.value.clientHeight) return
if (!chart) chart = init(container.value)
chart.setOption(props.option, { notMerge: true })
}
function resize() {
if (!chart) renderChart()
else chart.resize()
}
onMounted(() => {
renderChart()
resizeObserver = new ResizeObserver(resize)
resizeObserver.observe(container.value!)
})
watch(() => props.option, renderChart, { deep: true, flush: 'post' })
onBeforeUnmount(() => {
resizeObserver?.disconnect()
chart?.dispose()
chart = undefined
})
defineExpose({ resize })
</script>
<template>
<div ref="container" :style="{ width, height }" />
</template>
10 changes: 10 additions & 0 deletions ui/src/components/mk-echart/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import type { LineSeriesOption } from 'echarts/charts'

/** 折线系列,默认开启面积填充,area: false 可关闭。 */
export type MkLineChartSeries = Omit<LineSeriesOption, 'type'> & { area?: boolean }

/** 折线图的横轴和系列配置。 */
export interface MkLineChartOption {
xData: string[]
yData: MkLineChartSeries[]
}
15 changes: 14 additions & 1 deletion ui/src/styles/STYLE_README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ import './styles/index.scss'

## 基础样式

全局焦点轮廓统一在 `app.scss` 通过 `*:focus-visible { outline: none !important; }` 移除,
Admin 和 Chat 共用。组件不单独恢复焦点 `outline`,包括通过隐藏输入框的焦点为相邻可见元素
绘制轮廓的情况;组件原有的 `border`、`box-shadow` 和焦点行为不受此规则影响。

不要使用会影响所有元素间距的宽泛重置规则,例如:

```scss
Expand Down Expand Up @@ -276,6 +280,10 @@ Tailwind 类。

### 主题色

首页资源卡片通过各资源的固定 `color` 配置设置图标、背景和悬停边框,不随主题色切换。
颜色配置使用 RGB 通道字符串,模板通过 `rgb()` 设置原色、`rgba()` 设置浅色背景。
卡片背景由上层半透明渐变和下层白色渐变组成,CSS 背景列表中前面的图层位于上方。

当前主题色为:

```scss
Expand Down Expand Up @@ -403,7 +411,7 @@ Tree 的 `.el-tree-node__content` 统一保留 4px 底部间距,使同级节
按钮式多选使用 `el-checkbox-group` 和 `el-checkbox-button`,外观与按钮式单选一致:
组容器提供圆角边框、4px 内边距和间距,选中项使用主题色文字与浅色背景,不显示相邻按钮连接阴影。
规则统一维护在 `element-plus.scss` 的 checkbox 分组;仅包含 `el-checkbox-button` 的组容器
应用该布局,普通复选框组不受影响。保留禁用状态和键盘焦点提示。
应用该布局,普通复选框组不受影响。保留禁用状态,焦点轮廓遵循全局移除规则。

Element Plus 所需的 `light-3` 至 `light-9` 和 `dark-2` 色阶使用 `color-mix()` 根据主题色生成。业务代码通常不需要直接修改 `--el-color-primary-*`。

Expand Down Expand Up @@ -455,3 +463,8 @@ npm run type-check
npm run build-only
npm run build-only-chat
```

## 排行名次

`mk-rank` 提供 20px 圆形名次徽标,前三名叠加 `mk-rank-1`、`mk-rank-2`、
`mk-rank-3`,使用固定渐变配色。样式统一维护在 `app.scss`。
Loading
Loading