|
@@ -0,0 +1,382 @@
|
|
|
+<template>
|
|
|
+ <div>
|
|
|
+ <el-popover
|
|
|
+ :visible="state.show"
|
|
|
+ placement="bottom"
|
|
|
+ trigger="click"
|
|
|
+ :popper-style="{
|
|
|
+ padding: 0,
|
|
|
+ minWidth: 0,
|
|
|
+ width: '360px',
|
|
|
+ }"
|
|
|
+ >
|
|
|
+ <template #reference>
|
|
|
+ <div
|
|
|
+ @click="() => (state.show = !state.show)"
|
|
|
+ class="bg __hover-bg flex h-8 w-full items-center justify-end rounded-sm px-2 text-xs"
|
|
|
+ >
|
|
|
+ <img
|
|
|
+ v-if="modelConfig?.modelId"
|
|
|
+ src="@/assets/images/model/model-default-logo.png"
|
|
|
+ class="mr-2 size-4"
|
|
|
+ />
|
|
|
+ <span v-title class="mr-auto text-xs opacity-65">
|
|
|
+ {{ modelConfig?.modelId ? modelConfig?.modelName : '请选择模型' }}
|
|
|
+ </span>
|
|
|
+ <el-popover
|
|
|
+ :visible="state.showConfig"
|
|
|
+ placement="bottom"
|
|
|
+ trigger="click"
|
|
|
+ :popper-style="{
|
|
|
+ padding: 0,
|
|
|
+ minWidth: 0,
|
|
|
+ width: '360px',
|
|
|
+ }"
|
|
|
+ >
|
|
|
+ <template #reference>
|
|
|
+ <SvgIcon
|
|
|
+ name="config"
|
|
|
+ size="16"
|
|
|
+ rotate="90"
|
|
|
+ color="#33333377"
|
|
|
+ class="__hover mr-2"
|
|
|
+ @click.stop="() => (state.showConfig = !state.showConfig)"
|
|
|
+ />
|
|
|
+ </template>
|
|
|
+ <div class="px-2 py-3" :model-select-config-popover="true">
|
|
|
+ <div class="text-sm font-bold">参数</div>
|
|
|
+ <template v-for="item in state.configOptions">
|
|
|
+ <div class="mt-1 flex items-center">
|
|
|
+ <a-switch
|
|
|
+ v-model:checked="state.modelConfig.paramsConfig[item.isKey]"
|
|
|
+ size="small"
|
|
|
+ :checkedValue="1"
|
|
|
+ :unCheckedValue="0"
|
|
|
+ ></a-switch>
|
|
|
+ <span class="ml-1 flex w-20 items-center text-sm">
|
|
|
+ {{ item.label }}
|
|
|
+ <el-tooltip :content="item.tips" placement="top">
|
|
|
+ <SvgIcon name="czr_tip" size="14" class="ml-1" />
|
|
|
+ </el-tooltip>
|
|
|
+ </span>
|
|
|
+ <a-row class="ml-auto flex-1" justify="end">
|
|
|
+ <a-col :span="12" class="mr-2">
|
|
|
+ <a-slider
|
|
|
+ v-model:value="
|
|
|
+ state.modelConfig.paramsConfig[item.valueKey]
|
|
|
+ "
|
|
|
+ :min="item.min"
|
|
|
+ :max="item.max"
|
|
|
+ :step="item.step"
|
|
|
+ :disabled="!state.modelConfig.paramsConfig[item.isKey]"
|
|
|
+ />
|
|
|
+ </a-col>
|
|
|
+ <a-col :span="8">
|
|
|
+ <a-input-number
|
|
|
+ v-model:value="
|
|
|
+ state.modelConfig.paramsConfig[item.valueKey]
|
|
|
+ "
|
|
|
+ :min="item.min"
|
|
|
+ :max="item.max"
|
|
|
+ :step="item.step"
|
|
|
+ :disabled="!state.modelConfig.paramsConfig[item.isKey]"
|
|
|
+ style="width: 100%"
|
|
|
+ />
|
|
|
+ </a-col>
|
|
|
+ </a-row>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <div class="flex justify-between">
|
|
|
+ <div class="text-xs">
|
|
|
+ <div class="flex items-center">
|
|
|
+ 停止序列
|
|
|
+ <el-tooltip
|
|
|
+ content="最多四个序列,API 将停止生成更多的token。返回的文本将不包含停止序列。"
|
|
|
+ placement="top"
|
|
|
+ >
|
|
|
+ <SvgIcon name="czr_tip" size="14" class="ml-1" />
|
|
|
+ </el-tooltip>
|
|
|
+ </div>
|
|
|
+ <div>输入序列并按Enter键</div>
|
|
|
+ </div>
|
|
|
+ <div class="bg ml-8 w-full flex-1 rounded-sm p-2 text-xs">
|
|
|
+ <div class="flex flex-wrap gap-1">
|
|
|
+ <template
|
|
|
+ v-for="(item, index) in state.modelConfig.paramsConfig
|
|
|
+ .stopSequence"
|
|
|
+ >
|
|
|
+ <div
|
|
|
+ class="flex items-center gap-1 rounded-sm border border-gray-300 p-1"
|
|
|
+ >
|
|
|
+ <div class="flex-1 text-justify">
|
|
|
+ {{ item }}
|
|
|
+ </div>
|
|
|
+ <SvgIcon
|
|
|
+ class="__hover"
|
|
|
+ name="czr_close_1"
|
|
|
+ size="10"
|
|
|
+ @click="
|
|
|
+ state.modelConfig.paramsConfig.stopSequence.splice(
|
|
|
+ index,
|
|
|
+ 1,
|
|
|
+ )
|
|
|
+ "
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </div>
|
|
|
+ <div class="mt-1">
|
|
|
+ <a-input
|
|
|
+ v-model:value="state.stopText"
|
|
|
+ size="small"
|
|
|
+ :maxlength="20"
|
|
|
+ placeholder="输入序列并按 Enter 键"
|
|
|
+ @pressEnter="
|
|
|
+ (e) =>
|
|
|
+ state.stopText.trim()
|
|
|
+ ? (state.modelConfig.paramsConfig.stopSequence.push(
|
|
|
+ state.stopText,
|
|
|
+ ),
|
|
|
+ (state.stopText = ''))
|
|
|
+ : undefined
|
|
|
+ "
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-popover>
|
|
|
+ <SvgIcon name="czr_arrow" size="12" rotate="90" color="#33333377" />
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <div class="" :model-select-popover="true">
|
|
|
+ <div class="filter">
|
|
|
+ <el-input
|
|
|
+ v-model="state.text"
|
|
|
+ :prefix-icon="Search"
|
|
|
+ placeholder="搜索变量"
|
|
|
+ clearable
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <div class="flex flex-col p-1">
|
|
|
+ <template v-for="item in optionsCpt">
|
|
|
+ <div
|
|
|
+ class="__hover-bg flex items-center px-2 py-2"
|
|
|
+ @click="onModel(item)"
|
|
|
+ >
|
|
|
+ <img
|
|
|
+ src="@/assets/images/model/model-default-logo.png"
|
|
|
+ class="mr-2 size-4"
|
|
|
+ />
|
|
|
+ {{ item.name }}
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-popover>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+import { computed, onBeforeMount, onMounted, reactive, watch } from 'vue'
|
|
|
+import { domRootHasAttr } from '@/utils/czr-util'
|
|
|
+import { pluginGetInstanceList } from '@/api/modules/model'
|
|
|
+import { Search } from '@element-plus/icons-vue'
|
|
|
+import SvgIcon from '@/components/SvgIcon/index.vue'
|
|
|
+
|
|
|
+const emit = defineEmits(['update:modelConfig'])
|
|
|
+const props = defineProps({
|
|
|
+ type: { required: true },
|
|
|
+ modelConfig: {},
|
|
|
+})
|
|
|
+const state: any = reactive({
|
|
|
+ modelConfig: props.modelConfig,
|
|
|
+ show: false,
|
|
|
+ showConfig: false,
|
|
|
+ options: [],
|
|
|
+ text: '',
|
|
|
+ stopText: '',
|
|
|
+ configOptions: [
|
|
|
+ {
|
|
|
+ label: '温度',
|
|
|
+ tips: '核采样阈值。用于决定结果随机性,取值越高随机性越强即相同的问题得到的不同答案的可能性越高。',
|
|
|
+ isKey: 'isTemperature',
|
|
|
+ is: 1,
|
|
|
+ valueKey: 'temperature',
|
|
|
+ value: 0.7,
|
|
|
+ max: 2,
|
|
|
+ min: 0,
|
|
|
+ step: 0.1,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: 'Top P',
|
|
|
+ tips: '生成过程中核采样方法概率阈值。取值越大,生成的随机性越高;取值越小,生成的确定性越高。',
|
|
|
+ isKey: 'isTopP',
|
|
|
+ valueKey: 'topP',
|
|
|
+ value: 1,
|
|
|
+ max: 1,
|
|
|
+ min: 0,
|
|
|
+ step: 0.1,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '频率惩罚',
|
|
|
+ tips: '用于控制模型已使用字词的重复率。 提高此项可以降低模型在输出中重复相同字词的重复度。',
|
|
|
+ isKey: 'isFrequency',
|
|
|
+ valueKey: 'frequency',
|
|
|
+ value: 0,
|
|
|
+ max: 2,
|
|
|
+ min: -2,
|
|
|
+ step: 0.1,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '存在惩罚',
|
|
|
+ tips: '用于控制模型生成时的重复度。提高此项可以降低模型生成的重复度。',
|
|
|
+ isKey: 'isExist',
|
|
|
+ valueKey: 'exist',
|
|
|
+ value: 0,
|
|
|
+ max: 2,
|
|
|
+ min: -2,
|
|
|
+ step: 0.1,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '最大标记',
|
|
|
+ tips: '模型回答的tokens的最大长度。',
|
|
|
+ isKey: 'isTokens',
|
|
|
+ valueKey: 'tokens',
|
|
|
+ value: 512,
|
|
|
+ max: 4096,
|
|
|
+ min: 1,
|
|
|
+ step: 1,
|
|
|
+ },
|
|
|
+ ],
|
|
|
+})
|
|
|
+watch(
|
|
|
+ () => props.modelConfig,
|
|
|
+ (n) => {
|
|
|
+ reset()
|
|
|
+ },
|
|
|
+)
|
|
|
+const reset = () => {
|
|
|
+ if (props.modelConfig) {
|
|
|
+ state.modelConfig = props.modelConfig
|
|
|
+ } else {
|
|
|
+ state.modelConfig = {
|
|
|
+ modelId: '',
|
|
|
+ modelName: '',
|
|
|
+ paramsConfig: {
|
|
|
+ stopSequence: [],
|
|
|
+ },
|
|
|
+ }
|
|
|
+ state.configOptions.forEach((v) => {
|
|
|
+ state.modelConfig.paramsConfig[v.isKey] = v.is
|
|
|
+ state.modelConfig.paramsConfig[v.valueKey] = v.value
|
|
|
+ })
|
|
|
+ }
|
|
|
+}
|
|
|
+watch(
|
|
|
+ () => state.modelConfig,
|
|
|
+ (n) => {
|
|
|
+ emit('update:modelConfig', n)
|
|
|
+ },
|
|
|
+ { deep: true },
|
|
|
+)
|
|
|
+const optionsCpt = computed(() => {
|
|
|
+ if (!state.text) {
|
|
|
+ return state.options
|
|
|
+ }
|
|
|
+ return state.options
|
|
|
+ .map((v) => {
|
|
|
+ const obj = { ...v }
|
|
|
+ obj.options = obj.options.filter(
|
|
|
+ (s) => s.label.includes(state.text) || s.key.includes(state.text),
|
|
|
+ )
|
|
|
+ return obj
|
|
|
+ })
|
|
|
+ .filter((v) => v.options.length > 0)
|
|
|
+})
|
|
|
+const onModel = (row) => {
|
|
|
+ state.modelConfig.modelId = row.id
|
|
|
+ state.modelConfig.modelName = row.name
|
|
|
+ state.show = false
|
|
|
+}
|
|
|
+const onMouseDown = (e) => {
|
|
|
+ if (!domRootHasAttr(e.target, 'model-select-popover')) {
|
|
|
+ state.show = false
|
|
|
+ }
|
|
|
+}
|
|
|
+watch(
|
|
|
+ () => state.show,
|
|
|
+ (n) => {
|
|
|
+ if (n) {
|
|
|
+ document.addEventListener('mousedown', onMouseDown)
|
|
|
+ } else {
|
|
|
+ document.removeEventListener('mousedown', onMouseDown)
|
|
|
+ }
|
|
|
+ },
|
|
|
+ { immediate: true },
|
|
|
+)
|
|
|
+const onConfigMouseDown = (e) => {
|
|
|
+ if (!domRootHasAttr(e.target, 'model-select-config-popover')) {
|
|
|
+ state.showConfig = false
|
|
|
+ }
|
|
|
+}
|
|
|
+watch(
|
|
|
+ () => state.showConfig,
|
|
|
+ (n) => {
|
|
|
+ if (n) {
|
|
|
+ document.addEventListener('mousedown', onConfigMouseDown)
|
|
|
+ } else {
|
|
|
+ document.removeEventListener('mousedown', onConfigMouseDown)
|
|
|
+ }
|
|
|
+ },
|
|
|
+ { immediate: true },
|
|
|
+)
|
|
|
+onMounted(() => {
|
|
|
+ if (state.options.length === 0) {
|
|
|
+ pluginGetInstanceList({
|
|
|
+ page: 1,
|
|
|
+ size: 100000,
|
|
|
+ modeType: props.type,
|
|
|
+ status: 1,
|
|
|
+ }).then(({ data }: any) => {
|
|
|
+ state.options = data.records
|
|
|
+ })
|
|
|
+ }
|
|
|
+})
|
|
|
+onBeforeMount(() => {
|
|
|
+ reset()
|
|
|
+})
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+@use '@/views/workflow/instance/component/style';
|
|
|
+
|
|
|
+.bg {
|
|
|
+ background-color: style.$inputBg;
|
|
|
+}
|
|
|
+.vars-display {
|
|
|
+ //border: style.$borderStyle;
|
|
|
+ width: 100%;
|
|
|
+ height: 32px;
|
|
|
+ padding: 0 8px;
|
|
|
+ border-radius: 4px;
|
|
|
+ font-size: 12px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: flex-end;
|
|
|
+ position: relative;
|
|
|
+ background-color: style.$inputBg;
|
|
|
+ .del {
|
|
|
+ position: absolute;
|
|
|
+ right: 0;
|
|
|
+ width: 30px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ }
|
|
|
+}
|
|
|
+.filter {
|
|
|
+ padding: 10px;
|
|
|
+ border-bottom: style.$borderStyle;
|
|
|
+}
|
|
|
+</style>
|