Bladeren bron

llm节点

CzRger 5 dagen geleden
bovenliggende
commit
eb34573575

File diff suppressed because it is too large
+ 1 - 0
src/assets/svg/switch.svg


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

@@ -223,6 +223,13 @@ export const handleNodeSubmit = (no) => {
         })
       }
       break
+    case NodeType.LLM:
+      {
+        if (!no.isMemory) {
+          delete no.memory
+        }
+      }
+      break
   }
   return no
 }

+ 5 - 11
src/views/workflow/instance/component/params-textarea/index.vue

@@ -1,10 +1,9 @@
 <template>
-  <version2
-    v-model="state.value"
-    :node="node"
-    :readonly="readonly"
-    :title="title"
-  />
+  <version2 v-model="state.value" v-bind="$attrs">
+    <template #title>
+      <slot name="title" />
+    </template>
+  </version2>
 </template>
 
 <script setup lang="ts">
@@ -29,11 +28,6 @@ const props = defineProps({
     type: String,
     default: '',
   },
-  node: {},
-  title: { default: '' },
-  readonly: {
-    default: false,
-  },
 })
 const state = reactive({
   value: props.modelValue,

+ 17 - 1
src/views/workflow/instance/component/params-textarea/version-2.vue

@@ -6,7 +6,11 @@
       <div
         class="flex items-center gap-2 py-1 text-xs font-semibold text-gray-700"
       >
-        <div>{{ title }}</div>
+        <div class="flex items-center">
+          <slot name="title">
+            {{ title }}
+          </slot>
+        </div>
         <div class="ml-auto">{{ state.textCount }}</div>
         <varsPopover :node="node" @setVars="setVars">
           <el-tooltip content="变量" placement="top">
@@ -22,6 +26,17 @@
             @click="onCopy(ref_textarea.innerText)"
           />
         </el-tooltip>
+        <template v-if="delFunc">
+          <el-tooltip content="删除" placement="top">
+            <SvgIcon
+              class="__hover"
+              color="#364153"
+              name="czr_del"
+              size="16"
+              @click="delFunc?.()"
+            />
+          </el-tooltip>
+        </template>
       </div>
       <div class="text-text-secondary flex-1 overflow-y-auto py-1 text-[13px]">
         <div
@@ -123,6 +138,7 @@ const props = defineProps({
   readonly: {
     default: false,
   },
+  delFunc: { default: undefined },
 })
 const state = reactive({
   textCount: 0,

+ 4 - 1
src/views/workflow/instance/component/vars/vars-detail.vue

@@ -131,7 +131,6 @@ watch(
     if (n) {
       state.form = props.transfer.row || {
         type: 'String',
-        source: VarsSource.Param,
       }
       if (state.form.type === 'Select') {
         state.options = state.form.options.map((v) => ({ value: v }))
@@ -148,6 +147,7 @@ const onSubmit = () => {
     .then(() => {
       emit('update:show', false)
       const res: any = {
+        source: VarsSource.Param,
         label: state.form.label,
         key: state.form.key,
         type: state.form.type,
@@ -191,6 +191,9 @@ const onSubmit = () => {
         border-color: var(--czr-main-color);
         color: var(--czr-main-color);
       }
+      &.active {
+        background-color: #ffffff;
+      }
     }
   }
   .vars-detail-form {

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

@@ -1,6 +1,9 @@
 <template>
   <div class="vars-select">
-    <varsPopover :node="node" @setVars="(val) => (state.vars = val)">
+    <varsPopover
+      :node="node"
+      @setVars="(val) => ((state.vars = val), $emit('setVars', val))"
+    >
       <div
         class="vars-display __hover"
         @mouseenter="state.hovering = true"
@@ -13,7 +16,7 @@
             size="10"
             color="rgba(0, 0, 0, 0.5)"
             class="__hover"
-            @click.stop="state.vars = null"
+            @click.stop="((state.vars = null), $emit('setVars', null))"
           />
         </div>
       </div>
@@ -26,7 +29,7 @@ import { computed, getCurrentInstance, reactive, ref, watch } from 'vue'
 import varsValue from './vars-value.vue'
 import varsPopover from './vars-popover.vue'
 
-const emit = defineEmits([])
+const emit = defineEmits(['setVars'])
 const props = defineProps({
   node: {},
   vars: { default: null },
@@ -56,7 +59,6 @@ const state: any = reactive({
     position: absolute;
     right: 0;
     width: 30px;
-    background-color: #ffffff;
     display: flex;
     align-items: center;
     justify-content: center;

+ 17 - 5
src/views/workflow/instance/llm/default.ts

@@ -8,11 +8,23 @@ const nodeDefault = {
       paramConfigs: {},
       bizConfigs: {},
     },
-    chatModelMessages: [
-      { role: 'system', text: '', id: v4() },
-      { role: 'user', text: '', id: v4() },
-      { role: 'assistant', text: '', id: v4() },
-    ],
+    chatModelMessages: [{ role: 'system', text: '', id: v4() }],
+    context: {
+      enabled: false,
+      variable_selector: [],
+    },
+    isMemory: false,
+    memory: {
+      window: {
+        enabled: false,
+        size: 50,
+      },
+      query_prompt_template: '',
+      role_prefix: {
+        user: '',
+        assistant: '',
+      },
+    },
     outVars: [{ label: '生成内容', key: 'text', type: 'String' }],
   }),
 }

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

@@ -6,16 +6,6 @@
     >
       {{ state.nodeData.modelConfig.pluginInstanceName }}
     </div>
-    <template v-for="item in state.nodeData.chatModelMessages">
-      <paramsTextarea
-        v-if="item.text"
-        v-model="item.text"
-        :node="node"
-        :readonly="true"
-        class="mb-2"
-        :title="item.role"
-      />
-    </template>
   </div>
 </template>
 
@@ -28,7 +18,6 @@ import {
   reactive,
   ref,
 } from 'vue'
-import paramsTextarea from '@/views/workflow/instance/component/params-textarea/index.vue'
 
 const emit = defineEmits([])
 const props = defineProps({

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

@@ -15,13 +15,119 @@
         (val) => (state.nodeData.modelConfig.pluginInstanceName = val.name)
       "
     />
-    <template v-for="item in state.nodeData.chatModelMessages">
+    <div class="_p-title">
+      <div class="text-sm">上下文</div>
+    </div>
+    <varsSelect :node="props.node" @setVars="setContent" />
+    <template v-for="(item, index) in state.nodeData.chatModelMessages">
       <paramsTextarea
         v-model="item.text"
         :node="node"
         class="mt-4 max-h-100"
-        :title="item.role"
-      />
+        :delFunc="
+          item.role === 'system'
+            ? undefined
+            : () => state.nodeData.chatModelMessages.splice(index, 1)
+        "
+      >
+        <template #title>
+          <template v-if="item.role !== 'system'">
+            <span
+              class="__hover flex items-center"
+              @click="item.role = item.role === 'user' ? 'assistant' : 'user'"
+            >
+              {{ item.role.toUpperCase()
+              }}<SvgIcon name="switch" color="text-gray-700" />
+            </span>
+          </template>
+          <template v-else>{{ item.role.toUpperCase() }}</template>
+          <template v-if="item.role === 'system'">
+            <el-tooltip content="为对话提供高层指导" placement="top">
+              <SvgIcon name="czr_tip" size="14" class="ml-2" />
+            </el-tooltip>
+          </template>
+          <template v-else-if="item.role === 'user'">
+            <el-tooltip
+              content="向模型提供指令、查询或任何基于文本的输入"
+              placement="top"
+            >
+              <SvgIcon name="czr_tip" size="14" class="ml-2" />
+            </el-tooltip>
+          </template>
+          <template v-else-if="item.role === 'assistant'">
+            <el-tooltip content="基于用户消息的模型回复" placement="top">
+              <SvgIcon name="czr_tip" size="14" class="ml-2" />
+            </el-tooltip>
+          </template>
+        </template>
+      </paramsTextarea>
+    </template>
+    <a-button
+      style="margin-top: 8px"
+      class="w-full"
+      @click="
+        state.nodeData.chatModelMessages.push({
+          role: 'user',
+          text: '',
+          id: v4(),
+        })
+      "
+    >
+      添加消息
+    </a-button>
+    <div class="_p-title">
+      <div class="text-sm">记忆</div>
+      <el-tooltip content="聊天记忆设置" placement="top">
+        <SvgIcon name="czr_tip" size="14" class="mr-auto ml-2" />
+      </el-tooltip>
+      <a-switch
+        v-model:checked="state.nodeData.isMemory"
+        size="small"
+        @change="onSwitchMemory"
+      ></a-switch>
+    </div>
+    <template v-if="state.nodeData.isMemory && state.nodeData.memory">
+      <paramsTextarea
+        v-model="state.nodeData.memory.query_prompt_template"
+        :node="node"
+        class="mt-4 max-h-100"
+      >
+        <template #title>
+          USER
+          <el-tooltip
+            content="向模型提供指令、查询或任何基于文本的输入"
+            placement="top"
+          >
+            <SvgIcon name="czr_tip" size="14" class="ml-2" />
+          </el-tooltip>
+        </template>
+      </paramsTextarea>
+      <div class="mt-2 flex items-center">
+        <a-switch
+          v-model:checked="state.nodeData.memory.window.enabled"
+          size="small"
+        ></a-switch>
+        <span class="text-sm">记忆窗口</span>
+        <a-row class="ml-auto flex-1" justify="end">
+          <a-col :span="14">
+            <a-slider
+              v-model:value="state.nodeData.memory.window.size"
+              :min="1"
+              :max="100"
+              :disabled="!state.nodeData.memory.window.enabled"
+            />
+          </a-col>
+          <a-col :span="6">
+            <a-input-number
+              v-model:value="state.nodeData.memory.window.size"
+              :min="1"
+              :max="100"
+              :disabled="!state.nodeData.memory.window.enabled"
+              style="width: 100%"
+            />
+          </a-col>
+        </a-row>
+      </div>
     </template>
     <div class="_p-title">
       <div class="text-sm">输出变量</div>
@@ -32,12 +138,15 @@
 
 <script setup lang="ts">
 import { getCurrentInstance, reactive, ref, watch } from 'vue'
-import { useWorkflowStore } from '@/stores'
 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 { v4 } from 'uuid'
+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'
 
-const WorkflowStore = useWorkflowStore()
 const emit = defineEmits([])
 const props = defineProps({
   node: <any>{},
@@ -71,6 +180,29 @@ watch(
   },
   { immediate: true },
 )
+const setContent = (val) => {
+  if (val) {
+    state.nodeData.context = {
+      enabled: true,
+      variable_selector:
+        val.source === VarsSource.Root
+          ? [val.key.split('.')[0], val.key.split('.')[1]]
+          : [val.nodeId, val.key],
+    }
+  } else {
+    state.nodeData.context = {
+      enabled: false,
+      variable_selector: [],
+    }
+  }
+}
+const onSwitchMemory = (val) => {
+  if (val) {
+    if (!state.nodeData.memory) {
+      state.nodeData.memory = nodeDefault.defaultValue().memory
+    }
+  }
+}
 </script>
 
 <style lang="scss" scoped>