tycoding 1 год назад
Родитель
Сommit
cedb6d6417

+ 5 - 1
langchat-aigc/src/main/java/cn/tycoding/langchat/aigc/controller/AigcConversationController.java

@@ -1,11 +1,13 @@
 package cn.tycoding.langchat.aigc.controller;
 
+import cn.hutool.extra.servlet.JakartaServletUtil;
 import cn.tycoding.langchat.aigc.entity.AigcConversation;
 import cn.tycoding.langchat.aigc.entity.AigcMessage;
 import cn.tycoding.langchat.aigc.service.AigcMessageService;
 import cn.tycoding.langchat.common.utils.MybatisUtil;
 import cn.tycoding.langchat.common.utils.QueryPage;
 import cn.tycoding.langchat.common.utils.R;
+import jakarta.servlet.http.HttpServletRequest;
 import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.web.bind.annotation.*;
@@ -87,7 +89,9 @@ public class AigcConversationController {
     }
 
     @PostMapping("/message")
-    public R addMessage(@RequestBody AigcMessage message) {
+    public R addMessage(@RequestBody AigcMessage message, HttpServletRequest request) {
+        message.setIp(JakartaServletUtil.getClientIP(request));
+        message.setIp(JakartaServletUtil.getClientIP(request));
         return R.ok(aigcMessageService.addMessage(message));
     }
 }

+ 52 - 0
langchat-aigc/src/main/java/cn/tycoding/langchat/aigc/controller/AigcMessageController.java

@@ -0,0 +1,52 @@
+package cn.tycoding.langchat.aigc.controller;
+
+import cn.hutool.core.util.StrUtil;
+import cn.tycoding.langchat.aigc.entity.AigcMessage;
+import cn.tycoding.langchat.aigc.mapper.AigcMessageMapper;
+import cn.tycoding.langchat.aigc.service.AigcMessageService;
+import cn.tycoding.langchat.common.utils.MybatisUtil;
+import cn.tycoding.langchat.common.utils.QueryPage;
+import cn.tycoding.langchat.common.utils.R;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import lombok.AllArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * @author tycoding
+ * @since 2024/1/19
+ */
+@RequestMapping("/aigc/message")
+@RestController
+@AllArgsConstructor
+public class AigcMessageController {
+
+    private final AigcMessageService aigcMessageService;
+    private final AigcMessageMapper aigcMessageMapper;
+
+    @GetMapping("/page")
+    public R list(AigcMessage data, QueryPage queryPage) {
+        LambdaQueryWrapper<AigcMessage> queryWrapper = Wrappers.<AigcMessage>lambdaQuery()
+                .like(!StrUtil.isBlank(data.getUsername()), AigcMessage::getUsername, data.getUsername())
+                .eq(!StrUtil.isBlank(data.getRole()), AigcMessage::getRole, data.getRole())
+                .orderByDesc(AigcMessage::getCreateTime);
+        IPage<AigcMessage> iPage = aigcMessageService.page(MybatisUtil.wrap(data, queryPage), queryWrapper);
+        return R.ok(MybatisUtil.getData(iPage));
+    }
+
+    @GetMapping("/{id}")
+    public R getById(@PathVariable String id) {
+        return R.ok(aigcMessageService.getById(id));
+    }
+
+    @DeleteMapping("/{id}")
+    public R del(@PathVariable String id) {
+        return R.ok(aigcMessageService.removeById(id));
+    }
+
+    @GetMapping("/charts")
+    public R charts() {
+        return R.ok(aigcMessageMapper.charts());
+    }
+}

+ 17 - 0
langchat-aigc/src/main/java/cn/tycoding/langchat/aigc/dto/MsgChartDto.java

@@ -0,0 +1,17 @@
+package cn.tycoding.langchat.aigc.dto;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * @author tycoding
+ * @since 2024/4/24
+ */
+@Data
+public class MsgChartDto implements Serializable {
+    private static final long serialVersionUID = 8074252311773257985L;
+
+    private String date;
+    private String tokens;
+}

