tycoding 1 anno fa
parent
commit
d423eaca0b

+ 67 - 0
langchat-aigc/src/main/java/cn/tycoding/langchat/aigc/controller/AigcModelController.java

@@ -0,0 +1,67 @@
+package cn.tycoding.langchat.aigc.controller;
+
+import cn.tycoding.langchat.aigc.entity.AigcModel;
+import cn.tycoding.langchat.aigc.mapper.AigcModelMapper;
+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.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import java.util.List;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * @author tycoding
+ * @since 2024/4/15
+ */
+@RestController
+@RequiredArgsConstructor
+@RequestMapping("/aigc/model")
+public class AigcModelController {
+
+    private final AigcModelMapper docsMapper;
+
+    @GetMapping("/list")
+    public R<List<AigcModel>> list(AigcModel data) {
+        return R.ok(docsMapper.selectList(Wrappers.<AigcModel>lambdaQuery()));
+    }
+
+    @GetMapping("/page")
+    public R list(AigcModel data, QueryPage queryPage) {
+        Page<AigcModel> page = new Page<>(queryPage.getPage(), queryPage.getLimit());
+        return R.ok(MybatisUtil.getData(docsMapper.selectPage(page, Wrappers.<AigcModel>lambdaQuery()
+        )));
+    }
+
+    @GetMapping("/{id}")
+    public R<AigcModel> findById(@PathVariable String id) {
+        return R.ok(docsMapper.selectById(id));
+    }
+
+    @PostMapping
+    public R add(@RequestBody AigcModel data) {
+        docsMapper.insert(data);
+        return R.ok();
+    }
+
+    @PutMapping
+    public R update(@RequestBody AigcModel data) {
+        docsMapper.updateById(data);
+        return R.ok();
+    }
+
+    @DeleteMapping("/{id}")
+    public R delete(@PathVariable String id) {
+        docsMapper.deleteById(id);
+        return R.ok();
+    }
+}
+

+ 33 - 0
langchat-aigc/src/main/java/cn/tycoding/langchat/aigc/entity/AigcModel.java

@@ -0,0 +1,33 @@
+package cn.tycoding.langchat.aigc.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import java.io.Serializable;
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+/**
+ * @author tycoding
+ * @since 2024/1/4
+ */
+@Data
+@Accessors(chain = true)
+public class AigcModel implements Serializable {
+
+    private static final long serialVersionUID = -19545329638997333L;
+
+    /**
+     * 主键
+     */
+    @TableId(type = IdType.ASSIGN_UUID)
+    private String id;
+
+    private String model;
+    private String provider;
+    private String name;
+    private Integer responseLimit;
+    private Double temperature;
+    private Double topP;
+    private String apiKey;
+}
+

+ 15 - 0
langchat-aigc/src/main/java/cn/tycoding/langchat/aigc/mapper/AigcModelMapper.java

@@ -0,0 +1,15 @@
+package cn.tycoding.langchat.aigc.mapper;
+
+import cn.tycoding.langchat.aigc.entity.AigcModel;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * @author tycoding
+ * @since 2024/4/15
+ */
+@Mapper
+public interface AigcModelMapper extends BaseMapper<AigcModel> {
+
+}
+

+ 13 - 0
langchat-aigc/src/main/java/cn/tycoding/langchat/aigc/service/AigcModelService.java

@@ -0,0 +1,13 @@
+package cn.tycoding.langchat.aigc.service;
+
+import cn.tycoding.langchat.aigc.entity.AigcModel;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+/**
+ * @author tycoding
+ * @since 2024/1/19
+ */
+public interface AigcModelService extends IService<AigcModel> {
+
+}
+

+ 19 - 0
langchat-aigc/src/main/java/cn/tycoding/langchat/aigc/service/impl/AigcModelServiceImpl.java

