CzRger 3 nedēļas atpakaļ
vecāks
revīzija
9793459e98

+ 56 - 0
src/views/workflow/handle.ts

@@ -241,6 +241,34 @@ export const handleNodeSubmit = (no) => {
         if (!no.__isMemory) {
           delete no.memory
         }
+        if (no.__modelConfig) {
+          no.modelConfig.pluginInstanceId = no.__modelConfig.modelId
+          no.modelConfig.paramConfigs = {}
+          if (no.__modelConfig.paramsConfig.isTemperature) {
+            no.modelConfig.paramConfigs.temperature =
+              no.__modelConfig.paramsConfig.temperature
+          }
+          if (no.__modelConfig.paramsConfig.isTopP) {
+            no.modelConfig.paramConfigs.top_p =
+              no.__modelConfig.paramsConfig.topP
+          }
+          if (no.__modelConfig.paramsConfig.isFrequency) {
+            no.modelConfig.paramConfigs.frequency_penalty =
+              no.__modelConfig.paramsConfig.frequency
+          }
+          if (no.__modelConfig.paramsConfig.isExist) {
+            no.modelConfig.paramConfigs.presence_penalty =
+              no.__modelConfig.paramsConfig.exist
+          }
+          if (no.__modelConfig.paramsConfig.isTokens) {
+            no.modelConfig.paramConfigs.max_tokens =
+              no.__modelConfig.paramsConfig.tokens
+          }
+          if (no.__modelConfig.paramsConfig.stopSequence?.length > 0) {
+            no.modelConfig.paramConfigs.stop =
+              no.__modelConfig.paramsConfig.stopSequence
+          }
+        }
       }
       break
     case NodeType.Knowledge:
@@ -317,6 +345,34 @@ export const handleNodeSubmit = (no) => {
     case NodeType.Switch:
       {
         no.classes = no.__ports
+        if (no.__modelConfig) {
+          no.modelConfig.pluginInstanceId = no.__modelConfig.modelId
+          no.modelConfig.paramConfigs = {}
+          if (no.__modelConfig.paramsConfig.isTemperature) {
+            no.modelConfig.paramConfigs.temperature =
+              no.__modelConfig.paramsConfig.temperature
+          }
+          if (no.__modelConfig.paramsConfig.isTopP) {
+            no.modelConfig.paramConfigs.top_p =
+              no.__modelConfig.paramsConfig.topP
+          }
+          if (no.__modelConfig.paramsConfig.isFrequency) {
+            no.modelConfig.paramConfigs.frequency_penalty =
+              no.__modelConfig.paramsConfig.frequency
+          }
+          if (no.__modelConfig.paramsConfig.isExist) {
+            no.modelConfig.paramConfigs.presence_penalty =
+              no.__modelConfig.paramsConfig.exist
+          }
+          if (no.__modelConfig.paramsConfig.isTokens) {
+            no.modelConfig.paramConfigs.max_tokens =
+              no.__modelConfig.paramsConfig.tokens
+          }
+          if (no.__modelConfig.paramsConfig.stopSequence?.length > 0) {
+            no.modelConfig.paramConfigs.stop =
+              no.__modelConfig.paramsConfig.stopSequence
+          }
+        }
       }
       break
   }

+ 382 - 0
src/views/workflow/instance/component/model-select/index.vue

@@ -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>

+ 2 - 2
src/views/workflow/instance/component/vars/vars-select.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="vars-select">
+  <div>
     <varsPopover
       :node="node"
       @setVars="(val) => ((state.vars = val), $emit('setVars', val))"