+ 17 - 0
langchat-aigc/src/main/java/cn/tycoding/langchat/aigc/entity/AigcMessage.java

@@ -36,11 +36,28 @@ public class AigcMessage implements Serializable {
      */
     private String conversationId;
 
+    /**
+     * 用户ID
+     */
+    private String userId;
+    /**
+     * 用户名
+     */
+    private String username;
+
     /**
      * 应用ID
      */
     private String promptId;
 
+    /**
+     * 请求IP
+     */
+    private String ip;
+
+    private Integer tokens;
+    private Integer promptTokens;
+
     /**
      * 角色,user、assistant、system
      */

+ 18 - 0
langchat-aigc/src/main/java/cn/tycoding/langchat/aigc/mapper/AigcMessageMapper.java

@@ -1,8 +1,12 @@
 package cn.tycoding.langchat.aigc.mapper;
 
+import cn.tycoding.langchat.aigc.dto.MsgChartDto;
 import cn.tycoding.langchat.aigc.entity.AigcMessage;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Select;
+
+import java.util.List;
 
 /**
  * @author tycoding
@@ -11,5 +15,19 @@ import org.apache.ibatis.annotations.Mapper;
 @Mapper
 public interface AigcMessageMapper extends BaseMapper<AigcMessage> {
 
+    @Select("""
+        SELECT
+            DATE(create_time) as date,
+            SUM(tokens) as tokens
+        FROM
+            aigc_message
+        WHERE
+            create_time >= DATE_SUB(CURDATE(), INTERVAL 30 DAY)
+        GROUP BY
+            date
+        ORDER BY
+            date DESC;
+    """)
+    List<MsgChartDto> charts();
 }
 

+ 7 - 0
langchat-ui/src/api/aigc/message.ts

@@ -38,3 +38,10 @@ export function del(id: string) {
     method: 'delete',
   });
 }
+
+export function charts() {
+  return http.request({
+    url: `/aigc/message/charts`,
+    method: 'get',
+  });
+}

+ 4 - 4
langchat-ui/src/views/aigc/knowledge/components/index.vue

@@ -26,17 +26,17 @@
         <n-menu v-model:value="menu" :options="menuOptions" @update:value="handleSelect" />
       </n-gi>
       <n-gi class="h-full overflow-y-auto" span="21">
-        <!--        <DocList v-if="menu == 'doc-list'" />-->
-        <!--        <ImportFile v-if="menu == 'import-file'" />-->
+        <DocList v-if="menu == 'doc-list'" />
+        <ImportFile v-if="menu == 'import-file'" />
         <FileList v-if="menu == 'file-list'" />
       </n-gi>
     </n-grid>
   </div>
 </template>
 <script lang="ts" setup>
-  // import DocList from '@/views/kb/modules/DocList/Layout.vue';
+  import DocList from './DocList/index.vue';
   import FileList from './FileList/index.vue';
-  // import ImportFile from '@/views/kb/modules/ImportFile/Layout.vue';
+  import ImportFile from './ImportFile/index.vue';
   import { onMounted, ref } from 'vue';
   import type { MenuOption } from 'naive-ui';
   import { NIcon } from 'naive-ui';

+ 80 - 0
langchat-ui/src/views/aigc/statistics/components/Chart.vue

@@ -0,0 +1,80 @@
+<script lang="ts" setup>
+  import { onMounted, ref, Ref } from 'vue';
+  import { useECharts } from '@/hooks/web/useECharts';
+  import { charts } from '@/api/aigc/message';
+
+  const chartRef = ref<HTMLDivElement | null>(null);
+  const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>);
+
+  onMounted(async () => {
+    const data = await charts();
+    const xData: any = [];
+    const yData: any = [];
+    data.forEach((i: any) => {
+      xData.push(i.date);
+      yData.push(i.tokens);
+    });
+
+    setOptions({
+      tooltip: {
+        trigger: 'axis',
+        axisPointer: {
+          lineStyle: {
+            width: 1,
+            color: '#019680',
+          },
+        },
+      },
+      xAxis: {
+        type: 'category',
+        boundaryGap: false,
+        data: xData,
+        splitLine: {
+          show: true,
+          lineStyle: {
+            width: 1,
+            type: 'solid',
+            color: 'rgba(226,226,226,0.5)',
+          },
+        },
+        axisTick: {
+          show: false,
+        },
+      },
+      yAxis: [
+        {
+          type: 'value',
+          splitNumber: 4,
+          axisTick: {
+            show: false,
+          },
+          splitArea: {
+            show: true,
+            areaStyle: {
+              color: ['rgba(255,255,255,0.2)', 'rgba(226,226,226,0.2)'],
+            },
+          },
+        },
+      ],
+      grid: { left: '1%', right: '1%', top: '2  %', bottom: 0, containLabel: true },
+      series: [
+        {
+          smooth: true,
+          data: yData,
+          type: 'line',
+          areaStyle: {},
+          itemStyle: {
+            color: '#5ab1ef',
+          },
+        },
+      ],
+    });
+  });
+</script>
+
+<template>
+  <div>
+    <h3 class="my-2 mb-6 text-lg">近30天请求汇总</h3>
+    <div ref="chartRef" class="w-full h-60"></div>
+  </div>
+</template>

+ 88 - 0
langchat-ui/src/views/aigc/statistics/components/List.vue

@@ -0,0 +1,88 @@
+<script lang="ts" setup>
+  import { h, reactive, ref } from 'vue';
+  import { BasicTable, TableAction } from '@/components/Table';
+  import { BasicForm, useForm } from '@/components/Form/index';
+  import { del, page as getPage } from '@/api/aigc/message';
+  import { columns, searchSchemas } from './columns';
+  import { DeleteOutlined } from '@vicons/antd';
+  import { useDialog, useMessage } from 'naive-ui';
+
+  const message = useMessage();
+  const dialog = useDialog();
+
+  const actionRef = ref();
+
+  const actionColumn = reactive({
+    width: 100,
+    title: '操作',
+    key: 'action',
+    fixed: 'right',
+    align: 'center',
+    render(record: any) {
+      return h(TableAction as any, {
+        style: 'text',
+        actions: [
+          {
+            type: 'error',
+            icon: DeleteOutlined,
+            onClick: handleDelete.bind(null, record),
+          },
+        ],
+      });
+    },
+  });
+
+  const [register, { getFieldsValue }] = useForm({
+    gridProps: { cols: '1 s:1 m:2 l:3 xl:4 2xl:4' },
+    labelWidth: 80,
+    schemas: searchSchemas,
+    showAdvancedButton: false,
+  });
+
+  const loadDataTable = async (res: any) => {
+    return await getPage({ ...getFieldsValue(), ...res });
+  };
+
+  function reloadTable() {
+    actionRef.value.reload();
+  }
+
+  function handleDelete(record: Recordable) {
+    dialog.info({
+      title: '提示',
+      content: `您想删除 ${record.name}`,
+      positiveText: '确定',
+      negativeText: '取消',
+      onPositiveClick: async () => {
+        await del(record.id);
+        message.success('删除成功');
+        reloadTable();
+      },
+      onNegativeClick: () => {},
+    });
+  }
+
+  function handleReset(values: Recordable) {
+    reloadTable();
+  }
+</script>
+
+<template>
+  <div class="h-full">
+    <n-card :bordered="false">
+      <BasicForm @register="register" @reset="handleReset" @submit="reloadTable" />
+
+      <BasicTable
+        ref="actionRef"
+        :actionColumn="actionColumn"
+        :columns="columns"
+        :request="loadDataTable"
+        :row-key="(row:any) => row.id"
+        :single-line="false"
+        :size="'small'"
+      />
+    </n-card>
+  </div>
+</template>
+
+<style lang="less" scoped></style>

+ 52 - 0
langchat-ui/src/views/aigc/statistics/components/columns.ts

@@ -0,0 +1,52 @@
+import { BasicColumn } from '@/components/Table';
+import { FormSchema } from '@/components/Form';
+
+export const columns: BasicColumn[] = [
+  {
+    title: '用户名',
+    key: 'username',
+    align: 'center',
+  },
+  {
+    title: '模型名称',
+    key: 'model',
+    align: 'center',
+  },
+  {
+    title: 'Tokens',
+    key: 'tokens',
+    align: 'center',
+  },
+  {
+    title: 'Prompt Tokens',
+    key: 'promptTokens',
+    align: 'center',
+  },
+  {
+    title: 'Prompt Tokens',
+    key: 'promptTokens',
+    align: 'center',
+  },
+  {
+    title: 'IP地址',
+    key: 'ip',
+    align: 'center',
+  },
+  {
+    title: '调用时间',
+    key: 'createTime',
+    align: 'center',
+    width: 180,
+  },
+];
+
+export const searchSchemas: FormSchema[] = [
+  {
+    field: 'name',
+    component: 'NInput',
+    label: '用户名',
+    componentProps: {
+      placeholder: '请输入用户名查询',
+    },
+  },
+];

+ 4 - 99
langchat-ui/src/views/aigc/statistics/index.vue

@@ -1,106 +1,11 @@
 <script lang="ts" setup>
-  import { onMounted, ref, Ref } from 'vue';
-  import { useECharts } from '@/hooks/web/useECharts';
-
-  const chartRef = ref<HTMLDivElement | null>(null);
-  const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>);
-
-  onMounted(() => {
-    setOptions({
-      tooltip: {
-        trigger: 'axis',
-        axisPointer: {
-          lineStyle: {
-            width: 1,
-            color: '#019680',
-          },
-        },
-      },
-      xAxis: {
-        type: 'category',
-        boundaryGap: false,
-        data: [
-          '6:00',
-          '7:00',
-          '8:00',
-          '9:00',
-          '10:00',
-          '11:00',
-          '12:00',
-          '13:00',
-          '14:00',
-          '15:00',
-          '16:00',
-          '17:00',
-          '18:00',
-          '19:00',
-          '20:00',
-          '21:00',
-          '22:00',
-          '23:00',
-        ],
-        splitLine: {
-          show: true,
-          lineStyle: {
-            width: 1,
-            type: 'solid',
-            color: 'rgba(226,226,226,0.5)',
-          },
-        },
-        axisTick: {
-          show: false,
-        },
-      },
-      yAxis: [
-        {
-          type: 'value',
-          max: 80000,
-          splitNumber: 4,
-          axisTick: {
-            show: false,
-          },
-          splitArea: {
-            show: true,
-            areaStyle: {
-              color: ['rgba(255,255,255,0.2)', 'rgba(226,226,226,0.2)'],
-            },
-          },
-        },
-      ],
-      grid: { left: '1%', right: '1%', top: '2  %', bottom: 0, containLabel: true },
-      series: [
-        {
-          smooth: true,
-          data: [
-            111, 222, 4000, 18000, 33333, 55555, 66666, 33333, 14000, 36000, 66666, 44444, 22222,
-            11111, 4000, 2000, 500, 333, 222, 111,
-          ],
-          type: 'line',
-          areaStyle: {},
-          itemStyle: {
-            color: '#5ab1ef',
-          },
-        },
-        {
-          smooth: true,
-          data: [
-            33, 66, 88, 333, 3333, 5000, 18000, 3000, 1200, 13000, 22000, 11000, 2221, 1201, 390,
-            198, 60, 30, 22, 11,
-          ],
-          type: 'line',
-          areaStyle: {},
-          itemStyle: {
-            color: '#019680',
-          },
-        },
-      ],
-    });
-  });
+  import Chart from './components/Chart.vue';
+  import List from './components/List.vue';
 </script>
 
 <template>
   <n-card>
-    <h3 class="my-2 mb-6 text-lg">请求汇总</h3>
-    <div ref="chartRef" class="w-full h-80"></div>
+    <Chart />
+    <List />
   </n-card>
 </template>