@@ -0,0 +1,19 @@
+package cn.tycoding.langchat.aigc.service.impl;
+
+import cn.tycoding.langchat.aigc.entity.AigcModel;
+import cn.tycoding.langchat.aigc.mapper.AigcModelMapper;
+import cn.tycoding.langchat.aigc.service.AigcModelService;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+
+/**
+ * @author tycoding
+ * @since 2024/1/19
+ */
+@Service
+@RequiredArgsConstructor
+public class AigcModelServiceImpl extends ServiceImpl<AigcModelMapper, AigcModel> implements AigcModelService {
+
+}
+

+ 47 - 0
langchat-ui/src/api/aigc/model.ts

@@ -0,0 +1,47 @@
+import { http } from '@/utils/http/axios';
+
+export function page(params: any) {
+  return http.request({
+    url: '/aigc/model/page',
+    method: 'get',
+    params,
+  });
+}
+
+export function list(params: any) {
+  return http.request({
+    url: '/aigc/model/list',
+    method: 'get',
+    params,
+  });
+}
+
+export function getById(id: any) {
+  return http.request({
+    url: `/aigc/model/${id}`,
+    method: 'get',
+  });
+}
+
+export function add(params: any) {
+  return http.request({
+    url: '/aigc/model',
+    method: 'post',
+    params,
+  });
+}
+
+export function update(params: any) {
+  return http.request({
+    url: '/aigc/model',
+    method: 'put',
+    params,
+  });
+}
+
+export function del(id: string) {
+  return http.request({
+    url: `/aigc/model/${id}`,
+    method: 'delete',
+  });
+}

+ 5 - 2
langchat-ui/src/components/Form/src/hooks/useForm.ts

@@ -1,7 +1,7 @@
-import type { FormProps, FormActionType, UseFormReturnType } from '../types/form';
+import type { FormActionType, FormProps, UseFormReturnType } from '../types/form';
 import type { DynamicProps } from '/#/utils';
 
-import { ref, onUnmounted, unref, nextTick, watch } from 'vue';
+import { nextTick, onUnmounted, ref, unref, watch } from 'vue';
 import { isProdMode } from '@/utils/env';
 import { getDynamicProps } from '@/utils';
 
@@ -68,6 +68,9 @@ export function useForm(props?: Props): UseFormReturnType {
 
     setFieldsValue: async <T>(values: T) => {
       const form = await getForm();
+      if (form == null) {
+        return;
+      }
       // @ts-ignore
       form.setFieldsValue<T>(values);
     },

+ 52 - 50
langchat-ui/src/plugins/naive.ts

@@ -1,72 +1,73 @@
 import type { App } from 'vue';
 import {
   create,
-  NMessageProvider,
-  NDialogProvider,
-  NConfigProvider,
-  NInput,
-  NButton,
-  NForm,
-  NFormItem,
-  NCheckboxGroup,
-  NCheckbox,
-  NIcon,
-  NLayout,
-  NLayoutHeader,
-  NLayoutContent,
-  NLayoutFooter,
-  NLayoutSider,
-  NMenu,
+  NAlert,
+  NAvatar,
+  NBackTop,
+  NBadge,
   NBreadcrumb,
   NBreadcrumbItem,
-  NDropdown,
-  NSpace,
-  NTooltip,
-  NAvatar,
-  NTabs,
-  NTabPane,
+  NButton,
   NCard,
-  NRow,
+  NCascader,
+  NCheckbox,
+  NCheckboxGroup,
   NCol,
+  NConfigProvider,
+  NDataTable,
+  NDatePicker,
+  NDescriptions,
+  NDescriptionsItem,
+  NDialogProvider,
+  NDivider,
   NDrawer,
   NDrawerContent,
-  NDivider,
-  NSwitch,
-  NBadge,
-  NAlert,
+  NDropdown,
   NElement,
-  NTag,
-  NNotificationProvider,
-  NProgress,
-  NDatePicker,
+  NForm,
+  NFormItem,
   NGrid,
   NGridItem,
+  NIcon,
+  NInput,
+  NInputGroup,
+  NInputNumber,
+  NLayout,
+  NLayoutContent,
+  NLayoutFooter,
+  NLayoutHeader,
+  NLayoutSider,
   NList,
   NListItem,
-  NThing,
-  NDataTable,
-  NPopover,
+  NLoadingBarProvider,
+  NMenu,
+  NMessageProvider,
+  NModal,
+  NNotificationProvider,
   NPagination,
-  NSelect,
-  NRadioGroup,
+  NPopover,
+  NProgress,
   NRadio,
-  NSteps,
-  NStep,
-  NInputGroup,
+  NRadioGroup,
   NResult,
-  NDescriptions,
-  NDescriptionsItem,
-  NTable,
-  NInputNumber,
-  NLoadingBarProvider,
-  NModal,
-  NUpload,
-  NTree,
+  NRow,
+  NSelect,
+  NSkeleton,
+  NSlider,
+  NSpace,
   NSpin,
+  NStep,
+  NSteps,
+  NSwitch,
+  NTable,
+  NTabPane,
+  NTabs,
+  NTag,
+  NThing,
   NTimePicker,
-  NBackTop,
-  NSkeleton,
-  NCascader,
+  NTooltip,
+  NTree,
+  NUpload,
 } from 'naive-ui';
 
 // https://www.naiveui.com/en-US/os-theme/docs/import-on-demand
@@ -138,6 +139,7 @@ const naive = create({
     NBackTop,
     NSkeleton,
     NCascader,
+    NSlider,
   ],
 });
 

