Sfoglia il codice sorgente

feat: 合并prompt configuration页面

zhouyuhao 7 mesi fa
parent
commit
b6750934b6

+ 2 - 1
package.json

@@ -31,6 +31,7 @@
     "element-plus": "^2.8.1",
     "exceljs": "^4.4.0",
     "github-markdown-css": "^5.8.1",
+    "highlight.js": "^11.11.1",
     "leaflet": "^1.9.4",
     "lodash": "^4.17.21",
     "markdown-it": "^14.1.0",
@@ -46,7 +47,7 @@
     "vue3-virtual-scroller": "^0.2.3",
     "vuedraggable": "^2.24.3",
     "vxe-pc-ui": "^4.1.7",
-    "vxe-table": "^4.7.70",
+    "vxe-table": "^4.13.15",
     "vxe-table-plugin-export-xlsx": "^4.0.5",
     "xe-clipboard": "^1.10.2",
     "xlsx": "^0.18.5"

+ 31 - 6
src/components/AIRobot/src/AIRobot.vue

@@ -9,6 +9,7 @@ const AIRobotHoverVisible = ref(false)
 const clicked = ref(false)
 const isShowDefault = ref(false)
 const isShowAIRobotTop = ref(false)
+const AIIconVisible = ref(false)
 const DeQuestions = ref([
   {
     label: 'List shipments with ETA changes in the last 30 days.',
@@ -78,7 +79,7 @@ const AvatarMouseLeave = () => {
     AIRobotHoverVisible.value = false
   }
 }
-const emit = defineEmits(['AvatarClick'])
+const emit = defineEmits(['AvatarClick', 'handelClickAIDefault'])
 // 点击AIRobot图标
 const AvatarClick = () => {
   clicked.value = true
@@ -123,13 +124,31 @@ const prepareGroups = () => {
 
   itemGroups.value = groups
 }
-
 const isShowLogin = () => {
+  const LoginDays = 0
+  let settimeouttime = 0
+  AIIconVisible.value = true
   isShowDefault.value = true
+  if (LoginDays == 0) {
+    settimeouttime = 45000
+  } else if (LoginDays == 2) {
+    settimeouttime = 15000
+  } else {
+    settimeouttime = 10000
+  }
   setTimeout(() => {
     isShowDefault.value = false
     isShowAIRobotTop.value = true
-  }, 5000)
+  }, settimeouttime)
+}
+
+// 点击问题
+const handelClick = (item: any) => {
+  emit('handelClickAIDefault', item.value)
+  clicked.value = true
+  AIRobotHoverVisible.value = false
+  isShowDefault.value = false
+  isShowAIRobotTop.value = false
 }
 
 onMounted(() => {
@@ -150,7 +169,7 @@ defineExpose({
   <div class="AIRobot-top" v-if="isShowDefault">
     <div class="flex_end" @click="HideAIRobotTop">
       <div class="icon flex_center">
-        <span class="iconfont_icon icon_dark">
+        <span class="iconfont_icon icon_dark AI_icon">
           <svg class="iconfont" aria-hidden="true">
             <use xlink:href="#icon-icon_reject_b"></use>
           </svg>
@@ -176,6 +195,7 @@ defineExpose({
             <div class="dialogue_container">
               <div
                 class="dialogue_content_item"
+                @click="handelClick(item)"
                 v-for="item in group"
                 :key="item.label"
                 :class="{ long_item: item.isLong }"
@@ -191,7 +211,7 @@ defineExpose({
   <div class="AIRobot-top" v-if="isShowAIRobotTop">
     <div class="flex_end" @click="HideAIRobotTopTwo">
       <div class="icon flex_center">
-        <span class="iconfont_icon icon_dark">
+        <span class="iconfont_icon icon_dark AI_icon">
           <svg class="iconfont" aria-hidden="true">
             <use xlink:href="#icon-icon_reject_b"></use>
           </svg>
@@ -221,7 +241,7 @@ defineExpose({
 </template>
 
 <style lang="scss">
-.iconfont_icon {
+.AI_icon {
   margin-right: 0;
 }
 .flex_center {
@@ -331,6 +351,11 @@ defineExpose({
   word-break: break-word;
   overflow: hidden;
   transition: all 0.3s ease;
+  cursor: pointer;
+}
+.dialogue_content_item:hover {
+  background-color: var(--color-arrow-hoverL);
+  color: var(--color-theme);
 }
 .itemLable {
   width: 100%;

+ 1 - 1
src/components/ScoringGrade/src/ScoringGrade.vue

@@ -400,7 +400,7 @@ const SubmitText = ref()
   z-index: 2013;
   right: 20px;
   bottom: 80px;
-  display: flex;
+  display: none;
   align-items: center;
   justify-content: center;
 }

+ 5 - 0
src/router/index.ts

@@ -106,6 +106,11 @@ const router = createRouter({
           name: 'AI API Log',
           component: () => import('../views/AIApiLog')
         },
+        {
+          path: '/PromptConfiguration',
+          name: 'PromptConfiguration',
+          component: () => import('../views/PromptConfiguration')
+        },
         {
           path: '/system-message',
           name: 'System Message',

+ 29 - 0
src/styles/elementui.scss

@@ -15,6 +15,21 @@
   }
 }
 
+.el-button.el-button--noborder--configuration {
+  border: none;
+  span {
+    color: var(--color-theme);
+  }
+  &:hover {
+    border-color: var(--color-btn-default-bg-hover);
+    background-color: var(--color-btn-default-bg-hover);
+    fill: var(--color-theme);
+    span {
+      color: var(--color-theme);
+    }
+  }
+}
+
 button.el-button.el-button--text {
   height: 24px;
   padding: 4px 8px;
@@ -832,4 +847,18 @@ div .carousel .el-carousel__arrow--right {
 div .carousel .el-icon {
   fill: black;
   color: black;
+}
+div .carousel .el-carousel__arrow:hover {
+  background-color: var(--color-arrow-hoverL);
+  .el-icon {
+    fill: var(--color-theme);
+    color: var(--color-theme);
+  }
+}
+div .prompt-dialog {
+  min-height: 800px ;
+}
+div .prompt-dialog-inner .el-dialog__header {
+  padding: 0;
+  height: 48px;
 }

+ 1 - 0
src/styles/theme-g.scss

@@ -90,5 +90,6 @@
   --color-dialogue_container-bg:rgba(255, 255, 255, 0.10);
   --color-dialogue_title: linear-gradient(90deg, var(--1-gradient-ai-robot-faq-0, #FFA8C7) 1.77%, var(--1-gradient-ai-robot-faq-46, #5988f3) 46.77%);
   --color-dialogue_content-bg:linear-gradient(117deg, var(--1-gradient-ai-robot-0, #525CBA) 4.31%, var(--1-gradient-ai-robot-15, #5A57B2) 14.24%, var(--1-gradient-ai-robot-38, #5F54AD) 29.71%, var(--1-gradient-ai-robot-59, #664EA2) 43.72%, var(--1-gradient-ai-robot-83, #694CA0) 59.35%, var(--1-gradient-ai-robot-100, #724493) 70.56%);
+  --color-arrow-hoverL: #FCEEE3;
 
 }

+ 5 - 0
src/styles/theme.scss

@@ -304,6 +304,11 @@
   --color-ai-user-bubble-bg-gradient-end: #f2f4f7;
   --input-border: #eaebed;
   --color-pause-btn-bg: #fff1e6;
+  --color-arrow-hoverL: #FCEEE3;
+
+  --color-output-type-bg: #4361ED;
+  --color-output-type-string-bg: #6C757E;
+  --color-output-select-text: #F9A725;
   --color-loading-text: #b5b9bf;
   --color-warning-tips-bg: #fff4d1;
 }

+ 1 - 1
src/views/AIApiLog/src/components/TableView/src/TableView.vue

@@ -384,7 +384,7 @@ defineExpose({
         >
           <span
             style="margin-right: 2px; font-size: 15px"
-            class="font_family icon-icon_save_b"
+            class="font_family icon-icon_ai_api_log_b"
           ></span
           >AI API Log
         </el-button>

+ 4 - 0
src/views/AIRobotChat/src/AIRobotChat.vue

@@ -229,6 +229,10 @@ const handleClose = () => {
   progressInterval.value && clearInterval(progressInterval.value)
   emit('close')
 }
+
+defineExpose({
+  handleSend
+})
 </script>
 
 <template>

+ 7 - 1
src/views/AIRobotChat/src/components/AIQuestions.vue

@@ -208,7 +208,8 @@ const clickQuestion = (question) => {
               class="dialogue_content_item"
               v-for="item in group"
               :key="item.label"
-              :class="{ long_item: item.isLong }"
+              @click="clickQuestion(item.label)"
+              :class="{ 'long_item': item.isLong }"
             >
               {{ item.label }}
             </div>
@@ -256,6 +257,10 @@ const clickQuestion = (question) => {
   display: flex;
   min-height: 0; /* 防止内容溢出 */
 }
+.dialogue_content_item:hover {
+  background-color: var(--color-arrow-hoverL);
+  color: var(--color-theme);
+}
 .long-item {
   grid-column: span 2;
 }
@@ -276,6 +281,7 @@ const clickQuestion = (question) => {
   align-items: center;
   justify-content: start;
   text-align: center;
+  cursor: pointer;
   min-width: 0; /* 防止文本溢出 */
 }
 :deep(.el-carousel__item--card, .el-carousel__item.is-animating) {

+ 16 - 3
src/views/Layout/src/LayoutView.vue

@@ -22,10 +22,19 @@ const handleColseRobotChat = () => {
 const onClick = () => {
   isShowAIRobotChat.value = true
 }
-
 const AvatarClick = () => {
   isShowAIRobotChat.value = true
 }
+
+const AIRobotChatref = ref()
+// 点击问题
+const handelClickAIDefault = async (item: any) => {
+  isShowAIRobotChat.value = true
+  await nextTick()
+  if (AIRobotChatref.value) {
+    AIRobotChatref.value.handleSend(item)
+  }
+}
 </script>
 <template>
   <el-container class="layout-container">
@@ -55,8 +64,12 @@ const AvatarClick = () => {
         <router-view />
       </el-main>
     </el-container>
-    <AIRobot @AvatarClick="AvatarClick"></AIRobot>
-    <AIRobotChat v-if="isShowAIRobotChat" @close="handleColseRobotChat"></AIRobotChat>
+    <AIRobot @AvatarClick="AvatarClick" @handelClickAIDefault="handelClickAIDefault"></AIRobot>
+    <AIRobotChat
+      ref="AIRobotChatref"
+      v-if="isShowAIRobotChat"
+      @close="handleColseRobotChat"
+    ></AIRobotChat>
     <ScoringGrade></ScoringGrade>
   </el-container>
 </template>

+ 5 - 0
src/views/Layout/src/components/Menu/MenuView.vue

@@ -59,6 +59,11 @@ const menuList = ref([
         index: '4-5',
         label: 'AI API Log',
         path: '/ai-api-log'
+      },
+      {
+        index: '4-6',
+        label: 'Prompt Configuration',
+        path: '/PromptConfiguration'
       }
     ]
   }

+ 1 - 0
src/views/PromptConfiguration/index.ts

@@ -0,0 +1 @@
+export { default } from './src/PromptConfiguration.vue'

+ 383 - 0
src/views/PromptConfiguration/src/PromptConfiguration.vue

@@ -0,0 +1,383 @@
+<script setup lang="ts">
+import { ref } from 'vue'
+import TableConfiguration from './components/TableConfiguration.vue'
+import RespnseConfiguration from './components/RespnseConfiguration.vue'
+import OutputConfiguration from './components/OutputConfiguration.vue'
+import PreviewTesting from './components/PreviewTesting.vue'
+import moment from 'moment'
+
+const rolename = ref('')
+const professionalfield = ref('')
+const maintasks = ref('')
+const TableConfigurationref = ref()
+const RespnseConfigurationref = ref()
+const PromptdialogVisible = ref(false)
+const PromptdialogInnerVisible = ref(false)
+const ChangeLogList = ref([
+  {
+    time: '2025-04-23 09:30 Prompt1',
+    person: '提交人:caroline@kln.com',
+    text: '您是专門从事货运代理行业的SQL专家顾问专門从事货运代理行业的SQL专家顾问专門从事货运代理行业的SQL专家顾问专門从事货运代理行业的SQL1...'
+  },
+  {
+    time: '2025-04-23 09:30 Prompt2',
+    person: '提交人:caroline@kln.com',
+    text: '您是专門从事货运代理行业的SQL专家顾问专門从事货运代理行业的SQL专家顾问专門从事货运代理行业的SQL专家顾问专門从事货运代理行业的SQL2...'
+  },
+  {
+    time: '2025-04-23 09:30 Prompt3',
+    person: '提交人:caroline@kln.com',
+    text: '您是专門从事货运代理行业的SQL专家顾问专門从事货运代理行业的SQL专家顾问专門从事货运代理行业的SQL专家顾问专門从事货运代理行业的SQL3...'
+  },
+  {
+    time: '2025-04-23 09:30 Prompt4',
+    person: '提交人:caroline@kln.com',
+    text: '您是专門从事货运代理行业的SQL专家顾问专門从事货运代理行业的SQL专家顾问专門从事货运代理行业的SQL专家顾问专門从事货运代理行业的SQL4...'
+  },
+  {
+    time: '2025-04-23 09:30 Prompt5',
+    person: '提交人:caroline@kln.com',
+    text: '您是专門从事货运代理行业的SQL专家顾问专門从事货运代理行业的SQL专家顾问专門从事货运代理行业的SQL专家顾问专門从事货运代理行业的SQL5...'
+  },
+  {
+    time: '2025-04-23 09:30 Prompt6',
+    person: '提交人:caroline@kln.com',
+    text: '您是专門从事货运代理行业的SQL专家顾问专門从事货运代理行业的SQL专家顾问专門从事货运代理行业的SQL专家顾问专門从事货运代理行业的SQL6...'
+  },
+  {
+    time: '2025-04-23 09:30 Prompt7',
+    person: '提交人:caroline@kln.com',
+    text: '您是专門从事货运代理行业的SQL专家顾问专門从事货运代理行业的SQL专家顾问专門从事货运代理行业的SQL专家顾问专門从事货运代理行业的SQL7...'
+  },
+  {
+    time: '2025-04-23 09:30 Prompt8',
+    person: '提交人:caroline@kln.com',
+    text: '您是专門从事货运代理行业的SQL专家顾问专門从事货运代理行业的SQL专家顾问专門从事货运代理行业的SQL专家顾问专門从事货运代理行业的SQL8...'
+  }
+])
+const prompttext = ref('')
+
+const AddTableConfiguration = () => {
+  TableConfigurationref.value.addNewTableColumn()
+}
+
+const addstepdata = () => {
+  RespnseConfigurationref.value.addstepdata()
+}
+
+const stepData = ref([
+  {
+    description: '分析用户的具体问题,确定是否可以通过现有的表字段进行查询。'
+  },
+  {
+    description: '如果查询可行,根据确定的条件生成可直接执行的SQL语句。'
+  },
+  {
+    description: '如果因为表的字段未覆盖不能查询,说明原因,并联网查询该用户问题,并以KLN公司业务代表的角度回答问题,放在response里,如果回复要使用公司名称,请使用:KLN。'
+  },
+  {
+    description: '如果不能查询是因为问题缺失具体条件,请在response里面反馈让客户输入具体的reference number来进行查询。'
+  },
+  {
+    description: '请使用与用户消息相同的语言回复。如果用户使用中文提问,请用中文回答;如果用户使用英文提问,请使用英文回答;以此类推。始终以用户实际使用的语言为准,不要参考system prompt的语言。'
+  }
+])
+
+// 导出为txt
+const exporttxt = () => {
+  // 创建 Blob 对象
+  const blob = new Blob([prompttext.value], { type: 'text/plain' })
+   // 创建下载链接
+  const link = document.createElement('a')
+  link.href = URL.createObjectURL(blob)
+  const currentDate = moment();
+  const formattedDate = currentDate.format('YYYY-MM-DD HH:mm');
+  link.download = `${formattedDate} Prompt.txt` // 自定义文件名
+  
+  // 触发下载
+  link.click()
+  
+  // 清理内存
+  URL.revokeObjectURL(link.href)
+}
+
+// 查看日志
+const ViewPrompt = (item: any) => {
+  console.log(item)
+  PromptdialogInnerVisible.value = true
+  prompttext.value = item
+}
+
+// 返回上一级
+const Backprompt = () => {
+  PromptdialogInnerVisible.value = false
+}
+
+</script>
+
+<template>
+  <div class="Title">
+    <div>Prompt Configuration</div>
+    <div>
+      <el-button class="el-button--default title-button" style="margin-right: 8px;" @click="PromptdialogVisible = true"><span class="font_family icon-icon_dashboard_b icon_dark" style="margin-right: 5px;"></span>变更日志查看</el-button>
+      <el-button class="el-button--main title-button"><span class="font_family icon-icon_save_b icon_dark" style="margin-right: 5px;"></span>保存生效</el-button>
+    </div>
+  </div>
+  <el-dialog
+    v-model="PromptdialogVisible"
+    width="1000px"
+    title="变更日志"
+    :modal="false"
+    align-center
+    class="prompt-dialog"
+    :close-on-click-modal="false"
+  > 
+    <div class="diaolog-body">
+      <div class="diaolog-content" v-for="(item, index) in ChangeLogList" :key="index">
+        <div class="diaolog-flex">
+          <div>
+            <div class="dialog-title">{{ item.time }}</div>
+            <div class="dialog-person">{{ item.person }}</div>
+          </div>
+          <div>
+            <el-button class="el-button--default" @click="ViewPrompt(item.text)"><span class="font_family icon-icon_view_b icon_dark" style="margin-right: 5px;" ></span>查看完整Prompt</el-button>
+          </div>
+        </div>
+        <div class="diaolog-text">
+          {{ item.text }}
+        </div>
+      </div>
+    </div>
+  </el-dialog>
+  <el-dialog
+    v-model="PromptdialogInnerVisible"
+    width="1000px"
+    :modal="false"
+    align-center
+    class="prompt-dialog prompt-dialog-inner"
+    :close-on-click-modal="false"
+  > 
+    <template #header>
+      <div class="dialog-header">
+        <div style="width: 55%;display: flex;justify-content: space-between;">
+          <div class="Back" @click="Backprompt"><span class="font_family icon-icon_previous_b icon_dark"></span>Back</div>
+          <div style="display: flex;align-items: center;">查看完整Prmopt</div>
+        </div>
+        <el-button class="el-button--default" @click="exporttxt"><span class="font_family icon-icon_import_b icon_dark" style="margin-right: 5px;" ></span>导出完整日志</el-button>
+      </div>
+    </template>
+    <div class="diaolog-content">
+      {{ prompttext }}
+    </div>
+  </el-dialog>
+  <div class="prompt-content">
+    <!-- 系统角色设置 -->
+    <div class="prompt-border">
+      <div class="prompt-title">系统角色设置</div>
+      <div class="flex">
+        <div style="width: 50%; margin-right: 8px;">
+          <div class="little-title">角色名称</div>
+          <el-input v-model="rolename" placeholder="为AI Robot创建一个角色名称" class="input-name"></el-input>
+        </div>
+        <div style="width: 50%;">
+          <div class="little-title">专业领域</div>
+          <el-input v-model="professionalfield" placeholder="为AI Robot设定专业领域" class="input-name"></el-input>
+        </div>
+      </div>
+      <div style="margin-top: 16px;">
+        <div class="little-title">主要任务</div>
+        <el-input v-model="maintasks" type="textarea" placeholder="简要描述AI Robot的主要任务" class="input-name-textarea" :rows="3"></el-input>
+      </div>
+    </div>
+    <!-- 表结构配置 -->
+    <div class="prompt-border">
+      <div class="prompt-title">表结构配置</div>
+      <div class="flex">
+        <div style="width: 50%; margin-right: 8px;">
+          <div class="little-title"><span class="stars_red">*</span>表名</div>
+          <el-input v-model="rolename" placeholder="创建一个表名" class="input-name"></el-input>
+        </div>
+        <div style="width: 50%;">
+          <div class="little-title">表描述</div>
+          <el-input v-model="professionalfield" placeholder="简要描述表的用途" class="input-name"></el-input>
+        </div>
+      </div>
+      <div style="margin-top: 16px;">
+        <el-button class="el-button--noborder--configuration prompt-button" @click="AddTableConfiguration">+ 添加字段</el-button>
+        <TableConfiguration ref="TableConfigurationref"></TableConfiguration>
+      </div>
+    </div>
+    <!-- 响应规则配置 -->
+    <div class="prompt-border">
+      <div class="prompt-title">响应规则配置</div>
+      <div style="margin-top: 16px;">
+        <el-button class="el-button--noborder--configuration prompt-button" @click="addstepdata" v-if="stepData.length != 0">+ 添加步骤</el-button>
+        <RespnseConfiguration ref="RespnseConfigurationref" :stepDataprops="stepData"></RespnseConfiguration>
+      </div>
+    </div>
+    <!-- 输出格式配置 -->
+    <div class="prompt-border">
+      <div class="prompt-title">输出格式配置</div>
+      <div style="margin-top: 16px;">
+        <OutputConfiguration ref="OutputConfigurationref"></OutputConfiguration>
+      </div>
+    </div>
+    <!-- 预览与测试 -->
+     <div class="propmt-border-colorful">
+        <div class="prompt-title" style="padding: 0 14px;">预览与测试</div>
+        <div style="margin-top: 16px;">
+          <PreviewTesting ref="PreviewTestingref"></PreviewTesting>
+        </div>
+     </div>
+     <div class="propmt-save"><el-button class="el-button--main"><span class="font_family icon-icon_save_b icon_dark" style="margin-right: 5px;"></span>保存生效</el-button></div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.Title {
+  display: flex;
+  height: 68px;
+  border: 1px solid var(--color-border);
+  border-top: none;
+  border-width: 1px 0 1px 0;
+  font-size: var(--font-size-6);
+  font-weight: 700;
+  padding: 0 24px;
+  align-items: center;
+  justify-content: space-between;
+}
+.prompt-content {
+  padding: 16px 24px;
+}
+.prompt-border {
+  border: 1px solid var(--color-system-border-1);
+  border-radius: 12px;
+  padding: 13px 16px;
+  margin-bottom: 8px;
+}
+.propmt-border-colorful {
+  border: 2px solid transparent;
+  border-radius: 12px;
+  background-clip: padding-box, border-box;
+  background-origin: padding-box, border-box;
+  background-image: linear-gradient(to bottom, #FFF, #FFF), linear-gradient(to bottom, #FF7500, #8112FF);
+  margin-bottom: 8px;
+  padding: 13px 0;
+}
+.prompt-button {
+  margin-bottom: 8px;
+}
+.stars_red {
+  color: var(--color-danger);
+}
+.prompt-title {
+  font-size: 18px;
+  font-weight: 700;
+  margin-bottom: 21px;
+}
+.flex {
+  display: flex;
+}
+.little-title {
+  font-size: 12px;
+  font-weight: 400;
+  color: var(--color-neutral-2);
+  margin-bottom: 4px;
+}
+.input-name {
+  height: 40px;
+}
+.input-name-textarea {
+  height: 80px;
+}
+:deep(.el-textarea__inner) {
+  height: 80px;
+  line-height: 1.5; /* 多行文本的行高 */
+  padding: 11px 8px; /* 调整垂直居中 */
+  resize: none; /* 禁用拖拽调整大小(可选) */
+}
+:deep(.el-button, .el-button.is-round) {
+  padding: 4px 8px;
+}
+:deep(.el-textarea__inner) {
+  font-weight: 400;
+  font-size: 14px;
+  color: var(--color-neutral-1);
+}
+.propmt-save {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+.el-button--main {
+  width: 400px;
+  height: 40px;
+}
+.title-button {
+  width: 128px;
+  height: 40px;
+}
+.diaolog-body {
+  max-height: 720px;
+  overflow-y: scroll;
+}
+.diaolog-content {
+  background-color: #F8F9FD;
+  padding: 16px;
+  margin-bottom: 8px;
+  border-radius: 6px;
+}
+.diaolog-flex {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+}
+.el-button--default {
+  height: 40px;
+}
+.dialog-title {
+  font-size: 14px;
+  font-weight: 700;
+  color: var(--color-neutral-1);
+}
+.dialog-person {
+  font-size: 12px;
+  font-weight: 400;
+  color: var(--color-neutral-2);
+}
+.diaolog-text {
+  font-size: 14px;
+  color: var(--color-neutral-2);
+  font-weight: 400;
+  margin-top: 16px;
+  overflow: hidden;
+  white-space: nowrap;
+  text-overflow: ellipsis;
+}
+.dialog-header {
+  height: 48px;
+  width: 95%;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+.Back {
+  color: var(--color-neutral-1);
+  font-size: 14px;
+  font-weight: 400;
+  display: flex;
+  align-items: end;
+  margin-left: 23px;
+  cursor: pointer;
+  padding: 8px;
+  border-radius: 6px;
+}
+.Back:hover {
+  border-color: var(--color-btn-default-bg-hover);
+  background-color: var(--color-btn-default-bg-hover);
+  color: var(--color-theme);
+    span {
+      color: var(--color-theme);
+    }
+}
+</style>

+ 89 - 0
src/views/PromptConfiguration/src/components/CodeBlock.vue

@@ -0,0 +1,89 @@
+<template>
+  <pre><code class="json" ref="codeBlock">{{ formattedCode }}</code></pre>
+</template>
+
+<script setup>
+import { computed, onMounted, ref, watch } from 'vue'
+import hljs from 'highlight.js'
+
+const props = defineProps({
+  language: {
+    type: String,
+    default: 'javascript'
+  },
+  rawCode: {
+    type: String,
+    required: true
+  }
+})
+
+const codeBlock = ref(null)
+
+// 自动格式化 JSON
+const formattedCode = computed(() => {
+  if (props.language === 'json') {
+    try {
+      const parsed = JSON.parse(props.rawCode)
+      return JSON.stringify(parsed, null, 4) // 关键:2空格缩进
+    } catch (e) {
+      return props.rawCode // 解析失败时返回原始内容
+    }
+  }
+  return props.rawCode
+})
+
+// 自定义JSON高亮规则
+hljs.registerLanguage('json', function (hljs) {
+  return {
+    name: 'JSON',
+    contains: [
+      {
+        className: 'json-key', // 键名使用自定义类名
+        begin: /"(\\.|[^\\"\r\n])*"(?=\s*:)/, // 匹配键名(后跟冒号的字符串)
+        relevance: 0
+      },
+      hljs.QUOTE_STRING_MODE, // 默认的字符串规则(用于值中的字符串)
+      hljs.NUMBER_MODE,
+      {
+        className: 'literal',
+        begin: /\b(true|false|null)\b/
+      }
+      // 可根据需要添加其他规则
+    ]
+  }
+})
+
+// 高亮函数
+const highlightCode = () => {
+  if (codeBlock.value) {
+    hljs.highlightElement(codeBlock.value)
+  }
+}
+
+onMounted(highlightCode)
+watch(() => [props.language, formattedCode.value], highlightCode)
+</script>
+
+<style scoped>
+/* 新增换行保留样式 */
+pre {
+  white-space: pre-wrap; /* 允许自动换行 */
+  word-wrap: break-word; /* 长单词换行 */
+  width: 100%;
+  border-radius: 6px;
+  margin: 0;
+  padding: 8px 0 22px 16px;
+}
+:deep(.language-json) {
+  color: #f0f1f3;
+}
+:deep(.hljs-json-key) {
+  color: #f0f1f3;
+}
+:deep(.hljs-string) {
+  color: #42b0d5;
+}
+:deep(.hljs-number) {
+  color: #42b0d5;
+}
+</style>

+ 333 - 0
src/views/PromptConfiguration/src/components/OutputConfiguration.vue

@@ -0,0 +1,333 @@
+<script setup lang="ts">
+import { ref } from 'vue'
+
+const outputvalue = ref('JSON')
+const editdialogVisible = ref(false)
+const isEdit = ref(false)
+const UnableSaveVisible = ref(false)
+const outputoptions = ref([
+  {
+    label:'JSON',
+    value: 'JSON'
+  }
+])
+const isSelected = ref([
+  {
+    label: '必填字段',
+    value: '必填字段'
+  },
+  {
+    label: '有条件必填',
+    value: '有条件必填'
+  }
+])
+const EditDataType = ref('')
+const EditName = ref('')
+const Editdescribe = ref('')
+const isSelectedvalue = ref('')
+const Condition = ref('')
+
+const formatList = ref([
+  {
+    name: 'can_query',
+    type: 'Boolean',
+    describe: '是否可以通过表字段查询',
+    selecttype: '必填字段',
+    requirements: ''
+  },
+  {
+    name: 'reason',
+    type: 'String',
+    describe: '分析原因',
+    selecttype: '必填字段',
+    requirements: ''
+  },
+  {
+    name: 'sql',
+    type: 'String',
+    describe: '生成的SQL(如果表字段可查询),生成SQL时必须包含WHERE条件,只保留SQL规范的内容,不需要包含SQL语句之外的其他内容',
+    selecttype: '有条件必填',
+    requirements: ''
+  },
+  {
+    name: 'reference',
+    type: 'String',
+    describe: '给出使用SQL查询结果进行替换的回复用户问题的自然语言模板',
+    selecttype: '有条件必填',
+    requirements: ''
+  },
+  {
+    name: 'response',
+    type: 'String',
+    describe: '步骤3和步骤4的情况才可以在这里赋值,其他时候留空',
+    selecttype: '有条件必填',
+    requirements: ''
+  }
+]
+)
+
+// 编辑字段
+const temporaryname = ref('')
+const handelclickedit = (item: any) => {
+  editdialogVisible.value = true
+  isEdit.value = true
+  EditName.value = item.name
+  temporaryname.value = item.name
+  EditDataType.value = item.type
+  Editdescribe.value = item.describe
+  isSelectedvalue.value = item.selecttype
+  Condition.value = item.requirements
+}
+
+// 添加字段
+const handleaddclick = () => {
+  editdialogVisible.value = true
+}
+
+// 取消保存
+const handleclickcancel = () => {
+  editdialogVisible.value = false
+  isEdit.value = false
+  EditName.value = ''
+  EditDataType.value = ''
+  Editdescribe.value = ''
+  isSelectedvalue.value = ''
+  Condition.value = ''
+  temporaryname.value = ''
+}
+
+// 保存字段
+const handleclicksave = () => {
+  if(isEdit.value) {
+    const target = formatList.value.find(obj => obj.name === temporaryname.value);
+    if(EditName.value == '' || EditDataType.value == '' || Editdescribe.value == '' || isSelectedvalue.value == '') {
+      UnableSaveVisible.value = true
+    } else {
+      if(isSelectedvalue.value == '有条件必填' && Condition.value == '') {
+        UnableSaveVisible.value = true
+      } else {
+        const newData = { name: EditName.value, type: EditDataType.value, describe: Editdescribe.value, selecttype: isSelectedvalue.value,requirements: Condition.value }
+        if (target) Object.assign(target, newData);
+        EditName.value = ''
+        EditDataType.value = ''
+        Editdescribe.value = ''
+        isSelectedvalue.value = ''
+        editdialogVisible.value = false
+        Condition.value = ''
+        temporaryname.value = ''
+      }
+    }
+  } else {
+      if(formatList.value.some(obj => Object.values(obj).includes(EditName.value))) {
+        const target = formatList.value.find(obj => obj.name === EditName.value);
+        if(EditName.value == '' || EditDataType.value == '' || Editdescribe.value == '' || isSelectedvalue.value == '') {
+        UnableSaveVisible.value = true
+      } else {
+        if(isSelectedvalue.value == '有条件必填' && Condition.value == '') {
+          UnableSaveVisible.value = true
+        } else {
+          const newData = { name: EditName.value, type: EditDataType.value, describe: Editdescribe.value, selecttype: isSelectedvalue.value,requirements: Condition.value }
+          if (target) Object.assign(target, newData);
+          EditName.value = ''
+          EditDataType.value = ''
+          Editdescribe.value = ''
+          isSelectedvalue.value = ''
+          editdialogVisible.value = false
+          Condition.value = ''
+          temporaryname.value = ''
+        }
+      }
+    } else {
+      if(EditName.value == '' || EditDataType.value == '' || Editdescribe.value == '' || isSelectedvalue.value == '') {
+        UnableSaveVisible.value = true
+      } else {
+        if(isSelectedvalue.value == '有条件必填' && Condition.value == '') {
+          UnableSaveVisible.value = true
+        } else {
+          formatList.value.push({
+            name: EditName.value,
+            type: EditDataType.value,
+            describe: Editdescribe.value,
+            selecttype: isSelectedvalue.value,
+            requirements: Condition.value
+          })
+          EditName.value = ''
+          EditDataType.value = ''
+          Editdescribe.value = ''
+          isSelectedvalue.value = ''
+          editdialogVisible.value = false
+          Condition.value = ''
+          temporaryname.value = ''
+        }
+      }
+    }
+  }
+}
+</script>
+<template>
+  <div>
+    <div class="output-title">
+      <span class="stars_red">*</span>输出格式类型
+    </div>
+    <el-select
+      v-model="outputvalue"
+      placeholder="Select"
+      style="width: 480px;margin: 4px 0 16px 0;"
+    >
+      <el-option
+        v-for="item in outputoptions"
+        :key="item.value"
+        :label="item.label"
+        :value="item.value"
+      />
+    </el-select>
+    <div>
+      <el-button class="el-button--noborder--configuration prompt-button" @click="handleaddclick">+ 添加字段</el-button>
+    </div>
+    <div class="output-item" v-for="(item, index) in formatList" :key="index">
+      <div class="output-flex">
+        <div class="output-flex-left">
+          <div class="output-item-title">{{ item.name }}</div>
+          <div class="output-item-type">{{ item.type }}</div>
+        </div>
+        <div class="output-flex-left">
+          <el-button class="el-button--blue" style="height: 24px;width: 24px;" @click="handelclickedit(item)">
+            <span class="font_family icon-icon_edit_b icon_dark"></span>
+          </el-button>
+          <el-button class="el-button--blue" style="height: 24px;width: 24px;">
+            <span class="font_family icon-icon_delete_b icon_dark"></span>
+          </el-button>
+        </div>
+      </div>
+      <div class="output-describe">{{ item.describe }}</div>
+      <div class="output-select">{{ item.selecttype }}<span class="output-select" v-if="item.selecttype === '有条件必填'">({{item.requirements}})</span></div>
+    </div>
+    <el-dialog
+      v-model="editdialogVisible"
+      title="编辑字段"
+      :modal="false"
+      :show-close="false"
+      :close-on-click-modal="false"
+      class="dialog-edit"
+      width="480"
+    > 
+      <div class="dialog-title"><span class="stars_red">*</span>字段名称</div>
+      <el-input v-model="EditName" style="margin-bottom: 16px;"></el-input>
+      <div class="dialog-title"><span class="stars_red">*</span>数据类型</div>
+      <el-input v-model="EditDataType" style="margin-bottom: 16px;"></el-input>
+      <div class="dialog-title"><span class="stars_red">*</span>描述</div>
+      <el-input type="textarea" v-model="Editdescribe" style="margin-bottom: 16px;"></el-input>
+      <div class="dialog-title"><span class="stars_red">*</span>是否必填</div>
+      <el-select
+        v-model="isSelectedvalue"
+        placeholder="Select"
+        style="margin-bottom: 16px;"
+      >
+        <el-option
+          v-for="item in isSelected"
+          :key="item.value"
+          :label="item.label"
+          :value="item.value"
+        />
+      </el-select>
+      <div v-if="isSelectedvalue === '有条件必填'" class="dialog-title"><span class="stars_red">*</span>条件要求</div>
+      <el-input v-if="isSelectedvalue === '有条件必填'"  v-model="Condition"></el-input>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button class="dialog_button" type="default" @click="handleclickcancel">
+            <span class="font_family icon-icon_return_b icon_dark" style="margin-right: 5px;"></span>Cancel
+          </el-button>
+          <el-button class="el-button--dark dialog_button" @click="handleclicksave">
+            <span class="font_family icon-icon_save_b icon_dark" style="margin-right: 5px;"></span>Save
+          </el-button>
+        </div>
+      </template>
+    </el-dialog>
+    <el-dialog v-model="UnableSaveVisible" width="480">
+      <div>Please complete all required fields.</div>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button
+            class="el-button--danger"
+            @click="UnableSaveVisible = false"
+            style="width: 100px"
+          >
+            OK
+          </el-button>
+        </div>
+      </template>
+      <template #header>
+        <div class="cancel_header">
+          <span class="iconfont_icon iconfont_warning">
+            <svg class="iconfont icon_danger" aria-hidden="true">
+              <use xlink:href="#icon-icon_fail_fill_b"></use>
+            </svg>
+          </span>
+          Unable to Save
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.stars_red {
+  color: var(--color-danger);
+}
+.output-title {
+  font-size: 12px;
+  font-weight: 400;
+  color: var(--color-neutral-2);
+}
+.prompt-button {
+  margin-bottom: 8px;
+}
+.output-item {
+  background-color: var(--color-personal-preference-bg);
+  padding: 8px;
+  border-radius: 12px;
+  margin-bottom: 8px;
+}
+.output-flex {
+  display: flex;
+  justify-content: space-between;
+}
+.output-flex-left {
+  display: flex;
+  align-items: center;
+}
+.output-item-title {
+  color: var(--color-neutral-1);
+  font-size: 14px;
+  font-weight: 700;
+  margin-right: 8px;
+}
+.output-item-type {
+  color: var(--color-white);
+  background-color: var(--color-output-type-string-bg);
+  font-size: 12px;
+  border-radius: 3px;
+  padding: 1px 4px;
+}
+.output-describe {
+  color: var(--color-neutral-2);
+  font-size: 12px;
+  margin: 6px 0;
+}
+.output-select {
+  color:var(--color-output-select-text);
+  font-size: 12px;
+}
+:deep(.dialog_button) {
+  padding: 10px 34px !important;
+  height: 40px;
+}
+:deep(.el-dialog) {
+  box-shadow: 2px 2px 12px 0px rgba(0, 0, 0, 0.25) ;
+}
+.dialog-title {
+  color: var(--color-neutral-2);
+  font-size: 12px;
+  margin-bottom: 8px;
+}
+</style>

+ 223 - 0
src/views/PromptConfiguration/src/components/PreviewTesting.vue

@@ -0,0 +1,223 @@
+<script setup lang="ts">
+import icon from '../images/icon_ai_test@2x.png'
+import CodeBlock from './CodeBlock.vue'
+import moment from 'moment'
+
+const promptValue = ref(['您是专門从事货运代理行业的SQL专家顾问...', '表名:{public.kln_ocean}...', '响应规则,请按以下步骤处理...'])
+const PromptdialogVisible = ref(false)
+const testquestion = ref(false)
+const testquestionvalue = ref('')
+const prompttext = ref('您是专门从事货运代理行业的SQL专家顾问,您是专门从事货运代理行业的SQL专家顾问,您是专门从事货运代理行业的SQL专家顾问,您是专门从事货运代理行业的SQL专家顾问您是专门从事货运代理行业的SQL专家顾问,您是专门从事货运代理行业的SQL专家顾问您是专门从事货运代理行业的SQL专家顾问,您是专门从事货运代理行业的SQL专家顾问您是专门从事货运代理行业的SQL专家顾问,您是专门从事货运代理行业的SQL专家顾问您是专门从事货运代理行业的SQL专家顾问,您是专门从事货运代理行业的SQL专家顾问。publickln_ocean}ublic.kln_oceanublic.kln_oceanublic.kln_oceanublic.kln_oceanublic.kln_oceanublic.kln_oceanublic.kln_oceanublic.kln_oceanublic.kln_oceanublic.kln_oceanublic.kln_oceanublic.kln_oceanublic.kln_oceanublic.kln_oceanublic.kln_oceanublic.kln_oceanublic.kln_oceanublic.kln_oceanublic.kln_oceanublic.kln_oceanublic.kln_oceanublic.kln_oceanublic.kln_oceanublic.kln_oceanublic.kln_oceanublic.kln_oceanublic.kln_oceanublic.kln_oceanublic.kln_oceanublic.kln_oceanublic.kln_oceanublic.kln_oceanublic.kln_oceanublic.kln_oceanublic.kln_oceanublic.kln_oceanublic.kln_oceanublic.kln_oceanublic.kln_oceanublic.kln_oceanublic.kln_oceanublic.kln_oceanublic.kln_oceanublic.kln_oceanublic.kln_oceanublic.kln_oceanublic.kln_oceanublic.kln_oceanublic.kln_oceanublic.kln_oceanublic.kln_oceanublic.kln_oceanublic.kln_oceanublic.kln_oceanublic.kln_oceanublic.kln_oceanublic.kln_oceanublic.kln_oceanublic.kln_oceanublic.kln_oceanublic.kln_oceanublic.kln_oceanublic.kln_oceanublic.kln_oceanublic.kln_oceanublic.kln_ocean 响应规则,请按以下步骤处理步骤处理步骤处理步骤处理步骤处理步骤处理步骤处理步骤处理步骤处理步骤处理步骤处理步骤处理步骤处理')
+
+const handleclickprompt = () => {
+  PromptdialogVisible.value = true
+}
+
+// 导出为txt
+const exporttxt = () => {
+  // 创建 Blob 对象
+  const blob = new Blob([prompttext.value], { type: 'text/plain' })
+   // 创建下载链接
+   const link = document.createElement('a')
+  link.href = URL.createObjectURL(blob)
+  const currentDate = moment();
+  const formattedDate = currentDate.format('YYYY-MM-DD HH:mm');
+  link.download = `${formattedDate} Prompt.txt` // 自定义文件名
+  
+  // 触发下载
+  link.click()
+  
+  // 清理内存
+  URL.revokeObjectURL(link.href)
+}
+
+const unformattedJson = ref({
+  name: "Vue3",
+  features: [{
+    name: "123",
+    text: "156"
+  }],
+  version: 3.2
+})
+
+</script>
+
+<template>
+  <div>
+    <div class="preview">
+      <div class="preview_title">
+        新的prompt预览
+        <el-button class="el-button--default" @click="handleclickprompt"><span class="font_family icon-icon_view_b icon_dark" style="margin-right: 5px;" ></span>查看完整Prompt</el-button>
+      </div>
+      <el-dialog
+        v-model="PromptdialogVisible"
+        width="1000px"
+        :modal="false"
+        align-center
+        :close-on-click-modal="false"
+      > 
+        <template #header>
+          <div class="dialog-header">
+            <div style="width: 55%;display: flex;justify-content: end;">查看完整Prmopt</div>
+            <el-button class="el-button--default" @click="exporttxt"><span class="font_family icon-icon_import_b icon_dark" style="margin-right: 5px;" ></span>导出完整日志</el-button>
+          </div>
+        </template>
+        <div class="diaolog-content">
+          {{ prompttext }}
+        </div>
+      </el-dialog>
+      <div class="preview_content" v-if="promptValue.length == 0">暂无数据</div>
+      <div class="preview_content" v-else>
+        <div class="preview_content_title">提示词摘要</div>
+        <div class="preview_content_item" v-for="item in promptValue" :key="item">{{ item }}</div>
+      </div>
+    </div>
+    <div class="test">
+      <div class="test_title">
+        测试问题
+      </div>
+      <div class="test_diabled" v-if="testquestion">
+        请设置<span class="test_disabled_bold">系统角色+表结构+响应规则+输出格式</span>后再进行测试
+      </div>
+      <div v-else>
+        <el-input type="textarea" v-model="testquestionvalue"></el-input>
+      </div>
+      <div class="test_flex">
+        <div class="test_button" :class="{opacity: testquestion}">
+          <div class="test_button_color"><img :src="icon" width="16px" /> Test with DS</div>
+        </div>
+        <div class="test_button" :class="{opacity: testquestion}">
+          <div class="test_button_color"><img :src="icon" width="16px" />Test with Claude</div>
+        </div>
+      </div>
+      <div class="test_title">
+        测试结果
+      </div>
+      <div class="test_result">
+        <!-- 暂无数据 -->
+        <CodeBlock language="json" :raw-code="JSON.stringify(unformattedJson)">
+        </CodeBlock>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.preview {
+  background: #FCF7FF;
+  padding: 16px;
+}
+.preview_title {
+  font-size: 14px;
+  font-weight: 700;
+  color: var(--color-neutral-1);
+  margin-bottom: 16px;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+.preview_content {
+  font-size: 14px;
+  font-weight: 400;
+  color: var(--color-neutral-3);
+}
+.test {
+  padding: 0 16px;
+}
+.test_title {
+  font-size: 14px;
+  font-weight: 700;
+  color: var(--color-neutral-1);
+  margin: 17px 0 8px 0;
+}
+.test_diabled {
+  height: 80px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  border-radius: 6px;
+  border: 1px solid rgba(234, 235, 237, 0.30);
+  background-color: #F4F4F4;
+  color: var(--color-neutral-3);
+  font-size: 12px;
+  font-weight: 400;
+}
+.test_disabled_bold {
+  font-weight: 700;
+  color: var(--color-neutral-3);
+  font-size: 12px;
+}
+.test_flex {
+  display: flex;
+  margin-top: 8px;
+}
+.test_button {
+  border: 2px solid transparent;
+  border-radius: 12px;
+  background-clip: padding-box, border-box;
+  background-origin: padding-box, border-box;
+  background-image: linear-gradient(to bottom, #FFF, #FFF), linear-gradient(to bottom, #FF7500, #8112FF);
+  padding: 8px;
+  margin-right: 8px;
+  cursor: pointer;
+}
+.test_button_color {
+  background: linear-gradient(90deg, var(--1-gradient-ai-robot-0, #FF7500) 1.87%, var(--1-gradient-ai-robot-100, #8112FF) 56.62%);
+  background-clip: text;
+  -webkit-background-clip: text;
+  -webkit-text-fill-color: transparent;
+}
+.test_result {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  min-height: 80px;
+  background-color: #2B2F36;
+  color: rgba(240, 241, 243, 0.30);
+  border-radius: 6px;
+}
+.opacity {
+  opacity: 0.3;
+  cursor: no-drop;
+}
+.preview_content_title {
+  font-weight: 400;
+  font-size: 14px;
+  color: var(--color-neutral-1);
+}
+.preview_content_item {
+  font-weight: 400;
+  font-size: 14px;
+  color: var(--color-neutral-2);
+  margin: 8px 0;
+}
+.preview_content_item:last-child{
+  margin-bottom: 0;
+}
+.el-button--default {
+  width: 158px;
+  height: 40px;
+  background-color: transparent;
+}
+.diaolog-content {
+  background-color: #F8F9FD;
+  padding: 8px;
+  max-height: 720px;
+  line-height: 21px; 
+  overflow-y: scroll;
+}
+:deep(.el-dialog) {
+  min-height: 800px;
+}
+.dialog-header {
+  height: 48px;
+  width: 95%;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+:deep(.el-dialog__header ) {
+  padding: 0;
+  height: 48px;
+}
+</style>

+ 134 - 0
src/views/PromptConfiguration/src/components/RespnseConfiguration.vue

@@ -0,0 +1,134 @@
+<script setup lang="ts">
+import { ref } from 'vue'
+import addimg from '../images/default_add@2x.png'
+
+interface TypeItem {
+  description: string
+}
+interface Props {
+  stepDataprops: TypeItem []
+}
+const props = withDefaults(defineProps<Props>(), {})
+
+const stepData = ref(props.stepDataprops)
+watch(
+  () => props.stepDataprops,
+  (current) => {
+    stepData.value = current
+  }
+)
+
+// 上移
+const updatastep = (index: number) => {
+  if (index > 0) {
+    const temp = stepData.value[index - 1]
+    stepData.value[index - 1] = stepData.value[index]
+    stepData.value[index] = temp
+  }
+}
+
+// 下移
+const downdatastep = (index: number) => {
+  if (index < stepData.value.length - 1) {
+    const temp = stepData.value[index + 1]
+    stepData.value[index + 1] = stepData.value[index]
+    stepData.value[index] = temp
+  }
+}
+
+// 删除
+const deletedatastep = (index: number) => {
+  stepData.value.splice(index, 1)
+}
+
+// 添加步骤
+const addstepdata = () => {
+  stepData.value.push({
+    description: ''
+  })
+}
+
+defineExpose({
+  addstepdata
+})
+</script>
+
+<template>
+  <div class="empty" v-if="stepData.length === 0">
+    <div>
+      <div><img :src="addimg" width="100" /></div>
+      <el-button @click="addstepdata" class="el-button--main">+ 添加步骤</el-button>
+    </div>
+  </div>
+  <div
+    class="detail-step-item"
+    v-else
+    v-for="(stepItem, index) in stepData"
+    :key="index"
+  >
+    <div class="regulation">
+      <div class="left-step-icon">{{ index+1 }}</div>
+      <div class="right-info">
+        <el-input v-model="stepItem.description" type="textarea" style="width: 92%;"></el-input>
+        <el-button class="el-button--noborder--configuration" style="margin-left: 4px;" @click="updatastep(index)">
+          <span class="font_family icon-icon_moveup_b icon_dark"></span>
+        </el-button>
+        <el-button @click="downdatastep(index)" class="el-button--noborder--configuration">
+          <span class="font_family icon-icon_movedown_b icon_dark"></span>
+        </el-button>
+        <el-button @click="deletedatastep(index)" class="el-button--noborder--configuration">
+          <span class="font_family icon-icon_delete_b icon_dark"></span>
+        </el-button>
+      </div>
+    </div>
+    <div v-if="index+1 !== stepData.length " class="line"></div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.empty {
+  display: flex;
+  margin-top: 30px;
+  justify-content: center;
+  height: 215px;
+}
+:deep(.el-button, .el-button.is-round) {
+  padding: 8px 15px;
+}
+.el-button--main {
+  margin-top: 10px;
+}
+.detail-step-item {
+  position: relative;
+}
+.regulation {
+  display: flex;
+}
+.left-step-icon {
+  width: 22px;
+  height: 22px;
+  margin-right: 8px;
+  border-radius: 50%;
+  background-color: var(--color-neutral-1);
+  font-size: 12px;
+  font-weight: 700;
+  text-align: center;
+  line-height: 22px;
+  color: var(--color-mode);
+}
+.line {
+  position: absolute;
+  top: 22px;
+  height: 74px;
+  margin-left: 10px;
+  border-left: 1px solid var(--color-neutral-1);
+}
+.right-info {
+  width: 100%;
+  margin-bottom: 16px;
+  display: flex;
+}
+:deep(.el-button.el-button--noborder--configuration) {
+  padding: 20px 12px;
+}
+</style>

+ 187 - 0
src/views/PromptConfiguration/src/components/TableConfiguration.vue

@@ -0,0 +1,187 @@
+<script setup lang="ts">
+import { ref } from 'vue'
+import addimg from '../images/default_add@2x.png'
+
+const headerCoulumn = ref([
+  {
+    name: '字段名称',
+    width: '13%'
+  },
+  {
+    name: '数据类型',
+    width: '13%'
+  },
+  {
+    name: '描述',
+    width: '56%'
+  },
+  {
+    name: '示例数据',
+    width: '13%'
+  },
+  {
+    name: '操作',
+    width: '5%'
+  }
+])
+const tableData = ref([
+  {
+    name: '',
+    type: '',
+    description: '',
+    exampledata: '',
+  }
+])
+
+const addNewTableColumn = () => {
+  const item = {
+    name: '',
+    type: '',
+    description: '',
+    exampledata: '',
+  }
+  tableData.value.unshift(item)
+}
+
+// 展示全部字段
+const isShowAll = ref(false)
+const clickShowAll = () => {
+  if(tableData.value.length > 3) {
+    isShowAll.value = !isShowAll.value
+  }
+}
+
+// 删除字段
+const deleteitem = (item: any) => {
+  tableData.value.splice(tableData.value.indexOf(item), 1)
+}
+
+defineExpose({
+  addNewTableColumn
+})
+</script>
+<template>
+  <div class="tableconfiguration">
+    <div class="table_header">
+      <div class="header_item" v-for="(item,index) in headerCoulumn" :key="index" :style="{width: item.width}">{{ item.name }}</div>
+    </div>
+    <div class="table_content" :class="{ 'table_content_scroll': isShowAll }">
+      <div class="empty" v-if="tableData.length === 0">
+        <div>
+          <div><img :src="addimg" width="100" /></div>
+          <el-button @click="addNewTableColumn" class="el-button--main">+ 添加字段</el-button>
+        </div>
+      </div>
+      <div v-else>
+        <div v-for="(item,index) in tableData" :key="index" class="table-content-item">
+          <div style="width: 13%;" class="table-content-item-input">
+            <el-input v-model="item.name" type="textarea" placeholder="输入字段名称"></el-input>
+          </div>
+          <div style="width: 13%;" class="table-content-item-input">
+            <el-input v-model="item.type" type="textarea" placeholder="选择数据类型"></el-input>
+          </div>
+          <div style="width: 56%;" class="table-content-item-input">
+            <el-input v-model="item.description" type="textarea" placeholder="简要描述字段用途"></el-input>
+          </div>
+          <div style="width: 13%;" class="table-content-item-input">
+            <el-input v-model="item.exampledata" type="textarea" placeholder="输入示例数据"></el-input>
+          </div>
+          <div style="width: 5%;padding-top: 8px;" class="table-content-item-input">
+            <el-button @click="deleteitem(item)" class="el-button--blue" style="height: 24px;width: 24px;">
+              <span class="font_family icon-icon_delete_b icon_dark"></span>
+            </el-button>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+  <div class="showall" @click="clickShowAll" v-if="isShowAll"><span class="font_family icon-icon_up_b icon_dark icon_down"></span></div>
+  <div class="showall" @click="clickShowAll" v-else>展示全部字段<span class="font_family icon-icon_dropdown_b icon_dark icon_down"></span></div>
+</template>
+
+<style lang="css" scoped>
+.tableconfiguration {
+  border: 1px solid var(--color-border);
+  border-radius: 6px;
+  margin-bottom: 18px;
+}
+.table_header {
+  display: flex;
+  height: 40px;
+  align-items: center;
+  justify-content: space-between;
+  border-radius: 6px 6px 0 0;
+  background-color: var(--color-table-header-bg);
+  border-bottom: 1px solid var(--color-border);
+}
+.header_item {
+  border-right: 1px solid var(--color-border);
+  display: flex;
+  height: 40px;
+  align-items: center;
+  justify-content: start;
+  padding-left: 8px;
+  font-size: 14px;
+  font-weight: 700;
+}
+.header_item:last-child {
+  border-right: none;
+}
+.table_content {
+  max-height: 215px;
+  overflow: hidden;
+}
+.table_content_scroll {
+  min-height: 224px;
+  max-height: 725px;
+  overflow: scroll;
+}
+.empty {
+  display: flex;
+  margin-top: 30px;
+  justify-content: center;
+  height: 215px;
+}
+:deep(.el-button, .el-button.is-round) {
+  padding: 8px 15px;
+}
+.el-button--main {
+  margin-top: 10px;
+}
+.table-content-item {
+  display: flex;
+  height: 72px;
+}
+.table-content-item-input {
+  padding: 4px;
+  border-right: 1px solid var(--color-border);
+  border-bottom: 1px solid var(--color-border);
+}
+.table-content-item-input:last-child {
+  border-right: none;
+}
+:deep(.el-textarea__inner) {
+  height: 64px !important;
+  box-shadow: none;
+  border: 1px solid var(--color-border);
+}
+:deep(.el-select__wrapper) {
+  height: 64px !important;
+  box-shadow: none;
+  border: 1px solid var(--color-border);
+  align-items: baseline;
+}
+.showall {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+.showall:hover {
+  color: var(--color-theme);
+  span {
+    fill: var(--color-theme);
+    color: var(--color-theme);
+  }
+  cursor: pointer;
+}
+</style>

BIN
src/views/PromptConfiguration/src/images/default_add@2x.png


BIN
src/views/PromptConfiguration/src/images/icon_ai_test@2x.png