@@ -58,7 +58,7 @@ watch(
 @use '@/views/workflow/instance/component/style';
 
 .vars-display {
-  border: style.$borderStyle;
+  //border: style.$borderStyle;
   width: 100%;
   padding: 4px 4px;
   border-radius: 4px;

+ 3 - 1
src/views/workflow/instance/component/vars/vars-value.vue

@@ -22,7 +22,9 @@
     <span class="ml-4 opacity-65">{{ vars.type }}</span>
     <span v-if="vars.required" class="text-red-500">*</span>
   </div>
-  <div v-else class="opacity-65">{x}请选择变量</div>
+  <div v-else class="flex h-5.5 items-center px-1 opacity-65">
+    {x}请选择变量
+  </div>
 </template>
 
 <script setup lang="ts">

+ 2 - 1
src/views/workflow/instance/llm/default.ts

@@ -1,13 +1,14 @@
 import { v4 } from 'uuid'
 const nodeDefault = {
   defaultValue: () => ({
+    // handle字段
     modelConfig: {
       id: v4(),
       pluginInstanceId: '',
-      pluginInstanceName: '',
       paramConfigs: {},
       bizConfigs: {},
     },
+    __modelConfig: null,
     chatModelMessages: [{ role: 'system', text: '', id: v4() }],
     __contextVars: null,
     context: {

+ 2 - 2
src/views/workflow/instance/llm/node/index.vue

@@ -2,14 +2,14 @@
   <div class="">
     <div
       class="_n_content mb-2 flex items-center"
-      v-if="state.nodeData.modelConfig?.pluginInstanceName"
+      v-if="state.nodeData.__modelConfig?.modelName"
     >
       <img
         src="@/assets/images/model/model-default-logo.png"
         class="mr-2 size-4"
       />
       <span v-title>
-        {{ state.nodeData.modelConfig.pluginInstanceName }}
+        {{ state.nodeData.__modelConfig.modelName }}
       </span>
     </div>
   </div>

+ 5 - 27
src/views/workflow/instance/llm/panel/index.vue

@@ -1,17 +1,9 @@
 <template>
   <div class="panel-block" v-if="state.nodeData">
     <div class="__czr-title_2 my-4">模型</div>
-    <CzrFormColumn
-      :span="24"
-      label-width="0px"
-      v-model:param="state.nodeData.modelConfig.pluginInstanceId"
-      link="select"
-      :options="state.llmModelOptions"
-      labelKey="name"
-      valueKey="id"
-      @getObject="
-        (val) => (state.nodeData.modelConfig.pluginInstanceName = val.name)
-      "
+    <modelSelect
+      type="LLM"
+      v-model:modelConfig="state.nodeData.__modelConfig"
     />
     <div class="__czr-title_2 my-4">上下文</div>
     <varsSelect
@@ -112,7 +104,7 @@
         ></a-switch>
         <span class="text-sm">记忆窗口</span>
         <a-row class="ml-auto flex-1" justify="end">
-          <a-col :span="14">
+          <a-col :span="14" class="mr-2">
             <a-slider
               v-model:value="state.nodeData.memory.window.size"
               :min="1"
@@ -141,12 +133,12 @@
 import { getCurrentInstance, reactive, ref, watch } from 'vue'
 import paramsTextarea from '@/views/workflow/instance/component/params-textarea/index.vue'
 import varsOut from '@/views/workflow/instance/component/vars/vars-out.vue'
-import { pluginGetInstanceList } from '@/api/modules/model'
 import SvgIcon from '@/components/SvgIcon/index.vue'
 import varsSelect from '@/views/workflow/instance/component/vars/vars-select.vue'
 import { VarsSource } from '@/views/workflow/types'
 import nodeDefault from '@/views/workflow/instance/llm/default'
 import { v4 } from 'uuid'
+import modelSelect from '@/views/workflow/instance/component/model-select/index.vue'
 
 const emit = defineEmits([])
 const props = defineProps({
@@ -155,26 +147,12 @@ const props = defineProps({
 const { proxy }: any = getCurrentInstance()
 const state: any = reactive({
   nodeData: null,
-  llmModelOptions: [],
 })
-const initDictionary = () => {
-  if (state.llmModelOptions.length === 0) {
-    pluginGetInstanceList({
-      page: 1,
-      size: 100000,
-      modeType: 'LLM',
-      status: 1,
-    }).then(({ data }: any) => {
-      state.llmModelOptions = data.records
-    })
-  }
-}
 watch(
   () => props.node,
   (n) => {
     if (n) {
       state.nodeData = n.data.workflowData
-      initDictionary()
     }
   },
   { immediate: true },

+ 2 - 1
src/views/workflow/instance/switch/default.ts

@@ -4,13 +4,14 @@ const nodeDefault = {
   defaultValue: () => ({
     __queryVars: null,
     query_variable_selector: [],
+    // handle字段
     modelConfig: {
       id: v4(),
       pluginInstanceId: '',
-      pluginInstanceName: '',
       paramConfigs: {},
       bizConfigs: {},
     },
+    __modelConfig: null,
     classes: [], // handle字段
     memory: {
       window: {

+ 2 - 2
src/views/workflow/instance/switch/node/index.vue

@@ -2,14 +2,14 @@
   <div class="node-if-else my-2 flex flex-col gap-2">
     <div
       class="_n_content flex items-center"
-      v-if="state.nodeData.modelConfig?.pluginInstanceName"
+      v-if="state.nodeData.__modelConfig?.modelName"
     >
       <img
         src="@/assets/images/model/model-default-logo.png"
         class="mr-2 size-4"
       />
       <span v-title>
-        {{ state.nodeData.modelConfig.pluginInstanceName }}
+        {{ state.nodeData.__modelConfig.modelName }}
       </span>
     </div>
     <div class="flex flex-col gap-1">

+ 5 - 27
src/views/workflow/instance/switch/panel/index.vue

@@ -1,17 +1,9 @@
 <template>
   <div class="panel-block" v-if="state.nodeData">
     <div class="__czr-title_2 my-4">模型</div>
-    <CzrFormColumn
-      :span="24"
-      label-width="0px"
-      v-model:param="state.nodeData.modelConfig.pluginInstanceId"
-      link="select"
-      :options="state.llmModelOptions"
-      labelKey="name"
-      valueKey="id"
-      @getObject="
-        (val) => (state.nodeData.modelConfig.pluginInstanceName = val.name)
-      "
+    <modelSelect
+      type="LLM"
+      v-model:modelConfig="state.nodeData.__modelConfig"
     />
     <div class="__czr-title_2 my-4">输入变量</div>
     <varsSelect
@@ -60,7 +52,7 @@
       ></a-switch>
       <span class="text-sm">记忆窗口</span>
       <a-row class="ml-auto flex-1" justify="end">
-        <a-col :span="14">
+        <a-col :span="14" class="mr-2">
           <a-slider
             v-model:value="state.nodeData.memory.window.size"
             :min="1"
@@ -89,10 +81,10 @@ import { getCurrentInstance, inject, reactive, ref, watch } from 'vue'
 import { useWorkflowStore } from '@/stores'
 import switchNodeDefault from '../default'
 import varsSelect from '@/views/workflow/instance/component/vars/vars-select.vue'
-import { pluginGetInstanceList } from '@/api/modules/model'
 import paramsTextarea from '@/views/workflow/instance/component/params-textarea/index.vue'
 import SvgIcon from '@/components/SvgIcon/index.vue'
 import varsOut from '@/views/workflow/instance/component/vars/vars-out.vue'
+import modelSelect from '@/views/workflow/instance/component/model-select/index.vue'
 
 const WorkflowStore = useWorkflowStore()
 const emit = defineEmits(['reLayoutPort'])
@@ -102,26 +94,12 @@ const props = defineProps({
 const { proxy }: any = getCurrentInstance()
 const state: any = reactive({
   nodeData: null,
-  llmModelOptions: [],
 })
-const initDictionary = () => {
-  if (state.llmModelOptions.length === 0) {
-    pluginGetInstanceList({
-      page: 1,
-      size: 100000,
-      modeType: 'LLM',
-      status: 1,
-    }).then(({ data }: any) => {
-      state.llmModelOptions = data.records
-    })
-  }
-}
 watch(
   () => props.node,
   (n) => {
     if (n) {
       state.nodeData = n.data.workflowData
-      initDictionary()
     }
   },
   { immediate: true },