+ 8 - 0
langchat-ui/src/styles/index.less

@@ -104,3 +104,11 @@ body {
     border: none !important;
   }
 }
+
+.n-slider-handle-indicator.n-slider-handle-indicator--top {
+  margin-bottom: 6px !important;
+}
+
+.n-slider-handle-indicator {
+  padding: 0 5px !important;
+}

+ 143 - 0
langchat-ui/src/views/aigc/model/data.ts

@@ -0,0 +1,143 @@
+import { FormSchema } from '@/components/Form';
+
+export const LLMProviders: any[] = [
+  {
+    model: 'openai',
+    name: 'OpenAI',
+  },
+  {
+    model: 'google',
+    name: 'Google',
+  },
+  {
+    model: 'ollama',
+    name: 'Ollama',
+  },
+  {
+    model: 'baidu',
+    name: '百度大模型',
+  },
+  {
+    model: 'alibaba',
+    name: '阿里大模型',
+  },
+];
+
+export const columns = [
+  {
+    title: '供应商',
+    key: 'name',
+  },
+  {
+    title: '模型名称',
+    key: 'model',
+  },
+  {
+    title: '回复上限',
+    key: 'responseLimit',
+    width: '120',
+  },
+  {
+    title: '生成随机性',
+    key: 'temperature',
+    width: '120',
+  },
+  {
+    title: 'Top P',
+    key: 'topP',
+    width: '120',
+  },
+  {
+    title: 'Api Key',
+    key: 'apiKey',
+  },
+];
+
+export const formSchemas: FormSchema[] = [
+  {
+    field: 'id',
+    label: 'ID',
+    component: 'NInput',
+    isHidden: true,
+  },
+  {
+    field: 'provider',
+    label: 'LLM供应商',
+    component: 'NSelect',
+    componentProps: {
+      options: LLMProviders,
+      labelField: 'name',
+      valueField: 'model',
+    },
+    rules: [{ required: true, message: '请选择LLM供应商', trigger: ['blur'] }],
+  },
+  {
+    field: 'model',
+    label: '模型名称',
+    labelMessage: '该LLM供应商对应的模型版本号',
+    component: 'NInput',
+    rules: [{ required: true, message: '请输入模型别名', trigger: ['blur'] }],
+  },
+  {
+    field: 'name',
+    label: '模型别名',
+    component: 'NInput',
+    rules: [{ required: true, message: '请输入模型别名', trigger: ['blur'] }],
+  },
+  {
+    field: 'apiKey',
+    label: 'Api Key',
+    labelMessage: '模型链接的秘钥,注意有些模型例如Gemini是本地认证方式,则不是通过这种方式',
+    component: 'NInput',
+    // rules: [{ required: true, message: '请输入模型ApiKey', trigger: ['blur'] }],
+  },
+  {
+    field: 'baseUrl',
+    label: 'Base Url',
+    labelMessage: '注意对于大多数模型此参数仅代表中转地址,但是对于Ollama这类本地模型则是必填的',
+    component: 'NInput',
+  },
+  {
+    field: 'responseLimit',
+    label: '回复上限',
+    labelMessage: '控制模型输出的Tokens长度上限。通常 100 Tokens 约等于150个中文汉字',
+    component: 'NSlider',
+    rules: [{ type: 'number', required: true, message: '请输入回复上限', trigger: ['blur'] }],
+    componentProps: {
+      showTooltip: true,
+      defaultValue: 2000,
+      step: 1,
+      min: 1,
+      max: 8192,
+    },
+  },
+  {
+    field: 'temperature',
+    label: '生成随机性',
+    labelMessage: '调高参数会使得模型的输出更多样性和创新性,反之降低参数将会减少多样性',
+    component: 'NSlider',
+    rules: [{ type: 'number', required: true, message: '请输入生成随机性', trigger: ['blur'] }],
+    componentProps: {
+      showTooltip: true,
+      defaultValue: 0.8,
+      step: 0.05,
+      min: 0,
+      max: 2,
+    },
+  },
+  {
+    field: 'topP',
+    label: 'Top P',
+    labelMessage:
+      '模型在生成输出时会从概率最高的词汇开始选择,直到这些词汇的总概率累积达到Top p值。这样可以限制模型只选择这些高概率的词汇,从而控制输出内容的多样性。建议不要与“生成随机性“同时调整',
+    component: 'NSlider',
+    rules: [{ type: 'number', required: true, message: '请输入', trigger: ['blur'] }],
+    componentProps: {
+      showTooltip: true,
+      defaultValue: 1,
+      step: 0.1,
+      min: 0,
+      max: 1,
+    },
+  },
+];

+ 66 - 0
langchat-ui/src/views/aigc/model/edit.vue

@@ -0,0 +1,66 @@
+<template>
+  <n-drawer v-model:show="isShow" placement="right" width="40%">
+    <n-drawer-content :title="title" closable>
+      <BasicForm class="mt-5" @register="register" @submit="onSubmit" />
+    </n-drawer-content>
+  </n-drawer>
+</template>
+<script lang="ts" setup>
+  import { computed, nextTick, ref } from 'vue';
+  import { BasicForm, useForm } from '@/components/Form';
+  import { formSchemas } from './data';
+  import { isNullOrWhitespace } from '@/utils/is';
+  import { add, update } from '@/api/aigc/model';
+  import { useMessage } from 'naive-ui';
+
+  const emit = defineEmits(['reload']);
+  const isShow = ref(false);
+  const info = ref();
+  const message = useMessage();
+  const title = computed(() => {
+    return info.value == undefined || info.value.provider == undefined
+      ? 'Add Model'
+      : info.value.provider;
+  });
+  const form = {
+    responseLimit: 2000,
+    temperature: 0.8,
+    topP: 1,
+  };
+
+  async function show(record?: any) {
+    isShow.value = true;
+    await nextTick();
+    info.value = record;
+    setFieldsValue({ ...form, ...record });
+  }
+
+  const [register, { setFieldsValue }] = useForm({
+    labelWidth: 120,
+    gridProps: { cols: 1 },
+    layout: 'horizontal',
+    submitButtonText: '提交',
+    schemas: formSchemas,
+  });
+
+  async function onSubmit(values: any) {
+    if (values !== false) {
+      isShow.value = false;
+      if (isNullOrWhitespace(values.id)) {
+        await add(values);
+        emit('reload');
+        message.success('新增成功');
+      } else {
+        await update(values);
+        emit('reload');
+        message.success('修改成功');
+      }
+    } else {
+      message.error('请完善表单');
+    }
+  }
+
+  defineExpose({ show });
+</script>
+
+<style lang="less" scoped></style>

+ 115 - 0
langchat-ui/src/views/aigc/model/index.vue

@@ -0,0 +1,115 @@
+<script lang="ts" setup>
+  import { h, reactive, ref, toRaw } from 'vue';
+  import { BasicTable, TableAction } from '@/components/Table';
+  import { DeleteOutlined, EditOutlined, PlusOutlined } from '@vicons/antd';
+  import { columns, LLMProviders } from './data';
+  import Edit from './edit.vue';
+  import { list } from '@/api/aigc/model';
+
+  const actionRef = ref();
+  const editRef = ref();
+  const expands = ref([]);
+  const actionColumn = reactive({
+    width: 100,
+    title: '操作',
+    key: 'action',
+    fixed: 'right',
+    align: 'center',
+    render(record: any) {
+      const providers = LLMProviders.map((i) => i.model);
+      if (providers.includes(toRaw(record).model)) {
+        return h(TableAction as any, {
+          style: 'text',
+          actions: [
+            {
+              type: 'success',
+              icon: PlusOutlined,
+              onClick: handleAdd.bind(null, { provider: record.model }),
+            },
+          ],
+        });
+      }
+      return h(TableAction as any, {
+        style: 'text',
+        actions: [
+          {
+            type: 'info',
+            icon: EditOutlined,
+            onClick: handleEdit.bind(null, record),
+          },
+          {
+            type: 'error',
+            icon: DeleteOutlined,
+            onClick: handleDel.bind(null, record),
+          },
+        ],
+      });
+    },
+  });
+
+  const loadDataTable = async (params: any) => {
+    const models = await list({ ...params });
+    const data: any[] = [];
+    LLMProviders.forEach((i) => {
+      const children = models.filter((m) => m.provider == i.model);
+      console.log(children);
+      data.push({
+        model: i.model,
+        name: i.name,
+        type: 'expand',
+        expandable: true,
+        children: children,
+      });
+    });
+    return data;
+  };
+  function handleAdd(record: any) {
+    editRef.value.show(record);
+  }
+
+  function handleEdit(record: any) {
+    editRef.value.show(record);
+  }
+
+  function reloadTable() {
+    actionRef.value.reload();
+  }
+
+  function handleDel(record: any) {}
+</script>
+
+<template>
+  <div>
+    <div class="n-layout-page-header">
+      <n-card :bordered="false" title="模型配置"> 支持对常见的模型配置。 </n-card>
+    </div>
+
+    <n-card :bordered="false" class="mt-4">
+      <BasicTable
+        ref="actionRef"
+        :actionColumn="actionColumn"
+        :columns="columns"
+        :pagination="false"
+        :request="loadDataTable"
+        :row-key="(row:any) => row.model"
+        :single-line="false"
+        default-expand-all
+      >
+        <template #tableTitle>
+          <n-button type="primary" @click="handleAdd">
+            <template #icon>
+              <n-icon>
+                <PlusOutlined />
+              </n-icon>
+            </template>
+            新增模型
+          </n-button>
+        </template>
+      </BasicTable>
+    </n-card>
+
+    <Edit ref="editRef" @reload="reloadTable" />
+  </div>
+</template>
+
+<style lang="less" scoped></style>

+ 1 - 2
langchat-ui/src/views/system/log/columns.ts

@@ -1,9 +1,8 @@
 import { BasicColumn } from '@/components/Table';
-import { Log } from '@/api/models/auth';
 import { h } from 'vue';
 import { NTag } from 'naive-ui';
 
-export const columns: BasicColumn<Log>[] = [
+export const columns: BasicColumn<any>[] = [
   {
     title: '操作用户',
     key: 'username',

+ 19 - 18
langchat-ui/src/views/system/log/index.vue

@@ -1,34 +1,35 @@
 <template>
-  <div class="n-layout-page-header">
-    <n-card :bordered="false" title="日志数据管理"> 所有用户的操作日志信息。 </n-card>
-  </div>
+  <div>
+    <div class="n-layout-page-header">
+      <n-card :bordered="false" title="日志数据管理"> 所有用户的操作日志信息。 </n-card>
+    </div>
 
-  <n-card :bordered="false" class="mt-4">
-    <BasicTable
-      :single-line="false"
-      :size="'small'"
-      :columns="columns"
-      :request="loadDataTable"
-      :row-key="(row:any) => row.id"
-      ref="actionRef"
-      :actionColumn="actionColumn"
-    />
-  </n-card>
+    <n-card :bordered="false" class="mt-4">
+      <BasicTable
+        ref="actionRef"
+        :actionColumn="actionColumn"
+        :columns="columns"
+        :request="loadDataTable"
+        :row-key="(row:any) => row.id"
+        :single-line="false"
+        :size="'small'"
+      />
+    </n-card>
+  </div>
 </template>
 
 <script lang="ts" setup>
   import { h, reactive, ref } from 'vue';
   import { BasicTable, TableAction } from '@/components/Table';
-  import { page as getPage, del } from '@/api/upms/log';
+  import { del, page as getPage } from '@/api/upms/log';
   import { columns } from './columns';
   import { DeleteOutlined } from '@vicons/antd';
   import { useDialog, useMessage } from 'naive-ui';
-  import { Log } from '@/api/models/auth';
+
   const message = useMessage();
   const dialog = useDialog();
 
   const actionRef = ref();
-  const editRef = ref();
 
   const actionColumn = reactive({
     width: 100,
@@ -60,7 +61,7 @@
     actionRef.value.reload();
   }
 
-  function handleDelete(record: Log) {
+  function handleDelete(record: any) {
     dialog.info({
       title: '提示',
       content: `您确定删除 ${record.operation} 日志?`,

+ 17 - 17
langchat-ui/src/views/system/token/index.vue

@@ -1,21 +1,23 @@
 <template>
-  <div class="n-layout-page-header">
-    <n-card :bordered="false" title="令牌信息管理"> 系统中所有已经生成的令牌数据。 </n-card>
-  </div>
+  <div>
+    <div class="n-layout-page-header">
+      <n-card :bordered="false" title="令牌信息管理"> 系统中所有已经生成的令牌数据。 </n-card>
+    </div>
 
-  <n-card :bordered="false" class="mt-4">
-    <BasicTable
-      ref="actionRef"
-      :actionColumn="actionColumn"
-      :columns="columns"
-      :request="loadDataTable"
-      :row-key="(row:any) => row.id"
-      :single-line="false"
-      :size="'small'"
-    />
-  </n-card>
+    <n-card :bordered="false" class="mt-4">
+      <BasicTable
+        ref="actionRef"
+        :actionColumn="actionColumn"
+        :columns="columns"
+        :request="loadDataTable"
+        :row-key="(row:any) => row.id"
+        :single-line="false"
+        :size="'small'"
+      />
+    </n-card>
 
-  <Edit ref="editRef" @reload="reloadTable" />
+    <Edit ref="editRef" @reload="reloadTable" />
+  </div>
 </template>
 
 <script lang="ts" setup>
@@ -46,14 +48,12 @@
           {
             type: 'info',
             label: '详情',
-            size: 'tiny',
             icon: EyeOutlined,
             onClick: handleEdit.bind(null, record),
           },
           {
             type: 'error',
             label: '下线',
-            size: 'tiny',
             icon: DeleteOutlined,
             onClick: handleDelete.bind(null, record),
           },