Forráskód Böngészése

feat:新增rg,dify模型管理接口调试完成

hanyang 1 éve
szülő
commit
a7e8d41361
31 módosított fájl, 3114 hozzáadás és 350 törlés
  1. 157 0
      src/apis/rgflow-dify.js
  2. 1 0
      src/assets/image/management/model/deepseek.svg
  3. 0 0
      src/assets/image/management/model/huggingface.svg
  4. 0 0
      src/assets/image/management/model/ollama.svg
  5. 0 0
      src/assets/image/management/model/openai-api.svg
  6. 1 0
      src/assets/image/management/model/openai.svg
  7. 1 0
      src/assets/image/management/model/tongyi.svg
  8. 1 0
      src/assets/image/management/model/xinference.svg
  9. 1 1
      src/modules/common/CardItem.vue
  10. 20 21
      src/modules/common/icon-map.js
  11. 0 217
      src/modules/model-manage/MessageBody.vue
  12. 0 102
      src/modules/model-manage/ModelList.vue
  13. 47 1
      src/modules/model-manage/TopSetting.vue
  14. 44 0
      src/modules/model-manage/dify/CollapseItem.vue
  15. 444 0
      src/modules/model-manage/dify/MessageBody.vue
  16. 151 0
      src/modules/model-manage/dify/ModalAdd_1.vue
  17. 140 0
      src/modules/model-manage/dify/ModalAdd_2.vue
  18. 269 0
      src/modules/model-manage/dify/ModalAdd_3.vue
  19. 223 0
      src/modules/model-manage/dify/ModalAdd_4.vue
  20. 215 0
      src/modules/model-manage/dify/ModalAdd_5.vue
  21. 111 0
      src/modules/model-manage/dify/ModalAdd_6.vue
  22. 161 0
      src/modules/model-manage/dify/ModelList.vue
  23. 216 0
      src/modules/model-manage/dify/SystemSetting.vue
  24. 8 3
      src/modules/model-manage/ragflow/CollapseItem.vue
  25. 317 0
      src/modules/model-manage/ragflow/MessageBody.vue
  26. 143 0
      src/modules/model-manage/ragflow/ModalAdd_1.vue
  27. 103 0
      src/modules/model-manage/ragflow/ModalAdd_2.vue
  28. 168 0
      src/modules/model-manage/ragflow/ModelList.vue
  29. 157 0
      src/modules/model-manage/ragflow/SystemSetting.vue
  30. 12 3
      src/views/Model.vue
  31. 3 2
      vite.config.mjs

+ 157 - 0
src/apis/rgflow-dify.js

@@ -286,3 +286,160 @@ export function getTenantInfo() {
     method: 'get'
   });
 }
+
+// ragflow 获取已添加的模型
+export function getAddedModelsRG() {
+  return request({
+    url: `/rgflow//v1/llm/my_llms`,
+    method: 'get'
+  });
+}
+
+// ragflow 获取未添加的模型
+export function getUnaddedModelsRG() {
+  return request({
+    url: `/rgflow/v1/llm/factories`,
+    method: 'get'
+  });
+}
+
+// ragflow 获取系统模型设置
+export function getSystemModelSettingRG() {
+  return request({
+    url: '/rgflow/v1/user/tenant_info',
+    method: 'get'
+  });
+}
+
+// ragflow 更新系统配置
+export function updateSystemModelSettingRG(data) {
+  return request({
+    url: '/rgflow/v1/user/set_tenant_info',
+    method: 'post',
+    data
+  });
+}
+
+// ragflow 模型删除
+export function deleteFactoryRG({ llm_factory }) {
+  return request({
+    url: '/rgflow/v1/llm/delete_factory',
+    method: 'post',
+    data: {
+      llm_factory
+    }
+  });
+}
+// ragflow 模型子项删除
+export function deleteFactoryLLmRG({ llm_factory, llm_name }) {
+  return request({
+    url: '/rgflow/v1/llm/delete_llm',
+    method: 'post',
+    data: {
+      llm_factory,
+      llm_name
+    }
+  });
+}
+
+// ragflow 模型添加
+export function addModelRG(data) {
+  return request({
+    url: '/rgflow/v1/llm/add_llm',
+    method: 'post',
+    data
+  });
+}
+// ragflow 模型添加(openai,deepseek)
+export function updateModelRG(data) {
+  return request({
+    url: '/rgflow/v1/llm/set_api_key',
+    method: 'post',
+    data
+  });
+}
+
+// dify 获取未添加的模型
+export function getUnaddedModelsDIFY() {
+  return request({
+    url: '/dify/console/api/workspaces/current/model-providers',
+    method: 'get'
+  });
+}
+
+// dify 获取模型子项
+export function getAddModelChild(provider) {
+  return request({
+    url: `/dify/console/api/workspaces/current/model-providers/${provider}/models`,
+    method: 'get'
+  });
+}
+// dify 添加模型
+export function addModelDIFY(provider, data) {
+  return request({
+    url: `/dify/console/api/workspaces/current/model-providers/${provider}/models`,
+    method: 'post',
+    data
+  });
+}
+
+// dify 添加deepseek模型
+export function addModelDIFYDeepSeek(provider, data) {
+  return request({
+    url: `/dify/console/api/workspaces/current/model-providers/${provider}`,
+    method: 'post',
+    data
+  });
+}
+
+//dify 系统推理模型
+export function getSystemModel() {
+  return request({
+    url: `/dify/console/api/workspaces/current/models/model-types/llm`,
+    method: 'get'
+  });
+}
+//dify 获取embedding模型
+export function getEmbeddingModel() {
+  return request({
+    url: `/dify/console/api/workspaces/current/models/model-types/text-embedding`,
+    method: 'get'
+  });
+}
+// dify 获取rarank模型
+export function getRarankModel() {
+  return request({
+    url: `/dify/console/api/workspaces/current/models/model-types/rerank`,
+    method: 'get'
+  });
+}
+// dify 获取speech2text模型
+export function getSpeech2TextModel() {
+  return request({
+    url: `/dify/console/api/workspaces/current/models/model-types/speech2text`,
+    method: 'get'
+  });
+}
+//dify 获取tts模型
+export function getTtsModel() {
+  return request({
+    url: `/dify/console/api/workspaces/current/models/model-types/tts`,
+    method: 'get'
+  });
+}
+// dify 系统模型配置
+export function updateSystemModelSettingDIFY(data) {
+  return request({
+    url: `/dify/console/api/workspaces/current/default-model`,
+    method: 'post',
+    data
+  });
+}
+// dify 删除模型子项
+export function deleteModelDifyChild(provider, data) {
+  return request({
+    url: `/dify/console/api/workspaces/current/model-providers/${provider}/models`,
+    method: 'delete',
+    data
+  });
+}

+ 1 - 0
src/assets/image/management/model/deepseek.svg

@@ -0,0 +1 @@
+<svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="200" height="200"><path d="M320.512 804.864c-274.432-128-242.688-530.432 41.984-530.432 34.816 0 86.016-7.168 114.688-14.336 59.392-16.384 99.328-10.24 69.632 10.24-9.216 7.168-15.36 19.456-13.312 28.672 5.12 20.48 158.72 161.792 177.152 161.792 27.648 0 27.648-32.768 1.024-57.344-43.008-38.912-55.296-90.112-35.84-141.312l9.216-26.624 54.272 52.224c35.84 34.816 58.368 49.152 68.608 44.032 9.216-4.096 30.72-9.216 49.152-12.288 18.432-2.048 38.912-10.24 45.056-18.432 19.456-23.552 43.008-17.408 35.84 9.216-3.072 12.288-6.144 27.648-6.144 34.816 0 23.552-62.464 83.968-92.16 90.112-23.552 5.12-30.72 12.288-30.72 30.72 0 46.08-38.912 148.48-75.776 198.656l-37.888 51.2 36.864 15.36c56.32 23.552 40.96 41.984-37.888 43.008-43.008 1.024-75.776 7.168-92.16 18.432-68.608 45.056-198.656 50.176-281.6 12.288zm251.904-86.016c-24.576-27.648-66.56-79.872-93.184-117.76-69.632-98.304-158.72-150.528-256-150.528-37.888 0-38.912 1.024-38.912 34.816 0 94.208 99.328 240.64 175.104 257.024 38.912 9.216 59.392-7.168 39.936-29.696-7.168-9.216-10.24-23.552-6.144-31.744 5.12-14.336 9.216-14.336 38.912 1.024 18.432 9.216 50.176 29.696 69.632 45.056 35.84 27.648 58.368 37.888 96.256 39.936 14.336 1.024 9.216-10.24-25.6-48.128zM660.48 573.44c8.192-13.312-31.744-78.848-56.32-92.16-10.24-6.144-26.624-10.24-34.816-10.24-23.552 0-20.48 27.648 4.096 33.792 13.312 3.072 20.48 14.336 20.48 29.696 0 13.312 5.12 29.696 12.288 36.864 15.36 15.36 46.08 16.384 54.272 2.048z" fill="#4D6BFE"/></svg>

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 0 - 0
src/assets/image/management/model/huggingface.svg


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 0 - 0
src/assets/image/management/model/ollama.svg


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 0 - 0
src/assets/image/management/model/openai-api.svg


+ 1 - 0
src/assets/image/management/model/openai.svg

@@ -0,0 +1 @@
+<svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="200" height="200"><path d="M950.676 419.057a255.347 255.347 0 0 0-22.015-209.482A257.95 257.95 0 0 0 650.915 85.848a258.76 258.76 0 0 0-438.377 92.539A255.347 255.347 0 0 0 41.966 302.114a257.95 257.95 0 0 0 31.7 302.79 255.133 255.133 0 0 0 21.758 209.525 258.163 258.163 0 0 0 277.96 123.727 255.347 255.347 0 0 0 192.373 85.84 258.376 258.376 0 0 0 246.26-179.446 255.56 255.56 0 0 0 170.53-123.727 258.376 258.376 0 0 0-31.871-301.766zM565.757 957.013a190.966 190.966 0 0 1-122.703-44.37l6.016-3.456 203.893-117.67a33.918 33.918 0 0 0 16.725-29.054v-287.43l86.182 49.832a3.03 3.03 0 0 1 1.621 2.219v238.195a192.16 192.16 0 0 1-191.734 191.734zM153.62 780.98a190.71 190.71 0 0 1-22.826-128.59l6.058 3.626 204.065 117.71a32.894 32.894 0 0 0 33.278 0L623.482 629.99v99.494a3.413 3.413 0 0 1-1.408 2.645L415.578 851.206a191.99 191.99 0 0 1-261.96-70.226zM99.86 336.928a191.35 191.35 0 0 1 100.944-84.177v242.206a32.681 32.681 0 0 0 16.554 28.841l248.094 143.14-86.182 49.832a3.242 3.242 0 0 1-3.03 0L170.173 597.907a192.16 192.16 0 0 1-70.31-262.003zM807.963 501.4 559.102 356.895l85.968-49.661a3.242 3.242 0 0 1 3.03 0L854.169 426.31a191.734 191.734 0 0 1-28.841 345.796v-242.25a33.705 33.705 0 0 0-17.365-28.456zm85.756-128.975-6.016-3.627-203.68-118.692a33.108 33.108 0 0 0-33.491 0L401.456 393.842v-99.536a2.816 2.816 0 0 1 1.195-2.602l206.07-118.906a191.99 191.99 0 0 1 284.998 198.816zM354.44 548.842l-86.182-49.661a3.413 3.413 0 0 1-1.621-2.432V259.236a191.99 191.99 0 0 1 314.65-147.32l-6.058 3.413-203.851 117.668a33.918 33.918 0 0 0-16.767 29.055zm46.803-100.901 111.013-63.997 111.226 63.997v127.95l-110.8 63.997-111.226-63.996z"/></svg>

+ 1 - 0
src/assets/image/management/model/tongyi.svg

@@ -0,0 +1 @@
+<svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="200" height="200"><path d="M918.61 591.872 813.937 408.678l44.85-85.995a23.92 23.92 0 0 0 5.244-29.9l-57.59-103.957a24.678 24.678 0 0 0-20.173-11.223H572.416L524.554 93.86a22.446 22.446 0 0 0-17.203-11.96H394.465a23.92 23.92 0 0 0-20.193 11.96v4.505L267.366 281.54H164.884a23.941 23.941 0 0 0-20.93 11.223L84.89 397.414a26.931 26.931 0 0 0 0 23.962l106.168 184.668-47.841 83.763a26.931 26.931 0 0 0 0 23.9l54.579 95.724a24.678 24.678 0 0 0 20.93 11.96H432.58l51.589 89.703a24.678 24.678 0 0 0 18.698 11.98h121.119a23.92 23.92 0 0 0 20.193-11.96l105.431-184.688h94.208a26.194 26.194 0 0 0 20.193-12.698l54.58-96.46a23.183 23.183 0 0 0 0-25.437zm-132.342 11.96L731.69 502.866l-224.317 395.55-61.318-100.925H221.737l53.842-97.956H389.98L165.663 305.5h117.391l111.411-198.902 56.075 97.956-57.57 100.926h448.615l-56.791 100.229L897.7 603.832H786.289z" fill="#605BEC"/><path d="m502.129 641.946 139.837-224.297H361.554l140.575 224.297z" fill="#605BEC"/></svg>

+ 1 - 0
src/assets/image/management/model/xinference.svg

@@ -0,0 +1 @@
+<svg data-name="图层 1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 283.46 283.46"><defs><linearGradient id="a" data-name="未命名的渐变 5" x1="27.03" y1="287.05" x2="253.15" y2="1.14" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#e9a85e"/><stop offset="1" stop-color="#f52b76"/></linearGradient><linearGradient id="b" data-name="未命名的渐变 5" x1="25.96" y1="286.21" x2="252.09" y2=".3" xlink:href="#a"/><linearGradient id="c" data-name="未命名的渐变 9" x1="-474.33" y1="476.58" x2="-160.37" y2="476.58" gradientTransform="rotate(33.75 459.64 1064.864)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#6a0cf5"/><stop offset="1" stop-color="#ab66f3"/></linearGradient></defs><path d="M96.16 145.42c7.71 8.57 16.96 16.66 27.5 23.7 8.96 5.99 18.29 10.89 27.66 14.64a209.64 209.64 0 0 0 43.09-57.84L257.92 0 147.08 87.19a209.703 209.703 0 0 0-50.92 58.23Z" style="fill:url(#a)"/><path d="M87.08 223.86a297.38 297.38 0 0 1-22.59-16.7L26 283.47l69.17-54.4c-2.71-1.69-5.41-3.41-8.09-5.2Z" style="fill:url(#b)"/><path d="M229.81 101.27c20.81 27.65 26.93 58.36 12.79 79.51-20.63 30.88-76.59 29.69-124.98-2.64C69.23 145.8 46.73 94.56 67.37 63.68c14.14-21.15 44.86-27.25 78.36-18.6C87.76 20.46 32.59 22.86 11.22 54.85c-26.86 40.19 9.8 111.82 81.88 159.99 72.08 48.17 152.29 54.64 179.15 14.45 21.38-31.99 2.49-83.88-42.44-128.02Z" style="fill:url(#c)"/></svg>

+ 1 - 1
src/modules/common/CardItem.vue

@@ -2,7 +2,7 @@
   <div class="card-container" :style="{ height: `${height}px` }">
     <div class="card-name">
       <img v-if="!is_model" :src="img_src" class="logo" @click.stop="handleClickIcon" />
-      <img v-if="is_model" :src="getIconModel(img_src)" class="logo" @click.stop="handleClickIcon" />
+      <img v-if="is_model" :src="getIconModel(name)" class="logo" @click.stop="handleClickIcon" />
       <div class="name">{{ name }}</div>
     </div>
     <div class="card-label">

+ 20 - 21
src/modules/common/icon-map.js

@@ -67,16 +67,13 @@ import intellFrame66 from '../../assets/image/management/intellFrame66.png';
 import intellFrame67 from '../../assets/image/management/intellFrame67.png';
 import intellFrame68 from '../../assets/image/management/intellFrame68.png';
 
-import modelFrame1 from '../../assets/image/management/modelFrame1.svg';
-import modelFrame2 from '../../assets/image/management/modelFrame2.svg';
-import modelFrame3 from '../../assets/image/management/modelFrame3.svg';
-import modelFrame4 from '../../assets/image/management/modelFrame4.svg';
-import modelFrame5 from '../../assets/image/management/modelFrame5.svg';
-import modelFrame6 from '../../assets/image/management/modelFrame6.svg';
-import modelFrame7 from '../../assets/image/management/modelFrame7.svg';
-import modelFrame8 from '../../assets/image/management/modelFrame8.svg';
-import modelFrame9 from '../../assets/image/management/modelFrame9.svg';
-import modelFrame10 from '../../assets/image/management/modelFrame10.svg';
+import xinference from '../../assets/image/management/model/xinference.svg';
+import openaiApi from '../../assets/image/management/model/openai-api.svg';
+import tongyi from '../../assets/image/management/model/tongyi.svg';
+import huggingface from '../../assets/image/management/model/huggingface.svg';
+import deepseek from '../../assets/image/management/model/deepseek.svg';
+import ollama from '../../assets/image/management/model/ollama.svg';
+import openai from '../../assets/image/management/model/openai.svg';
 
 const image_knowLedge_map = {
   knowledgeFrame1: knowledgeFrame1,
@@ -150,17 +147,19 @@ const icon_intelligent_map = {
   intellFrame67: intellFrame67,
   intellFrame68: intellFrame68
 };
+
 const image_model_map = {
-  modelFrame1: modelFrame1,
-  modelFrame2: modelFrame2,
-  modelFrame3: modelFrame3,
-  modelFrame4: modelFrame4,
-  modelFrame5: modelFrame5,
-  modelFrame6: modelFrame6,
-  modelFrame7: modelFrame7,
-  modelFrame8: modelFrame8,
-  modelFrame9: modelFrame9,
-  modelFrame10: modelFrame10
+  'Tongyi-Qianwen': tongyi,
+  HuggingFace: huggingface,
+  'OpenAI-API-Compatible': openaiApi,
+  Xinference: xinference,
+  OpenAI: openai,
+  DeepSeek: deepseek,
+  Ollama: ollama,
+  'Hugging Face Model': huggingface,
+  deepseek: deepseek,
+  'OpenAI-API-compatible': openaiApi,
+  'Xorbits Inference': xinference
 };
 
 /**
@@ -177,5 +176,5 @@ export const getIconIntelligent = (avatar) => {
   return icon_intelligent_map[avatar] || intellFrame1;
 };
 export const getIconModel = (avatar) => {
-  return image_model_map[avatar] || modelFrame1;
+  return image_model_map[avatar] || openaiApi;
 };

+ 0 - 217
src/modules/model-manage/MessageBody.vue

@@ -1,217 +0,0 @@
-<template>
-  <div class="model-body">
-    <div class="body-item">
-      <div class="item-title">
-        <div class="title">{{ $t('management.addedModel') }}</div>
-        <div class="icon" @click="changeStatusAdd">
-          <template v-if="added_show_status"> {{ $t('management.upBtn') }} <icon-up /> </template>
-          <template v-if="!added_show_status"> {{ $t('management.downBtn') }} <icon-down /> </template>
-        </div>
-      </div>
-      <transition name="expand-collapse" @before-enter="beforeEnter" @enter="enter" @leave="leave">
-        <div v-if="added_show_status">
-          <ModelList v-for="item in added_data" :key="item.id" :model_item="item" @showMore="showMoreModel"></ModelList>
-        </div>
-      </transition>
-    </div>
-    <div class="body-item">
-      <div class="item-title">
-        <div class="title">{{ $t('management.unaddModel') }}</div>
-        <div class="icon" @click="changeStatusUnAdd">
-          <template v-if="unadd_show_status"> {{ $t('management.upBtn') }} <icon-up /> </template>
-          <template v-if="!unadd_show_status"> {{ $t('management.downBtn') }} <icon-down /> </template>
-        </div>
-      </div>
-      <transition name="expand-collapse" @before-enter="beforeEnter" @enter="enter" @leave="leave">
-        <div v-if="unadd_show_status" class="card-list">
-          <CardItem v-for="item in un_added_data" :key="item.id" :name="item.name" :img_src="item.img_src" is_model>
-            <template #labels>
-              <div class="labels">{{ item.keyword }}</div>
-            </template>
-            <template #description>
-              <div class="desc">{{ item.desc }}</div>
-            </template>
-            <template #bottom>
-              <a-button>
-                <template #icon> <icon-plus /> </template>{{ $t('management.addModelBtn') }}</a-button
-              ></template
-            >
-          </CardItem>
-        </div>
-      </transition>
-    </div>
-  </div>
-</template>
-
-<script setup>
-import { ref } from 'vue';
-import ModelList from './ModelList.vue';
-import CardItem from '../common/CardItem.vue';
-import { useCollapse } from './use-collapse';
-
-const { beforeEnter, enter, leave } = useCollapse(1000);
-
-const added_data = ref([
-  {
-    id: '1',
-    name: 'OpenAl-APl-Compatible',
-    desc: 'LLM,TEXT EMBEDDING,SPEECH2TEXT,MODERATION ',
-    img_src: 'modelFrame1',
-    show_more: false,
-    models: [
-      {
-        id: '1-1',
-        name: 'qwen-2.5-72b-instruct OpenAl-APl'
-      },
-      {
-        id: '1-2',
-        name: 'qwen-2.5-72b-instruct-gptq-int4 OpenAI-API'
-      }
-    ]
-  },
-  {
-    id: '2',
-    name: 'Tongyi-Qianwen',
-    desc: 'LLM,TEXT EMBEDDING,SPEECH2TEXT,MODERATION',
-    img_src: 'modelFrame2',
-    show_more: false,
-    models: [
-      {
-        id: '2-1',
-        name: 'qwen-plus'
-      },
-      {
-        id: '2-2',
-        name: 'qwen-turbo'
-      },
-      {
-        id: '2-3',
-        name: 'qwen-vl-plus'
-      }
-    ]
-  },
-  {
-    id: '3',
-    name: 'Xinference',
-    desc: 'LLM,TEXT EMBEDDING,SPEECH2TEXT,MODERATION,TEXT RE-RANK',
-    img_src: 'modelFrame3',
-    show_more: false,
-    models: [
-      {
-        id: '3-1',
-        name: 'basic-bge-large-zh-v1.5'
-      }
-    ]
-  }
-]);
-const un_added_data = ref([
-  {
-    id: '1',
-    name: 'OpenAl',
-    keyword: 'LLM,TEXT',
-    desc: 'EMBEDDING,SPEECH2TEXT,MODERATION',
-    img_src: 'modelFrame4'
-  },
-  {
-    id: '2',
-    name: 'Moonshot',
-    keyword: 'LLM,TEXT EMBEDDING',
-    desc: '',
-    img_src: 'modelFrame5'
-  },
-  {
-    id: '3',
-    name: 'ZHIPU-AI',
-    keyword: 'LLM,TEXT',
-    desc: 'EMBEDDING,SPEECH2TEXT,MODERATION',
-    img_src: 'modelFrame6'
-  },
-  {
-    id: '4',
-    name: 'BaiChuan',
-    keyword: 'LLM,TEXT EMBEDDING',
-    desc: '',
-    img_src: 'modelFrame7'
-  },
-  {
-    id: '5',
-    name: 'DeepSeek',
-    keyword: 'LLM',
-    desc: '',
-    img_src: 'modelFrame8'
-  },
-  {
-    id: '6',
-    name: 'Mistral',
-    keyword: 'LLM,TEXT EMBEDDING',
-    desc: '',
-    img_src: 'modelFrame9'
-  },
-  {
-    id: '7',
-    name: 'StepFun',
-    keyword: 'LLM',
-    desc: '',
-    img_src: 'modelFrame10'
-  }
-]);
-// 展开收起状态
-const added_show_status = ref(true);
-const unadd_show_status = ref(true);
-
-const changeStatusAdd = () => {
-  added_show_status.value = !added_show_status.value;
-  if (!added_show_status.value) {
-    added_data.value.forEach((item) => {
-      item.show_more = false;
-    });
-  }
-};
-const changeStatusUnAdd = () => {
-  unadd_show_status.value = !unadd_show_status.value;
-};
-
-const showMoreModel = (item) => {
-  const index = added_data.value.findIndex((u) => u.id === item.id);
-  if (index !== -1) {
-    added_data.value[index].show_more = !added_data.value[index].show_more;
-  }
-};
-</script>
-<style scoped lang="css">
-.model-body {
-  height: calc(100% - 80px);
-  overflow-y: auto;
-}
-.model-body::-webkit-scrollbar {
-  display: none;
-}
-.body-item {
-  margin-bottom: 50px;
-}
-
-.item-title {
-  display: flex;
-  flex-direction: row;
-  justify-content: space-between;
-  margin-bottom: 20px;
-  cursor: pointer;
-}
-.title {
-  font-size: 16px;
-  font-weight: bold;
-  color: var(--color-text-1);
-}
-.card-list {
-  display: grid;
-  grid-template-columns: repeat(5, 1fr);
-  gap: 20px;
-  transition: all 0.3s ease;
-  .labels {
-    color: var(--color-text-3);
-  }
-  .desc {
-    font-size: 14px;
-  }
-}
-</style>

+ 0 - 102
src/modules/model-manage/ModelList.vue

@@ -1,102 +0,0 @@
-<template>
-  <div class="model-list">
-    <div class="list-header">
-      <div class="list-info">
-        <img class="icon" :src="getIconModel(model_item.img_src)" alt="" />
-        <div class="info-desc">
-          <div class="name">{{ model_item.name }}</div>
-          <div class="desc">{{ model_item.desc }}</div>
-        </div>
-      </div>
-      <div class="list-tools">
-        <a-space>
-          <a-button @click="handlerAddModel">
-            <template #icon>
-              <icon-plus />
-            </template>
-            {{ $t('management.addModelBtn') }}
-          </a-button>
-          <a-button @click="showMoreModel(model_item)">
-            {{ $t('management.showMoreModelBtn') }}
-            <template v-if="model_item.show_more">
-              <icon-down />
-            </template>
-            <template v-if="!model_item.show_more">
-              <icon-up />
-            </template>
-          </a-button>
-          <icon-delete class="delete-icon" @click="handlerDeleteModel" />
-        </a-space>
-      </div>
-    </div>
-    <transition name="expand-collapse" @before-enter="beforeEnter" @enter="enter" @leave="leave">
-      <div v-if="model_item.show_more" class="list-collapse">
-        <CollapseItem v-for="item in model_item.models" :key="item.id" :models="item" />
-      </div>
-    </transition>
-  </div>
-</template>
-
-<script setup>
-import CollapseItem from './CollapseItem.vue';
-import { useCollapse } from './use-collapse';
-import { getIconModel } from '../common/icon-map';
-
-const props = defineProps({
-  model_item: {
-    request: true,
-    type: Object
-  }
-});
-const emit = defineEmits(['showMore']);
-
-const { beforeEnter, enter, leave } = useCollapse(500);
-
-const handlerAddModel = () => {};
-const handlerDeleteModel = () => {};
-const showMoreModel = (item) => {
-  emit('showMore', item);
-};
-</script>
-<style scoped lang="css">
-.model-list {
-  padding: 20px 30px;
-  margin-top: 10px;
-  background-color: var(--color-bg-2);
-}
-.list-header {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-}
-.list-info {
-  display: flex;
-  flex-direction: row;
-  align-items: center;
-  gap: 20px;
-  .icon {
-    width: 44px;
-  }
-}
-.info-desc {
-  display: flex;
-  flex-direction: column;
-  gap: 10px;
-}
-.name {
-  font-size: 16px;
-  font-weight: bold;
-}
-.desc {
-  color: var(--color-text-3);
-}
-.delete-icon {
-  font-size: 20px;
-  color: rgb(var(--orange-5));
-  cursor: pointer;
-}
-.list-collapse {
-  padding: 20px 20px 0 20px;
-  max-height: 0;
-}
-</style>

+ 47 - 1
src/modules/model-manage/TopSetting.vue

@@ -1,5 +1,12 @@
 <template>
   <a-space size="large">
+    <a-select
+      :style="{ width: '200px', 'background-color': 'var(--color-bg-5)' }"
+      v-model="target_select"
+      @change="handleChange"
+    >
+      <a-option v-for="item in select_options" :key="item.value" :value="item.value">{{ item.label }}</a-option>
+    </a-select>
     <a-button type="primary" size="large" @click="handleCreate">
       <template #icon>
         <icon-settings />
@@ -7,11 +14,50 @@
       {{ $t('management.setModel') }}
     </a-button>
   </a-space>
+  <SystemSettingRG v-if="show_rg_modal" :show_modal="show_rg_modal" @ragClose="ragClose"></SystemSettingRG>
+  <SystemSettingDify v-if="show_dify_modal" :show_modal="show_dify_modal" @difyClose="difyClose"></SystemSettingDify>
 </template>
 
 <script setup>
+import { ref } from 'vue';
+import SystemSettingRG from './ragflow/SystemSetting.vue';
+import SystemSettingDify from './dify/SystemSetting.vue';
+
+const emit = defineEmits(['changeType']);
+const select_options = ref([
+  {
+    label: '助手',
+    value: 'assistant'
+  },
+  {
+    label: '智能体',
+    value: 'agent'
+  }
+]);
+
+const target_select = ref('assistant');
+const show_rg_modal = ref(false);
+const show_dify_modal = ref(false);
+
+const handleChange = (value) => {
+  emit('changeType', value);
+};
 // 系统模型设置
-const handleCreate = () => {};
+const handleCreate = () => {
+  if (target_select.value === 'assistant') {
+    show_rg_modal.value = true;
+  }
+  if (target_select.value === 'agent') {
+    show_dify_modal.value = true;
+  }
+};
+
+const ragClose = () => {
+  show_rg_modal.value = false;
+};
+const difyClose = () => {
+  show_dify_modal.value = false;
+};
 </script>
 <style scoped lang="scss"></style>
 <style></style>

+ 44 - 0
src/modules/model-manage/dify/CollapseItem.vue

@@ -0,0 +1,44 @@
+<template>
+  <div class="collapse-item">
+    <div class="name">{{ models.model }}</div>
+    <a-tag>{{ models.model_type }}</a-tag>
+    <!-- <div class="name">{{ models.type }}</div> -->
+    <!-- <a-button type="primary" @click="toUse">{{ $t('management.useBtn') }}</a-button> -->
+    <icon-delete class="delete-icon" @click="toDelete(models)" />
+  </div>
+</template>
+
+<script setup>
+const props = defineProps({
+  models: {
+    type: Object,
+    required: true
+  }
+});
+const emit = defineEmits(['toDeleteLLm']);
+
+const toUse = () => {};
+const toDelete = (name) => {
+  emit('toDeleteLLm', name);
+};
+</script>
+
+<style lang="css" scoped>
+.collapse-item {
+  display: flex;
+  align-items: center;
+  gap: 50px;
+  padding: 10px 30px;
+}
+.name {
+  min-width: 350px;
+  font-size: 14px;
+  font-weight: bold;
+}
+
+.delete-icon {
+  font-size: 20px;
+  color: rgb(var(--orange-5));
+  cursor: pointer;
+}
+</style>

+ 444 - 0
src/modules/model-manage/dify/MessageBody.vue

@@ -0,0 +1,444 @@
+<template>
+  <div class="model-body">
+    <div class="body-item">
+      <div class="item-title">
+        <div class="title">{{ $t('management.addedModel') }}</div>
+        <div class="icon" @click="changeStatusAdd">
+          <template v-if="added_show_status"> {{ $t('management.upBtn') }} <icon-up /> </template>
+          <template v-if="!added_show_status"> {{ $t('management.downBtn') }} <icon-down /> </template>
+        </div>
+      </div>
+      <transition name="expand-collapse" @before-enter="beforeEnter" @enter="enter" @leave="leave">
+        <div v-if="added_show_status">
+          <ModelList
+            v-for="(item, idx) in dify_added_data"
+            :key="idx"
+            :model_item="item"
+            :model_name="item.label.en_US"
+            @showMore="showMoreModel"
+            @updateAddModelDify="updateModelList"
+            @toAddModel="toAddModel"
+            >{{ (key, index) }}</ModelList
+          >
+          <a-empty v-if="!dify_added_data.length" class="empty-list"></a-empty>
+        </div>
+      </transition>
+    </div>
+    <div class="body-item">
+      <div class="item-title">
+        <div class="title">{{ $t('management.unaddModel') }}</div>
+        <div class="icon" @click="changeStatusUnAdd">
+          <template v-if="unadd_show_status"> {{ $t('management.upBtn') }} <icon-up /> </template>
+          <template v-if="!unadd_show_status"> {{ $t('management.downBtn') }} <icon-down /> </template>
+        </div>
+      </div>
+      <transition name="expand-collapse" @before-enter="beforeEnter" @enter="enter" @leave="leave">
+        <div v-if="unadd_show_status" class="card-list">
+          <CardItem
+            v-for="item in dify_unadded_data"
+            :key="item.id"
+            :name="item.label.en_US"
+            :img_src="item.img_src"
+            is_model
+          >
+            <template #labels>
+              <div class="labels">{{ item.supported_model_types.join(',') }}</div>
+            </template>
+            <template #description>
+              <div class="desc">{{ item.description && item.description.zh_Hans }}</div>
+            </template>
+            <template #bottom>
+              <a-button @click="handleAddModel(item)">
+                <template #icon> <icon-plus /> </template>{{ $t('management.addModelBtn') }}</a-button
+              ></template
+            >
+          </CardItem>
+        </div>
+      </transition>
+      <a-empty v-if="!dify_unadded_data.length" class="empty-list"></a-empty>
+    </div>
+  </div>
+  <ModalAdd_1
+    v-if="show_Xinference_model"
+    :show_modal="show_Xinference_model"
+    :model_data="Xinference_data"
+    @difyClose="closeXinference"
+    @updateModelList="updateModelList"
+  ></ModalAdd_1>
+  <ModalAdd_2
+    v-if="show_Openai_model"
+    :show_modal="show_Openai_model"
+    :model_data="Openai_data"
+    @difyClose="closeOpenAi"
+    @updateModelList="updateModelList"
+  ></ModalAdd_2>
+  <ModalAdd_3
+    v-if="show_Openai_api_compatible"
+    :show_modal="show_Openai_api_compatible"
+    :model_data="Openai_api_compatible_data"
+    @difyClose="closeOpenAiComatible"
+    @updateModelList="updateModelList"
+  ></ModalAdd_3>
+  <ModalAdd_4
+    v-if="show_Huggingface"
+    :show_modal="show_Huggingface"
+    :model_data="Huggingface_data"
+    @difyClose="closeHuggingface"
+    @updateModelList="updateModelList"
+  ></ModalAdd_4>
+  <ModalAdd_5
+    v-if="show_Ollama"
+    :show_modal="show_Ollama"
+    :model_data="Ollama_data"
+    @difyClose="closeOllama"
+    @updateModelList="updateModelList"
+  ></ModalAdd_5>
+  <ModalAdd_6
+    v-if="show_deepseek"
+    :show_modal="show_deepseek"
+    :model_data="Deepseek_data"
+    @difyClose="closeDeepseek"
+    @updateModelList="updateModelList"
+  ></ModalAdd_6>
+</template>
+
+<script setup>
+import { ref, onMounted } from 'vue';
+import ModelList from './ModelList.vue';
+import CardItem from '../../common/CardItem.vue';
+import { useCollapse } from '../use-collapse';
+import { getAddedModelsRG, getUnaddedModelsDIFY, getAddModelChild } from '../../../apis/rgflow-dify';
+import ModalAdd_1 from './ModalAdd_1.vue';
+import ModalAdd_2 from './ModalAdd_2.vue';
+import ModalAdd_3 from './ModalAdd_3.vue';
+import ModalAdd_4 from './ModalAdd_4.vue';
+import ModalAdd_5 from './ModalAdd_5.vue';
+import ModalAdd_6 from './ModalAdd_6.vue';
+
+const { beforeEnter, enter, leave } = useCollapse(1000);
+
+const added_data = ref([
+  // {
+  //   id: '1',
+  //   name: 'OpenAl-APl-Compatible',
+  //   desc: 'LLM,TEXT EMBEDDING,SPEECH2TEXT,MODERATION ',
+  //   img_src: 'modelFrame1',
+  //   show_more: false,
+  //   models: [
+  //     {
+  //       id: '1-1',
+  //       name: 'qwen-2.5-72b-instruct OpenAl-APl'
+  //     },
+  //     {
+  //       id: '1-2',
+  //       name: 'qwen-2.5-72b-instruct-gptq-int4 OpenAI-API'
+  //     }
+  //   ]
+  // },
+  // {
+  //   id: '2',
+  //   name: 'Tongyi-Qianwen',
+  //   desc: 'LLM,TEXT EMBEDDING,SPEECH2TEXT,MODERATION',
+  //   img_src: 'modelFrame2',
+  //   show_more: false,
+  //   models: [
+  //     {
+  //       id: '2-1',
+  //       name: 'qwen-plus'
+  //     },
+  //     {
+  //       id: '2-2',
+  //       name: 'qwen-turbo'
+  //     },
+  //     {
+  //       id: '2-3',
+  //       name: 'qwen-vl-plus'
+  //     }
+  //   ]
+  // },
+  // {
+  //   id: '3',
+  //   name: 'Xinference',
+  //   desc: 'LLM,TEXT EMBEDDING,SPEECH2TEXT,MODERATION,TEXT RE-RANK',
+  //   img_src: 'modelFrame3',
+  //   show_more: false,
+  //   models: [
+  //     {
+  //       id: '3-1',
+  //       name: 'basic-bge-large-zh-v1.5'
+  //     }
+  //   ]
+  // }
+]);
+const un_added_data = ref([
+  {
+    id: '1',
+    name: 'OpenAl',
+    keyword: 'LLM,TEXT',
+    desc: 'EMBEDDING,SPEECH2TEXT,MODERATION',
+    img_src: 'modelFrame4'
+  },
+  {
+    id: '2',
+    name: 'Moonshot',
+    keyword: 'LLM,TEXT EMBEDDING',
+    desc: '',
+    img_src: 'modelFrame5'
+  },
+  {
+    id: '3',
+    name: 'ZHIPU-AI',
+    keyword: 'LLM,TEXT',
+    desc: 'EMBEDDING,SPEECH2TEXT,MODERATION',
+    img_src: 'modelFrame6'
+  },
+  {
+    id: '4',
+    name: 'BaiChuan',
+    keyword: 'LLM,TEXT EMBEDDING',
+    desc: '',
+    img_src: 'modelFrame7'
+  },
+  {
+    id: '5',
+    name: 'DeepSeek',
+    keyword: 'LLM',
+    desc: '',
+    img_src: 'modelFrame8'
+  },
+  {
+    id: '6',
+    name: 'Mistral',
+    keyword: 'LLM,TEXT EMBEDDING',
+    desc: '',
+    img_src: 'modelFrame9'
+  },
+  {
+    id: '7',
+    name: 'StepFun',
+    keyword: 'LLM',
+    desc: '',
+    img_src: 'modelFrame10'
+  }
+]);
+const dify_added_data = ref({});
+const dify_unadded_data = ref([]);
+const dify_all_data = ref([]);
+// 展开收起状态
+const added_show_status = ref(true);
+const unadd_show_status = ref(true);
+
+const show_Xinference_model = ref(false);
+const Xinference_data = ref({});
+
+const show_Openai_api_compatible = ref(false);
+const Openai_api_compatible_data = ref({});
+
+const show_Openai_model = ref(false);
+const Openai_data = ref({});
+
+const show_Huggingface = ref(false);
+const Huggingface_data = ref({});
+
+const show_Ollama = ref(false);
+const Ollama_data = ref({});
+
+const show_deepseek = ref(false);
+const Deepseek_data = ref({});
+
+const changeStatusAdd = () => {
+  added_show_status.value = !added_show_status.value;
+};
+const changeStatusUnAdd = () => {
+  unadd_show_status.value = !unadd_show_status.value;
+};
+
+const showMoreModel = async (item) => {
+  if (item.showMore) {
+    dify_added_data.value.forEach((it) => {
+      if (it.provider === item.provider) {
+        it.showMore = false;
+      }
+    });
+    return;
+  }
+  const res = await getAddModelChildFun(item.provider);
+  if (res.data) {
+    dify_added_data.value.forEach((it) => {
+      if (it.provider === item.provider) {
+        it.showMore = true;
+        it.llm = res.data;
+      }
+    });
+  }
+  // dify_added_data.value[item.name].showMore = !dify_added_data.value[item.name].showMore;
+};
+
+const getAddModelChildFun = async (provide) => {
+  try {
+    const res = await getAddModelChild(provide);
+    return res;
+  } catch (e) {
+    console.log(e);
+  }
+};
+const getAddedModelsRGFun = async () => {
+  try {
+    const res = await getAddedModelsRG();
+    if (res.code / 1 === 0) {
+      for (const key in res.data) {
+        res.data[key].showMore = false;
+        res.data[key].name = key;
+      }
+      // Object.values(res.data).forEach((item) => {
+      //   item.showMore = true;
+      //   item.name =
+      // });
+      dify_added_data.value = { ...res.data };
+    }
+  } catch (e) {
+    console.log(e);
+  }
+};
+const getUnaddedModelsDIFYFun = async () => {
+  try {
+    const res = await getUnaddedModelsDIFY();
+    if (res.data) {
+      dify_all_data.value = res.data;
+      dify_unadded_data.value = res.data
+        .filter((item) => {
+          return ['openai', 'ollama', 'deepseek', 'openai_api_compatible', 'xinference', 'huggingface_hub'].includes(
+            item.provider
+          );
+        })
+        .filter((item) => item.custom_configuration.status === 'no-configure');
+      dify_added_data.value = res.data
+        .filter((item) => {
+          return ['openai', 'ollama', 'deepseek', 'openai_api_compatible', 'xinference', 'huggingface_hub'].includes(
+            item.provider
+          );
+        })
+        .filter((item) => item.custom_configuration.status === 'active')
+        .map((item) => {
+          return {
+            ...item,
+            showMore: false
+          };
+        });
+    }
+  } catch (e) {
+    console.log(e);
+  }
+};
+const updateModelList = async () => {
+  await getUnaddedModelsDIFYFun();
+};
+const toAddModel = (name) => {
+  const item = dify_all_data.value.find((it) => it.label.en_US === name);
+  handleAddModel(item);
+};
+const handleAddModel = async (item) => {
+  if (['xinference'].includes(item.provider)) {
+    show_Xinference_model.value = true;
+    Xinference_data.value = item;
+  }
+  if (['openai_api_compatible'].includes(item.provider)) {
+    show_Openai_api_compatible.value = true;
+    Openai_api_compatible_data.value = item;
+  }
+  if (['openai'].includes(item.provider)) {
+    show_Openai_model.value = true;
+    Openai_data.value = item;
+  }
+  if (['huggingface_hub'].includes(item.provider)) {
+    show_Huggingface.value = true;
+    Huggingface_data.value = item;
+  }
+  if (['ollama'].includes(item.provider)) {
+    show_Ollama.value = true;
+    Ollama_data.value = item;
+  }
+  if (['deepseek'].includes(item.provider)) {
+    show_deepseek.value = true;
+    Deepseek_data.value = item;
+  }
+
+  // if (['OpenAI-API-Compatible', 'HuggingFace', 'Xinference', 'Ollama'].includes(item.name)) {
+  //   show_Xinference_model.value = true;
+  //   Xinference_data.value = item;
+  // }
+  // if (['OpenAI', 'DeepSeek'].includes(item.name)) {
+  //   console.log(item);
+  //   show_Openai_model.value = true;
+  //   Openai_data.value = item;
+  // }
+};
+const closeXinference = () => {
+  show_Xinference_model.value = false;
+  Xinference_data.value = {};
+};
+const closeOpenAi = () => {
+  show_Openai_model.value = false;
+  Openai_data.value = {};
+};
+const closeOpenAiComatible = () => {
+  show_Openai_api_compatible.value = false;
+  Openai_api_compatible_data.value = {};
+};
+const closeHuggingface = () => {
+  show_Huggingface.value = false;
+  Huggingface_data.value = {};
+};
+const closeOllama = () => {
+  show_Ollama.value = false;
+  Ollama_data.value = {};
+};
+const closeDeepseek = () => {
+  show_deepseek.value = false;
+  Deepseek_data.value = {};
+};
+
+onMounted(() => {
+  getUnaddedModelsDIFYFun();
+});
+</script>
+<style scoped lang="css">
+.model-body {
+  height: calc(100% - 80px);
+  overflow-y: auto;
+}
+.model-body::-webkit-scrollbar {
+  display: none;
+}
+.body-item {
+  margin-bottom: 50px;
+}
+.empty-list {
+  background-color: var(--color-bg-2);
+}
+
+.item-title {
+  display: flex;
+  flex-direction: row;
+  justify-content: space-between;
+  margin-bottom: 20px;
+  cursor: pointer;
+}
+.title {
+  font-size: 16px;
+  font-weight: bold;
+  color: var(--color-text-1);
+}
+.card-list {
+  display: grid;
+  grid-template-columns: repeat(5, 1fr);
+  gap: 20px;
+  transition: all 0.3s ease;
+  .labels {
+    color: var(--color-text-3);
+  }
+  .desc {
+    font-size: 14px;
+  }
+}
+</style>

+ 151 - 0
src/modules/model-manage/dify/ModalAdd_1.vue

@@ -0,0 +1,151 @@
+<template>
+  <a-modal
+    v-model:visible="visible"
+    title-align="start"
+    :align-center="false"
+    :esc-to-close="false"
+    :ok-loading="loading"
+    top="15vh"
+    width="30vw"
+    @before-cancel="handleCancel"
+    @ok="handleOk"
+  >
+    <template #title>
+      <div class="model-title">
+        <b>添加 &nbsp;{{ props.model_data.label.en_US }}</b>
+      </div>
+    </template>
+    <div class="section">
+      <a-form :model="form" ref="formRef" :rules="rules" :layout="layout" @submit="handleSubmit">
+        <a-form-item field="model_type" label="模型类型">
+          <a-select v-model="form.model_type">
+            <a-option v-for="item in model_types" :key="item" :value="item">{{ item }}</a-option>
+          </a-select>
+        </a-form-item>
+        <a-form-item field="model" label="模型名称">
+          <a-input v-model="form.model" placeholder="请输入模型名称"></a-input>
+        </a-form-item>
+        <a-form-item field="credentials.server_url" label="服务器Url">
+          <a-input v-model="form.credentials.server_url" placeholder="请输入服务器地址"></a-input>
+        </a-form-item>
+        <a-form-item field="credentials.model_uid" label="模型UID">
+          <a-input v-model="form.credentials.model_uid" placeholder="请输入模型UID"></a-input>
+        </a-form-item>
+        <a-form-item field="api_key" label="API密钥">
+          <a-input v-model="form.credentials.api_key" placeholder="请输入API密钥"></a-input>
+        </a-form-item>
+      </a-form>
+    </div>
+  </a-modal>
+</template>
+
+<script setup>
+import { ref, computed, onMounted } from 'vue';
+import { Message } from '@arco-design/web-vue';
+import { addModelDIFY } from '../../../apis/rgflow-dify';
+const props = defineProps({
+  show_modal: Boolean,
+  model_data: Object
+});
+
+const emit = defineEmits(['difyClose', 'updateModelList']);
+
+const visible = computed(() => {
+  return props.show_modal;
+});
+
+const layout = ref('vertical');
+const loading = ref(false);
+const form = ref({
+  model_type: '',
+  model: '',
+  credentials: {
+    api_key: '',
+    model_uid: '',
+    server_url: ''
+  },
+  load_balancing: {
+    configs: [],
+    enabled: false
+  }
+});
+
+const rules = {
+  model_type: [
+    {
+      required: true,
+      message: '模型类型必填'
+    }
+  ],
+  model: [
+    {
+      required: true,
+      message: '模型名称必填'
+    }
+  ],
+  'credentials.server_url': [
+    {
+      required: true,
+      message: '服务器URL必填'
+    }
+  ],
+  'credentials.model_uid': [
+    {
+      required: true,
+      message: '模型UID必填'
+    }
+  ]
+};
+
+const model_types = computed(() => {
+  return props.model_data.supported_model_types;
+});
+
+const handleOk = async () => {
+  try {
+    if (
+      form.value.model_type &&
+      form.value.model &&
+      form.value.credentials.server_url &&
+      form.value.credentials.model_uid
+    ) {
+      loading.value = true;
+      const res = await addModelDIFYFun(props.model_data.provider, form.value);
+      if (res && res.result === 'success') {
+        Message.success('添加成功');
+        emit('updateModelList');
+        handleCancel();
+      } else {
+        Message.error(res.message);
+      }
+      loading.value = false;
+    } else {
+      Message.error('缺少必填项');
+    }
+  } catch (e) {
+  } finally {
+    loading.value = false;
+  }
+};
+const handleCancel = () => {
+  emit('difyClose');
+};
+
+const addModelDIFYFun = async (provider, data) => {
+  try {
+    const res = await addModelDIFY(provider, data);
+    return res;
+  } catch (e) {
+    console.log(e);
+    Message.error(e.response?.data?.message);
+  }
+};
+const handleSubmit = () => {};
+
+onMounted(() => {
+  if (props.model_data.supported_model_types.length) {
+    form.value.model_type = props.model_data.supported_model_types[0];
+  }
+});
+</script>
+<style scoped lang="css"></style>

+ 140 - 0
src/modules/model-manage/dify/ModalAdd_2.vue

@@ -0,0 +1,140 @@
+<template>
+  <a-modal
+    v-model:visible="visible"
+    title-align="start"
+    :align-center="false"
+    :esc-to-close="false"
+    :ok-loading="loading"
+    top="15vh"
+    width="30vw"
+    @before-cancel="handleCancel"
+    @ok="handleOk"
+  >
+    <template #title>
+      <div class="model-title">
+        <b>添加 &nbsp;{{ props.model_data.label.en_US }}</b>
+      </div>
+    </template>
+    <div class="section">
+      <a-form :model="form" ref="formRef" :rules="rules" :layout="layout" @submit="handleSubmit">
+        <a-form-item field="model_type" label="模型类型">
+          <a-select v-model="form.model_type">
+            <a-option v-for="item in model_types" :key="item" :value="item">{{ item }}</a-option>
+          </a-select>
+        </a-form-item>
+        <a-form-item field="model" label="模型名称">
+          <a-input v-model="form.model" placeholder="请输入模型名称"></a-input>
+        </a-form-item>
+        <a-form-item field="credentials.openai_api_key" label="API Key">
+          <a-input v-model="form.credentials.openai_api_key" placeholder="请输入API Key"></a-input>
+        </a-form-item>
+        <a-form-item field="credentials.openai_organization" label="组织 ID">
+          <a-input v-model="form.credentials.openai_organization" placeholder="请输入组织 ID"></a-input>
+        </a-form-item>
+        <a-form-item field="credentials.openai_api_base" label="API Base">
+          <a-input v-model="form.credentials.openai_api_base" placeholder="请输入API Base"></a-input>
+        </a-form-item>
+      </a-form>
+    </div>
+  </a-modal>
+</template>
+
+<script setup>
+import { ref, computed, onMounted } from 'vue';
+import { Message } from '@arco-design/web-vue';
+import { addModelDIFY } from '../../../apis/rgflow-dify';
+const props = defineProps({
+  show_modal: Boolean,
+  model_data: Object
+});
+
+const emit = defineEmits(['difyClose', 'updateModelList']);
+
+const visible = computed(() => {
+  return props.show_modal;
+});
+
+const layout = ref('vertical');
+const loading = ref(false);
+const form = ref({
+  model_type: '',
+  model: '',
+  credentials: {
+    openai_api_key: '',
+    openai_api_base: '',
+    openai_organization: ''
+  },
+  load_balancing: {
+    configs: [],
+    enabled: false
+  }
+});
+
+const rules = {
+  model_type: [
+    {
+      required: true,
+      message: '模型类型必填'
+    }
+  ],
+  model: [
+    {
+      required: true,
+      message: '模型名称必填'
+    }
+  ],
+  'credentials.openai_api_key': [
+    {
+      required: true,
+      message: 'API Key必填'
+    }
+  ]
+};
+
+const model_types = computed(() => {
+  return props.model_data.supported_model_types;
+});
+
+const handleOk = async () => {
+  try {
+    if (form.value.model_type && form.value.model && form.value.credentials.openai_api_key) {
+      loading.value = true;
+      const res = await addModelDIFYFun(props.model_data.provider, form.value);
+      if (res && res.result === 'success') {
+        // Message.success('添加成功');
+        emit('updateModelList');
+        handleCancel();
+      } else {
+        // Message.error(res.message);
+      }
+      loading.value = false;
+    } else {
+      Message.error('缺少必填项');
+    }
+  } catch (e) {
+  } finally {
+    loading.value = false;
+  }
+};
+const handleCancel = () => {
+  emit('difyClose');
+};
+
+const addModelDIFYFun = async (provider, data) => {
+  try {
+    const res = await addModelDIFY(provider, data);
+    return res;
+  } catch (e) {
+    console.log(e);
+    Message.error(e.response?.data?.message);
+  }
+};
+const handleSubmit = () => {};
+
+onMounted(() => {
+  if (props.model_data.supported_model_types.length) {
+    form.value.model_type = props.model_data.supported_model_types[0];
+  }
+});
+</script>
+<style scoped lang="css"></style>

+ 269 - 0
src/modules/model-manage/dify/ModalAdd_3.vue

@@ -0,0 +1,269 @@
+<template>
+  <a-modal
+    v-model:visible="visible"
+    title-align="start"
+    :align-center="false"
+    :esc-to-close="false"
+    :ok-loading="loading"
+    top="15vh"
+    width="30vw"
+    @before-cancel="handleCancel"
+    @ok="handleOk"
+  >
+    <template #title>
+      <div class="model-title">
+        <b>添加 &nbsp;{{ props.model_data.label.en_US }}</b>
+      </div>
+    </template>
+    <div class="section">
+      <a-form :model="form" ref="formRef" :rules="rules" :layout="layout" @submit="handleSubmit">
+        <a-form-item field="model_type" label="模型类型">
+          <a-select v-model="form.model_type">
+            <a-option v-for="item in model_types" :key="item" :value="item">{{ item }}</a-option>
+          </a-select>
+        </a-form-item>
+        <a-form-item field="model" label="模型名称">
+          <a-input v-model="form.model" placeholder="请输入模型名称"></a-input>
+        </a-form-item>
+        <a-form-item field="api_key" label="API Key">
+          <a-input v-model="form.credentials.api_key" placeholder="请输入API Key"></a-input>
+        </a-form-item>
+        <a-form-item field="credentials.endpoint_url" label="API endpoint URL">
+          <a-input
+            v-model="form.credentials.endpoint_url"
+            placeholder="Base URL,e.g.https://api.openai.com/v1"
+          ></a-input>
+        </a-form-item>
+        <a-form-item v-if="form.model_type === 'llm'" field="credentials.mode" label="Completion mode">
+          <a-select v-model="form.credentials.mode" allow-clear placeholder="选择对话类型">
+            <a-option v-for="item in Completion_mode_options" :key="item.value" :value="item.value">{{
+              item.label
+            }}</a-option>
+          </a-select>
+        </a-form-item>
+        <a-form-item v-if="form.model_type !== 'speech2text'" field="credentials.context_size" label="模型上下文长度">
+          <a-input v-model="form.credentials.context_size" placeholder="请输入模型上下文长度"></a-input>
+        </a-form-item>
+        <a-form-item v-if="form.model_type === 'llm'" field="credentials.max_tokens_to_sample" label="最大token上限">
+          <a-input v-model="form.credentials.max_tokens_to_sample"></a-input>
+        </a-form-item>
+        <a-form-item
+          v-if="form.model_type === 'llm'"
+          field="credentials.function_calling_type"
+          label="Function calling"
+        >
+          <a-select v-model="form.credentials.function_calling_type" allow-clear placeholder="请选择">
+            <a-option v-for="item in function_calling_options" :key="item.value" :value="item.value">{{
+              item.label
+            }}</a-option>
+          </a-select>
+        </a-form-item>
+        <a-form-item
+          v-if="form.model_type === 'llm'"
+          field="credentials.stream_function_calling"
+          label="Stream function calling"
+        >
+          <a-select v-model="form.credentials.stream_function_calling" allow-clear placeholder="请选择">
+            <a-option v-for="item in stream_function_calling_options" :key="item.value" :value="item.value">{{
+              item.label
+            }}</a-option>
+          </a-select>
+        </a-form-item>
+        <a-form-item v-if="form.model_type === 'llm'" field="credentials.vision_support" label="Vision 支持">
+          <a-select v-model="form.credentials.vision_support" allow-clear placeholder="请选择">
+            <a-option v-for="item in vision_support_options" :key="item.value" :value="item.value">{{
+              item.label
+            }}</a-option>
+          </a-select>
+        </a-form-item>
+        <a-form-item
+          v-if="form.model_type === 'llm'"
+          field="credentials.stream_mode_delimiter"
+          label="流模式返回结果的分隔符"
+        >
+          <a-input v-model="form.credentials.stream_mode_delimiter" placeholder="请输入模型UID"></a-input>
+        </a-form-item>
+      </a-form>
+    </div>
+  </a-modal>
+</template>
+
+<script setup>
+import { ref, computed, onMounted } from 'vue';
+import { Message } from '@arco-design/web-vue';
+import { addModelDIFY } from '../../../apis/rgflow-dify';
+const props = defineProps({
+  show_modal: Boolean,
+  model_data: Object
+});
+
+const emit = defineEmits(['difyClose', 'updateModelList']);
+
+const visible = computed(() => {
+  return props.show_modal;
+});
+
+const layout = ref('vertical');
+const loading = ref(false);
+const form = ref({
+  model_type: '',
+  model: '',
+  credentials: {
+    api_key: '',
+    endpoint_url: '',
+    context_size: '4096',
+    function_calling_type: 'no_call',
+    max_tokens_to_sample: '4096',
+    mode: 'chat',
+    stream_function_calling: 'not_supported',
+    stream_mode_delimiter: '\\n\\n',
+    vision_support: 'no_support'
+  },
+  load_balancing: {
+    configs: [],
+    enabled: false
+  }
+});
+
+const rules = {
+  model_type: [
+    {
+      required: true,
+      message: '模型类型必填'
+    }
+  ],
+  model: [
+    {
+      required: true,
+      message: '模型名称必填'
+    }
+  ],
+  'credentials.endpoint_url': [
+    {
+      required: true,
+      message: 'API endpoint URL必填'
+    }
+  ],
+  'credentials.context_size': [
+    {
+      required: true,
+      message: '模型上下文长度必填'
+    }
+  ],
+  'credentials.max_tokens_to_sample': [
+    {
+      required: true,
+      message: '最大token上限必填'
+    }
+  ],
+  'credentials.stream_mode_delimiter': [
+    {
+      required: true,
+      message: '流模式返回结果的分隔符必填'
+    }
+  ]
+};
+const Completion_mode_options = ref([
+  {
+    label: '补全',
+    value: 'completion'
+  },
+  {
+    label: '对话',
+    value: 'chat'
+  }
+]);
+
+const function_calling_options = ref([
+  {
+    label: 'Function Call',
+    value: 'function_call'
+  },
+  {
+    label: 'Tool Call',
+    value: 'tool_call'
+  },
+  {
+    label: '不支持',
+    value: 'no_call'
+  }
+]);
+const stream_function_calling_options = ref([
+  {
+    label: '不支持',
+    value: 'not_supported'
+  },
+  {
+    label: '支持',
+    value: 'supported'
+  }
+]);
+const vision_support_options = ref([
+  {
+    label: '不支持',
+    value: 'no_support'
+  },
+  {
+    label: '支持',
+    value: 'support'
+  }
+]);
+
+const model_types = computed(() => {
+  return props.model_data.supported_model_types;
+});
+
+const handleOk = async () => {
+  try {
+    if (
+      form.value.model_type &&
+      form.value.model &&
+      form.value.credentials.endpoint_url &&
+      form.value.credentials.context_size &&
+      form.value.credentials.max_tokens_to_sample &&
+      form.value.credentials.stream_mode_delimiter
+    ) {
+      loading.value = true;
+      const res = await addModelDIFYFun(props.model_data.provider, form.value);
+      if (res && res.result === 'success') {
+        // Message.success('添加成功');
+        emit('updateModelList');
+        handleCancel();
+      } else {
+        // Message.error(res.message);
+      }
+      loading.value = false;
+    } else {
+      Message.error('缺少必填项');
+    }
+  } catch (e) {
+  } finally {
+    loading.value = false;
+  }
+};
+const handleCancel = () => {
+  emit('difyClose');
+};
+
+const addModelDIFYFun = async (provider, data) => {
+  try {
+    const res = await addModelDIFY(provider, data);
+    return res;
+  } catch (e) {
+    console.log(e);
+    Message.error(e.response?.data?.message);
+  }
+};
+const handleSubmit = () => {};
+
+onMounted(() => {
+  if (props.model_data.supported_model_types.length) {
+    form.value.model_type = props.model_data.supported_model_types[0];
+  }
+});
+</script>
+<style scoped lang="css">
+.section {
+  max-height: 50vh;
+}
+</style>

+ 223 - 0
src/modules/model-manage/dify/ModalAdd_4.vue

@@ -0,0 +1,223 @@
+<template>
+  <a-modal
+    v-model:visible="visible"
+    title-align="start"
+    :align-center="false"
+    :esc-to-close="false"
+    :ok-loading="loading"
+    top="15vh"
+    width="30vw"
+    @before-cancel="handleCancel"
+    @ok="handleOk"
+  >
+    <template #title>
+      <div class="model-title">
+        <b>添加 &nbsp;{{ props.model_data.label.en_US }}</b>
+      </div>
+    </template>
+    <div class="section">
+      <a-form :model="form" ref="formRef" :rules="rules" :layout="layout" @submit="handleSubmit">
+        <a-form-item field="model_type" label="模型类型">
+          <a-select v-model="form.model_type">
+            <a-option v-for="item in model_types" :key="item" :value="item">{{ item }}</a-option>
+          </a-select>
+        </a-form-item>
+        <a-form-item field="model" label="模型名称">
+          <a-input v-model="form.model" placeholder="请输入模型名称"></a-input>
+        </a-form-item>
+        <a-form-item field="credentials.huggingfacehub_api_type" label="端点类型">
+          <a-select v-model="form.credentials.huggingfacehub_api_type">
+            <a-option v-for="item in credential_form_schemas" :key="item.value" :value="item.value">{{
+              item.label
+            }}</a-option>
+          </a-select>
+        </a-form-item>
+        <a-form-item field="credentials.huggingfacehub_api_token" label="API Token">
+          <a-input v-model="form.credentials.huggingfacehub_api_token" placeholder="请输入API Token"></a-input>
+        </a-form-item>
+        <a-form-item
+          v-if="form.credentials.huggingfacehub_api_type === 'inference_endpoints'"
+          field="credentials.huggingfacehub_endpoint_url"
+          label="端点 URL"
+        >
+          <a-input v-model="form.credentials.huggingfacehub_endpoint_url" placeholder="请输入端点 URL"></a-input>
+        </a-form-item>
+        <a-form-item
+          v-if="form.credentials.huggingfacehub_api_type === 'inference_endpoints'"
+          field="credentials.task_type"
+          label="Task"
+        >
+          <a-select v-model="form.credentials.task_type" placeholder="请选择">
+            <a-option v-for="item in task_types" :key="item.value" :value="item.value">{{ item.label }}</a-option>
+          </a-select>
+        </a-form-item>
+      </a-form>
+    </div>
+  </a-modal>
+</template>
+
+<script setup>
+import { ref, computed, onMounted } from 'vue';
+import { Message } from '@arco-design/web-vue';
+import { addModelDIFY } from '../../../apis/rgflow-dify';
+const props = defineProps({
+  show_modal: Boolean,
+  model_data: Object
+});
+
+const emit = defineEmits(['difyClose', 'updateModelList']);
+
+const visible = computed(() => {
+  return props.show_modal;
+});
+
+const layout = ref('vertical');
+const loading = ref(false);
+const form = ref({
+  model_type: '',
+  model: '',
+  credentials: {
+    huggingfacehub_api_type: 'hosted_inference_api',
+    huggingfacehub_api_token: '',
+    huggingfacehub_endpoint_url: '',
+    task_type: ''
+  },
+  load_balancing: {
+    configs: [],
+    enabled: false
+  }
+});
+
+const rules = {
+  model_type: [
+    {
+      required: true,
+      message: '模型类型必填'
+    }
+  ],
+  model: [
+    {
+      required: true,
+      message: '模型名称必填'
+    }
+  ],
+  'credentials.huggingfacehub_api_token': [
+    {
+      required: true,
+      message: 'API Token必填'
+    }
+  ],
+  'credentials.huggingfacehub_api_type': [
+    {
+      required: true,
+      message: '端点类型必填'
+    }
+  ],
+  'credentials.huggingfacehub_endpoint_url': [
+    {
+      required: true,
+      message: '端点URL必填'
+    }
+  ],
+  'credentials.task_type': [
+    {
+      required: true,
+      message: 'Task必填'
+    }
+  ]
+};
+const credential_form_schemas = ref([
+  {
+    label: 'Hosted Inference API',
+    value: 'hosted_inference_api'
+  },
+  {
+    label: 'Inference Endpoints',
+    value: 'inference_endpoints'
+  }
+]);
+
+const task_types = ref([
+  {
+    label: 'Text-to-Text Generation',
+    value: 'text2text-generation'
+  },
+  {
+    label: '文本生成',
+    value: 'text-generation'
+  }
+]);
+
+const model_types = computed(() => {
+  return props.model_data.supported_model_types;
+});
+
+const handleOk = async () => {
+  try {
+    let is_true = false;
+    if (form.value.credentials.huggingfacehub_api_type === 'hosted_inference_api') {
+      if (form.value.model_type && form.value.model && form.value.credentials.huggingfacehub_api_token) {
+        is_true = true;
+      } else {
+        is_true = false;
+      }
+    }
+    if (form.value.credentials.huggingfacehub_api_type === 'inference_endpoints') {
+      if (
+        form.value.model_type &&
+        form.value.model &&
+        form.value.credentials.huggingfacehub_api_token &&
+        form.value.credentials.huggingfacehub_endpoint_url &&
+        form.value.credentials.task_type
+      ) {
+        is_true = true;
+      } else {
+        is_true = false;
+      }
+    }
+
+    if (is_true) {
+      loading.value = true;
+      const res = await addModelDIFYFun(props.model_data.provider, form.value);
+      if (res && res.result === 'success') {
+        // Message.success('添加成功');
+        emit('updateModelList');
+        handleCancel();
+      } else {
+        // Message.error(res.message);
+      }
+      loading.value = false;
+    } else {
+      Message.error('缺少必填项');
+    }
+  } catch (e) {
+  } finally {
+    loading.value = false;
+  }
+};
+const handleCancel = () => {
+  emit('difyClose');
+};
+
+const addModelDIFYFun = async (provider, data) => {
+  try {
+    const res = await addModelDIFY(provider, data);
+    return res;
+  } catch (e) {
+    console.log(e);
+    Message.error(e.response?.data?.message);
+  }
+};
+const handleSubmit = () => {};
+
+onMounted(() => {
+  if (props.model_data.supported_model_types.length) {
+    form.value.model_type = props.model_data.supported_model_types[0];
+  }
+});
+</script>
+<style scoped lang="css">
+.section {
+  max-height: 50vh;
+}
+</style>

+ 215 - 0
src/modules/model-manage/dify/ModalAdd_5.vue

@@ -0,0 +1,215 @@
+<template>
+  <a-modal
+    v-model:visible="visible"
+    title-align="start"
+    :align-center="false"
+    :esc-to-close="false"
+    :ok-loading="loading"
+    top="15vh"
+    width="30vw"
+    @before-cancel="handleCancel"
+    @ok="handleOk"
+  >
+    <template #title>
+      <div class="model-title">
+        <b>添加 &nbsp;{{ props.model_data.label.en_US }}</b>
+      </div>
+    </template>
+    <div class="section">
+      <a-form :model="form" ref="formRef" :rules="rules" :layout="layout" @submit="handleSubmit">
+        <a-form-item field="model_type" label="模型类型">
+          <a-select v-model="form.model_type">
+            <a-option v-for="item in model_types" :key="item" :value="item">{{ item }}</a-option>
+          </a-select>
+        </a-form-item>
+        <a-form-item field="model" label="模型名称">
+          <a-input v-model="form.model" placeholder="请输入模型名称"></a-input>
+        </a-form-item>
+        <a-form-item field="credentials.base_url" label="基础 URL">
+          <a-input v-model="form.credentials.base_url" placeholder="Ollama server的基础URL"></a-input>
+        </a-form-item>
+        <a-form-item v-if="form.model_type === 'llm'" field="credentials.mode" label="模型类型">
+          <a-select v-model="form.credentials.mode" placeholder="选择对话类型" allow-clear>
+            <a-option v-for="item in Completion_mode_options" :key="item.value" :value="item.value">{{
+              item.label
+            }}</a-option>
+          </a-select>
+        </a-form-item>
+        <a-form-item field="credentials.context_size" label="模型上下文长度">
+          <a-input v-model="form.credentials.context_size" placeholder="请输入模型上下文长度"></a-input>
+        </a-form-item>
+        <a-form-item v-if="form.model_type === 'llm'" field="credentials.max_tokens" label="最大token上限">
+          <a-input v-model="form.credentials.max_tokens"></a-input>
+        </a-form-item>
+
+        <a-form-item v-if="form.model_type === 'llm'" field="credentials.vision_support" label="Vision 支持">
+          <a-select v-model="form.credentials.vision_support" allow-clear placeholder="请选择">
+            <a-option v-for="item in vision_support_options" :key="item.value" :value="item.value">{{
+              item.label
+            }}</a-option>
+          </a-select>
+        </a-form-item>
+      </a-form>
+    </div>
+  </a-modal>
+</template>
+
+<script setup>
+import { ref, computed, onMounted } from 'vue';
+import { Message } from '@arco-design/web-vue';
+import { addModelDIFY } from '../../../apis/rgflow-dify';
+const props = defineProps({
+  show_modal: Boolean,
+  model_data: Object
+});
+
+const emit = defineEmits(['difyClose', 'updateModelList']);
+
+const visible = computed(() => {
+  return props.show_modal;
+});
+
+const layout = ref('vertical');
+const loading = ref(false);
+const form = ref({
+  model_type: '',
+  model: '',
+  credentials: {
+    base_url: '',
+    context_size: '4096',
+    max_tokens: '4096',
+    mode: 'chat',
+    vision_support: false
+  },
+  load_balancing: {
+    configs: [],
+    enabled: false
+  }
+});
+
+const rules = {
+  model_type: [
+    {
+      required: true,
+      message: '模型类型必填'
+    }
+  ],
+  model: [
+    {
+      required: true,
+      message: '模型名称必填'
+    }
+  ],
+  'credentials.base_url': [
+    {
+      required: true,
+      message: '基础 URL必填'
+    }
+  ],
+  'credentials.context_size': [
+    {
+      required: true,
+      message: '模型上下文长度必填'
+    }
+  ],
+  'credentials.max_tokens': [
+    {
+      required: true,
+      message: '最大token上限必填'
+    }
+  ],
+  'credentials.stream_mode_delimiter': [
+    {
+      required: true,
+      message: '流模式返回结果的分隔符必填'
+    }
+  ]
+};
+const Completion_mode_options = ref([
+  {
+    label: '补全',
+    value: 'completion'
+  },
+  {
+    label: '对话',
+    value: 'chat'
+  }
+]);
+const vision_support_options = ref([
+  {
+    label: '不支持',
+    value: false
+  },
+  {
+    label: '支持',
+    value: true
+  }
+]);
+
+const model_types = computed(() => {
+  return props.model_data.supported_model_types;
+});
+
+const handleOk = async () => {
+  try {
+    let is_true = false;
+    if (form.value.model_type === 'llm') {
+      if (
+        form.value.model &&
+        form.value.credentials.base_url &&
+        form.value.credentials.context_size &&
+        form.value.credentials.max_tokens
+      ) {
+        is_true = true;
+      }
+    }
+    if (form.value.model_type === 'text-embedding') {
+      if (form.value.model && form.value.credentials.base_url && form.value.credentials.context_size) {
+        is_true = true;
+      }
+    }
+    if (is_true) {
+      loading.value = true;
+      const res = await addModelDIFYFun(props.model_data.provider, form.value);
+      if (res && res.result === 'success') {
+        // Message.success('添加成功');
+        emit('updateModelList');
+        handleCancel();
+      } else {
+        // Message.error(res.message);
+      }
+      loading.value = false;
+    } else {
+      Message.error('缺少必填项');
+    }
+  } catch (e) {
+  } finally {
+    loading.value = false;
+  }
+};
+const handleCancel = () => {
+  emit('difyClose');
+};
+
+const addModelDIFYFun = async (provider, data) => {
+  try {
+    const res = await addModelDIFY(provider, data);
+    return res;
+  } catch (e) {
+    console.log(e);
+    Message.error(e.response?.data?.message);
+  }
+};
+const handleSubmit = () => {};
+
+onMounted(() => {
+  if (props.model_data.supported_model_types.length) {
+    form.value.model_type = props.model_data.supported_model_types[0];
+  }
+});
+</script>
+<style scoped lang="css">
+.section {
+  max-height: 55vh;
+}
+</style>

+ 111 - 0
src/modules/model-manage/dify/ModalAdd_6.vue

@@ -0,0 +1,111 @@
+<template>
+  <a-modal
+    v-model:visible="visible"
+    title-align="start"
+    :align-center="false"
+    :esc-to-close="false"
+    :ok-loading="loading"
+    top="15vh"
+    width="30vw"
+    @before-cancel="handleCancel"
+    @ok="handleOk"
+  >
+    <template #title>
+      <div class="model-title">
+        <b>设置 &nbsp;{{ props.model_data.label.en_US }}</b>
+      </div>
+    </template>
+    <div class="section">
+      <a-form :model="form" ref="formRef" :rules="rules" :layout="layout" @submit="handleSubmit">
+        <a-form-item field="credentials.api_key" label="API Key">
+          <a-input v-model="form.credentials.api_key" placeholder="请输入API Key"></a-input>
+        </a-form-item>
+        <a-form-item field="credentials.endpoint_url" label="自定义 API endpoint 地址">
+          <a-input v-model="form.credentials.endpoint_url" placeholder="Base URL"></a-input>
+        </a-form-item>
+      </a-form>
+    </div>
+  </a-modal>
+</template>
+
+<script setup>
+import { ref, computed, onMounted } from 'vue';
+import { Message } from '@arco-design/web-vue';
+import { addModelDIFYDeepSeek } from '../../../apis/rgflow-dify';
+const props = defineProps({
+  show_modal: Boolean,
+  model_data: Object
+});
+
+const emit = defineEmits(['difyClose', 'updateModelList']);
+
+const visible = computed(() => {
+  return props.show_modal;
+});
+
+const layout = ref('vertical');
+const loading = ref(false);
+const form = ref({
+  config_from: 'predefined-model',
+  credentials: {
+    api_key: '',
+    endpoint_url: ''
+  },
+  load_balancing: {
+    configs: [],
+    enabled: false
+  }
+});
+
+const rules = {
+  'credentials.api_key': [
+    {
+      required: true,
+      message: 'API Key必填'
+    }
+  ]
+};
+
+const model_types = computed(() => {
+  return props.model_data.supported_model_types;
+});
+
+const handleOk = async () => {
+  try {
+    if (form.value.credentials.api_key) {
+      loading.value = true;
+      const res = await addModelDIFYFun(props.model_data.provider, form.value);
+      if (res && res.result === 'success') {
+        // Message.success('添加成功');
+        emit('updateModelList');
+        handleCancel();
+      } else {
+        // Message.error(res.message);
+      }
+      loading.value = false;
+    } else {
+      Message.error('缺少必填项');
+    }
+  } catch (e) {
+  } finally {
+    loading.value = false;
+  }
+};
+const handleCancel = () => {
+  emit('difyClose');
+};
+
+const addModelDIFYFun = async (provider, data) => {
+  try {
+    const res = await addModelDIFYDeepSeek(provider, data);
+    return res;
+  } catch (e) {
+    console.log(e);
+    Message.error(e.response?.data?.message);
+  }
+};
+const handleSubmit = () => {};
+
+onMounted(() => {});
+</script>
+<style scoped lang="css"></style>

+ 161 - 0
src/modules/model-manage/dify/ModelList.vue

@@ -0,0 +1,161 @@
+<template>
+  <div class="model-list">
+    <div class="list-header">
+      <div class="list-info">
+        <img class="icon" :src="getIconModel(model_name)" alt="" />
+        <div class="info-desc">
+          <div class="name">{{ model_name }}</div>
+          <div class="desc">{{ model_item.supported_model_types.join(',') }}</div>
+        </div>
+      </div>
+      <div class="list-tools">
+        <a-space>
+          <a-button
+            v-if="['OpenAI-API-compatible', 'HuggingFace', 'Xorbits Inference', 'Ollama'].includes(model_name)"
+            @click="handlerAddModel"
+          >
+            <template #icon>
+              <icon-plus />
+            </template>
+            {{ $t('management.addModelBtn') }}
+          </a-button>
+          <a-button v-if="['OpenAI', 'DeepSeek'].includes(model_item.name)" @click="handlerAddModel">
+            <template #icon>
+              <icon-plus />
+            </template>
+            API_Key
+          </a-button>
+          <a-button @click="showMoreModel(model_item)">
+            {{ $t('management.showMoreModelBtn') }}
+            <template v-if="model_item.showMore">
+              <icon-down />
+            </template>
+            <template v-if="!model_item.showMore">
+              <icon-up />
+            </template>
+          </a-button>
+          <!-- <icon-delete class="delete-icon" @click="handlerDeleteModel(model_item.name)" /> -->
+        </a-space>
+      </div>
+    </div>
+    <transition name="expand-collapse" @before-enter="beforeEnter" @enter="enter" @leave="leave">
+      <div v-if="model_item.showMore" class="list-collapse">
+        <CollapseItem v-for="(item, index) in model_item.llm" :key="index" :models="item" @toDeleteLLm="toDeleteLLm" />
+      </div>
+    </transition>
+  </div>
+  <DeleteModal :visible.sync="delete_visible" @ok="deleteModalOk" @cancel="delete_visible = false" />
+</template>
+
+<script setup>
+import { ref } from 'vue';
+import CollapseItem from './CollapseItem.vue';
+import { useCollapse } from '../use-collapse';
+import { getIconModel } from '../../common/icon-map';
+import DeleteModal from '../../../components/DeleteModal.vue';
+import { deleteFactoryRG, deleteModelDifyChild } from '../../../apis/rgflow-dify';
+import { Message } from '@arco-design/web-vue';
+
+const props = defineProps({
+  model_item: {
+    request: true,
+    type: Object
+  },
+  model_name: {
+    request: true,
+    type: String
+  }
+});
+const emit = defineEmits(['showMore', 'updateAddModelDify', 'toAddModel']);
+
+const delete_visible = ref(false);
+const delete_llm = ref({});
+
+const { beforeEnter, enter, leave } = useCollapse(500);
+
+const handlerAddModel = () => {
+  emit('toAddModel', props.model_name);
+};
+const handlerDeleteModel = () => {
+  delete_visible.value = true;
+};
+const deleteModalOk = async () => {
+  await deleteModelDifyChildFun();
+};
+const showMoreModel = (item) => {
+  emit('showMore', item);
+};
+const deleteModelDifyChildFun = async () => {
+  try {
+    await deleteModelDifyChild(delete_llm.value.provider, {
+      model: delete_llm.value.model,
+      model_type: delete_llm.value.model_type
+    });
+    // emit('updateAddModelDify');
+    delete_llm.value = {};
+    delete_visible.value = false;
+    // if (res.result === 'success') {
+    Message.success('删除成功');
+
+    // } else {
+    //   // Message.error('删除失败');
+    // }
+  } catch (e) {
+    // Message.error(e.response?.data.message);
+  } finally {
+    emit('updateAddModelDify');
+    delete_llm_name.value = {};
+    delete_visible.value = false;
+  }
+};
+const toDeleteLLm = (item) => {
+  delete_llm.value = {
+    model: item.model,
+    model_type: item.model_type,
+    provider: item.provider.provider
+  };
+  delete_visible.value = true;
+};
+</script>
+<style scoped lang="css">
+.model-list {
+  padding: 20px 30px;
+  margin-top: 10px;
+  background-color: var(--color-bg-2);
+}
+.list-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+.list-info {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  gap: 20px;
+  .icon {
+    width: 44px;
+  }
+}
+.info-desc {
+  display: flex;
+  flex-direction: column;
+  gap: 10px;
+}
+.name {
+  font-size: 16px;
+  font-weight: bold;
+}
+.desc {
+  color: var(--color-text-3);
+}
+.delete-icon {
+  font-size: 20px;
+  color: rgb(var(--orange-5));
+  cursor: pointer;
+}
+.list-collapse {
+  padding: 20px 20px 0 20px;
+  max-height: 0;
+}
+</style>

+ 216 - 0
src/modules/model-manage/dify/SystemSetting.vue

@@ -0,0 +1,216 @@
+<template>
+  <a-modal
+    v-model:visible="visible"
+    title-align="start"
+    :align-center="false"
+    :esc-to-close="false"
+    top="15vh"
+    width="30vw"
+    :ok-loading="loading"
+    @before-cancel="handleCancel"
+    @ok="handleOk"
+  >
+    <template #title>
+      <div class="model-title">系统模型管理设置</div>
+    </template>
+    <div class="section">
+      <a-form :model="form" :layout="layout">
+        <a-form-item field="" label="系统推理模型">
+          <a-select v-model="form.llm.model">
+            <a-option v-for="item in llm_options" :key="item.model" :Value="item.model">{{ item.model }}</a-option>
+          </a-select>
+        </a-form-item>
+        <a-form-item field="" label="Embedding 模型">
+          <a-select v-model="form.embd.model">
+            <a-option v-for="item in Embedding_options" :key="item.model" :Value="item.model">{{
+              item.model
+            }}</a-option>
+          </a-select>
+        </a-form-item>
+        <a-form-item field="" label="Rerank 模型">
+          <a-select v-model="form.rerank.model">
+            <a-option v-for="item in rarank_options" :key="item.model" :Value="item.model">{{ item.model }}</a-option>
+          </a-select>
+        </a-form-item>
+        <a-form-item field="" label="语音转文本模型">
+          <a-select v-model="form.speech2txt.model">
+            <a-option v-for="item in speech2text_options" :key="item.model" :Value="item.model">{{
+              item.model
+            }}</a-option>
+          </a-select>
+        </a-form-item>
+        <a-form-item field="" label="文本转语音模型">
+          <a-select v-model="form.tts.model">
+            <a-option v-for="item in tts_options" :key="item.model" :Value="item.model">{{ item.model }}</a-option>
+          </a-select>
+        </a-form-item>
+      </a-form>
+    </div>
+  </a-modal>
+</template>
+
+<script setup>
+import { ref, computed, onMounted } from 'vue';
+import {
+  getSystemModel,
+  getEmbeddingModel,
+  getRarankModel,
+  getSpeech2TextModel,
+  getTtsModel,
+  updateSystemModelSettingDIFY
+} from '../../../apis/rgflow-dify';
+import { Message } from '@arco-design/web-vue';
+const props = defineProps({
+  show_modal: Boolean
+});
+
+const emit = defineEmits(['difyClose']);
+
+const visible = computed(() => {
+  return props.show_modal;
+});
+
+const layout = ref('vertical');
+const form = ref({
+  llm: {
+    model: '',
+    model_type: 'llm',
+    provider: ''
+  },
+  embd: {
+    model: '',
+    model_type: 'text-embedding',
+    provider: ''
+  },
+  speech2txt: {
+    model: '',
+    model_type: 'speech2text',
+    provider: ''
+  },
+  rerank: {
+    model: '',
+    model_type: 'rerank',
+    provider: ''
+  },
+  tts: {
+    model: '',
+    model_type: 'tts',
+    provider: ''
+  }
+});
+const loading = ref(false);
+
+const handleOk = () => {
+  const model_settings = Object.values(form.value);
+  updateSystemModelSettingDIFYFun({ model_settings });
+};
+const handleCancel = () => {
+  emit('difyClose');
+};
+
+const llm_options = ref([]);
+const Embedding_options = ref([]);
+const rarank_options = ref([]);
+const speech2text_options = ref([]);
+const tts_options = ref([]);
+
+const updateSystemModelSettingDIFYFun = async (data) => {
+  loading.value = true;
+  try {
+    const res = await updateSystemModelSettingDIFY(data);
+    if (res.result === 'success') {
+      Message.success('修改成功');
+      handleCancel();
+    }
+  } catch (e) {
+    Message.error(e.response?.data.message);
+  } finally {
+    loading.value = false;
+  }
+};
+const getSystemModelFun = async () => {
+  try {
+    const res = await getSystemModel();
+
+    if (res.data) {
+      llm_options.value = res.data[0]?.models || [];
+      form.value.llm.provider = res.data.provider;
+      if (llm_options.value.length) {
+        form.value.llm.model = llm_options.value[0].model;
+        form.value.llm.model_type = llm_options.value[0].model_type;
+      }
+    }
+  } catch (e) {
+    console.log(e);
+  }
+};
+const getEmbeddingModelFun = async () => {
+  try {
+    const res = await getEmbeddingModel();
+    if (res.data) {
+      form.value.embd.provider = res.data.provider;
+      Embedding_options.value = res.data[0]?.models || [];
+      if (Embedding_options.value.length) {
+        form.value.embd.model = Embedding_options.value[0].model;
+        form.value.embd.model_type = Embedding_options.value[0].model_type;
+      }
+    }
+  } catch (e) {
+    console.log(e);
+  }
+};
+const getRarankModelFun = async () => {
+  try {
+    const res = await getRarankModel();
+    if (res.data) {
+      rarank_options.value = res.data[0]?.models || [];
+      form.value.rerank.provider = res.data.provider;
+      if (rarank_options.value.length) {
+        form.value.rerank.model = rarank_options.value[0].model;
+        form.value.rerank.model_type = rarank_options.value[0].model_type;
+      }
+    }
+  } catch (e) {
+    console.log(e);
+  }
+};
+const getSpeech2TextModelFun = async () => {
+  try {
+    const res = await getSpeech2TextModel();
+    if (res.data) {
+      form.value.speech2txt.provider = res.data.provider;
+      speech2text_options.value = res.data[0]?.models || [];
+      if (speech2text_options.value.length) {
+        form.value.speech2txt.model = speech2text_options.value[0].model;
+        form.value.speech2txt.model_type = speech2text_options.value[0].model_type;
+      }
+    }
+  } catch (e) {
+    console.log(e);
+  }
+};
+const getTtsModelFun = async () => {
+  try {
+    const res = await getTtsModel();
+    if (res.data) {
+      form.value.tts.provider = res.data.provider;
+      tts_options.value = res.data[0]?.models || [];
+      if (tts_options.value.length) {
+        form.value.tts.model = tts_options.value[0].model;
+        form.value.tts.model_type = tts_options.value[0].model_type;
+      }
+    }
+  } catch (e) {
+    console.log(e);
+  }
+};
+
+onMounted(async () => {
+  await getSystemModelFun();
+  await getEmbeddingModelFun();
+  await getRarankModelFun();
+  await getSpeech2TextModelFun();
+  await getTtsModelFun();
+});
+</script>
+<style scoped lang="css"></style>

+ 8 - 3
src/modules/model-manage/CollapseItem.vue → src/modules/model-manage/ragflow/CollapseItem.vue

@@ -1,8 +1,10 @@
 <template>
   <div class="collapse-item">
     <div class="name">{{ models.name }}</div>
-    <a-button type="primary" @click="toUse">{{ $t('management.useBtn') }}</a-button>
-    <icon-delete class="delete-icon" @click="toDelete" />
+    <a-tag>{{ models.type }}</a-tag>
+    <!-- <div class="name">{{ models.type }}</div> -->
+    <!-- <a-button type="primary" @click="toUse">{{ $t('management.useBtn') }}</a-button> -->
+    <icon-delete class="delete-icon" @click="toDelete(models.name)" />
   </div>
 </template>
 
@@ -13,9 +15,12 @@ const props = defineProps({
     required: true
   }
 });
+const emit = defineEmits(['toDeleteLLm']);
 
 const toUse = () => {};
-const toDelete = () => {};
+const toDelete = (name) => {
+  emit('toDeleteLLm', name);
+};
 </script>
 
 <style lang="css" scoped>

+ 317 - 0
src/modules/model-manage/ragflow/MessageBody.vue

@@ -0,0 +1,317 @@
+<template>
+  <div class="model-body">
+    <div class="body-item">
+      <div class="item-title">
+        <div class="title">{{ $t('management.addedModel') }}</div>
+        <div class="icon" @click="changeStatusAdd">
+          <template v-if="added_show_status"> {{ $t('management.upBtn') }} <icon-up /> </template>
+          <template v-if="!added_show_status"> {{ $t('management.downBtn') }} <icon-down /> </template>
+        </div>
+      </div>
+      <transition name="expand-collapse" @before-enter="beforeEnter" @enter="enter" @leave="leave">
+        <div v-if="added_show_status">
+          <ModelList
+            v-for="(item, key) in rag_added_data"
+            :key="key"
+            :model_item="item"
+            :model_name="key"
+            @showMore="showMoreModel"
+            @updateAddModelRG="updateModelList"
+            @toAddModel="toAddModel"
+            >{{ (key, index) }}</ModelList
+          >
+          <a-empty v-if="!rag_added_data.length" class="empty-list"></a-empty>
+        </div>
+      </transition>
+    </div>
+    <div class="body-item">
+      <div class="item-title">
+        <div class="title">{{ $t('management.unaddModel') }}</div>
+        <div class="icon" @click="changeStatusUnAdd">
+          <template v-if="unadd_show_status"> {{ $t('management.upBtn') }} <icon-up /> </template>
+          <template v-if="!unadd_show_status"> {{ $t('management.downBtn') }} <icon-down /> </template>
+        </div>
+      </div>
+      <transition name="expand-collapse" @before-enter="beforeEnter" @enter="enter" @leave="leave">
+        <div v-if="unadd_show_status" class="card-list">
+          <CardItem v-for="item in rag_unadded_data" :key="item.id" :name="item.name" :img_src="item.img_src" is_model>
+            <template #labels>
+              <div class="labels">{{ item.keyword }}</div>
+            </template>
+            <template #description>
+              <div class="desc">{{ item.tags }}</div>
+            </template>
+            <template #bottom>
+              <a-button @click="handleAddModel(item)">
+                <template #icon> <icon-plus /> </template>{{ $t('management.addModelBtn') }}</a-button
+              ></template
+            >
+          </CardItem>
+        </div>
+      </transition>
+      <a-empty v-if="!rag_unadded_data.length" class="empty-list"></a-empty>
+    </div>
+  </div>
+  <ModalAdd_1
+    v-if="show_Xinference_model"
+    :show_modal="show_Xinference_model"
+    :model_data="Xinference_data"
+    @ragClose="closeXinference"
+    @updateModelList="updateModelList"
+  ></ModalAdd_1>
+  <ModalAdd_2
+    v-if="show_Openai_model"
+    :show_modal="show_Openai_model"
+    :model_data="Openai_data"
+    @ragClose="closeOpenAi"
+    @updateModelList="updateModelList"
+  ></ModalAdd_2>
+</template>
+
+<script setup>
+import { ref, onMounted } from 'vue';
+import ModelList from './ModelList.vue';
+import CardItem from '../../common/CardItem.vue';
+import { useCollapse } from '../use-collapse';
+import { getAddedModelsRG, getUnaddedModelsRG } from '../../../apis/rgflow-dify';
+import ModalAdd_1 from './ModalAdd_1.vue';
+import ModalAdd_2 from './ModalAdd_2.vue';
+
+const { beforeEnter, enter, leave } = useCollapse(1000);
+
+const added_data = ref([
+  // {
+  //   id: '1',
+  //   name: 'OpenAl-APl-Compatible',
+  //   desc: 'LLM,TEXT EMBEDDING,SPEECH2TEXT,MODERATION ',
+  //   img_src: 'modelFrame1',
+  //   show_more: false,
+  //   models: [
+  //     {
+  //       id: '1-1',
+  //       name: 'qwen-2.5-72b-instruct OpenAl-APl'
+  //     },
+  //     {
+  //       id: '1-2',
+  //       name: 'qwen-2.5-72b-instruct-gptq-int4 OpenAI-API'
+  //     }
+  //   ]
+  // },
+  // {
+  //   id: '2',
+  //   name: 'Tongyi-Qianwen',
+  //   desc: 'LLM,TEXT EMBEDDING,SPEECH2TEXT,MODERATION',
+  //   img_src: 'modelFrame2',
+  //   show_more: false,
+  //   models: [
+  //     {
+  //       id: '2-1',
+  //       name: 'qwen-plus'
+  //     },
+  //     {
+  //       id: '2-2',
+  //       name: 'qwen-turbo'
+  //     },
+  //     {
+  //       id: '2-3',
+  //       name: 'qwen-vl-plus'
+  //     }
+  //   ]
+  // },
+  // {
+  //   id: '3',
+  //   name: 'Xinference',
+  //   desc: 'LLM,TEXT EMBEDDING,SPEECH2TEXT,MODERATION,TEXT RE-RANK',
+  //   img_src: 'modelFrame3',
+  //   show_more: false,
+  //   models: [
+  //     {
+  //       id: '3-1',
+  //       name: 'basic-bge-large-zh-v1.5'
+  //     }
+  //   ]
+  // }
+]);
+const un_added_data = ref([
+  {
+    id: '1',
+    name: 'OpenAl',
+    keyword: 'LLM,TEXT',
+    desc: 'EMBEDDING,SPEECH2TEXT,MODERATION',
+    img_src: 'modelFrame4'
+  },
+  {
+    id: '2',
+    name: 'Moonshot',
+    keyword: 'LLM,TEXT EMBEDDING',
+    desc: '',
+    img_src: 'modelFrame5'
+  },
+  {
+    id: '3',
+    name: 'ZHIPU-AI',
+    keyword: 'LLM,TEXT',
+    desc: 'EMBEDDING,SPEECH2TEXT,MODERATION',
+    img_src: 'modelFrame6'
+  },
+  {
+    id: '4',
+    name: 'BaiChuan',
+    keyword: 'LLM,TEXT EMBEDDING',
+    desc: '',
+    img_src: 'modelFrame7'
+  },
+  {
+    id: '5',
+    name: 'DeepSeek',
+    keyword: 'LLM',
+    desc: '',
+    img_src: 'modelFrame8'
+  },
+  {
+    id: '6',
+    name: 'Mistral',
+    keyword: 'LLM,TEXT EMBEDDING',
+    desc: '',
+    img_src: 'modelFrame9'
+  },
+  {
+    id: '7',
+    name: 'StepFun',
+    keyword: 'LLM',
+    desc: '',
+    img_src: 'modelFrame10'
+  }
+]);
+const rag_added_data = ref({});
+const rag_unadded_data = ref([]);
+// 展开收起状态
+const added_show_status = ref(true);
+const unadd_show_status = ref(true);
+
+const show_Xinference_model = ref(false);
+const Xinference_data = ref({});
+
+const show_Openai_model = ref(false);
+const Openai_data = ref({});
+
+const changeStatusAdd = () => {
+  added_show_status.value = !added_show_status.value;
+  if (!added_show_status.value) {
+    added_data.value.forEach((item) => {
+      item.show_more = false;
+    });
+  }
+};
+const changeStatusUnAdd = () => {
+  unadd_show_status.value = !unadd_show_status.value;
+};
+
+const showMoreModel = (item) => {
+  rag_added_data.value[item.name].showMore = !rag_added_data.value[item.name].showMore;
+};
+
+const getAddedModelsRGFun = async () => {
+  try {
+    const res = await getAddedModelsRG();
+    if (res.code / 1 === 0) {
+      for (const key in res.data) {
+        res.data[key].showMore = false;
+        res.data[key].name = key;
+      }
+      // Object.values(res.data).forEach((item) => {
+      //   item.showMore = true;
+      //   item.name =
+      // });
+      rag_added_data.value = { ...res.data };
+    }
+  } catch (e) {
+    console.log(e);
+  }
+};
+const getUnaddedModelsRGFun = async () => {
+  try {
+    const res = await getUnaddedModelsRG();
+    if (res.code / 1 === 0) {
+      rag_unadded_data.value = res.data.filter((item) => {
+        return ['OpenAI', 'Ollama', 'DeepSeek', 'OpenAI-API-Compatible', 'Xinference', 'HuggingFace'].includes(
+          item.name
+        );
+      });
+    }
+  } catch (e) {
+    console.log(e);
+  }
+};
+const updateModelList = async () => {
+  await getAddedModelsRGFun();
+  await getUnaddedModelsRGFun();
+};
+const toAddModel = (name) => {
+  const item = rag_unadded_data.value.find((item) => item.name === name);
+  handleAddModel(item);
+};
+const handleAddModel = async (item) => {
+  if (['OpenAI-API-Compatible', 'HuggingFace', 'Xinference', 'Ollama'].includes(item.name)) {
+    show_Xinference_model.value = true;
+    Xinference_data.value = item;
+  }
+  if (['OpenAI', 'DeepSeek'].includes(item.name)) {
+    show_Openai_model.value = true;
+    Openai_data.value = item;
+  }
+};
+const closeXinference = () => {
+  show_Xinference_model.value = false;
+  Xinference_data.value = {};
+};
+const closeOpenAi = () => {
+  show_Openai_model.value = false;
+  Openai_data.value = {};
+};
+
+onMounted(() => {
+  getAddedModelsRGFun();
+  getUnaddedModelsRGFun();
+});
+</script>
+<style scoped lang="css">
+.model-body {
+  height: calc(100% - 80px);
+  overflow-y: auto;
+}
+.model-body::-webkit-scrollbar {
+  display: none;
+}
+.body-item {
+  margin-bottom: 50px;
+}
+.empty-list {
+  background-color: var(--color-bg-2);
+}
+
+.item-title {
+  display: flex;
+  flex-direction: row;
+  justify-content: space-between;
+  margin-bottom: 20px;
+  cursor: pointer;
+}
+.title {
+  font-size: 16px;
+  font-weight: bold;
+  color: var(--color-text-1);
+}
+.card-list {
+  display: grid;
+  grid-template-columns: repeat(5, 1fr);
+  gap: 20px;
+  transition: all 0.3s ease;
+  .labels {
+    color: var(--color-text-3);
+  }
+  .desc {
+    font-size: 14px;
+  }
+}
+</style>

+ 143 - 0
src/modules/model-manage/ragflow/ModalAdd_1.vue

@@ -0,0 +1,143 @@
+<template>
+  <a-modal
+    v-model:visible="visible"
+    title-align="start"
+    :align-center="false"
+    :esc-to-close="false"
+    :ok-loading="loading"
+    top="15vh"
+    width="30vw"
+    @before-cancel="handleCancel"
+    @ok="handleOk"
+  >
+    <template #title>
+      <div class="model-title">
+        <b>添加LLM</b>
+      </div>
+    </template>
+    <div class="section">
+      <a-form :model="form" ref="formRef" :rules="rules" :layout="layout" @submit="handleSubmit">
+        <a-form-item field="model_type" label="模型类型">
+          <a-select v-model="form.model_type">
+            <a-option v-for="item in model_types" :key="item" :value="item">{{ item }}</a-option>
+          </a-select>
+        </a-form-item>
+        <a-form-item
+          field="llm_name"
+          :label="model_data.name == 'Xinference' || model_data.name == 'Ollama' ? '模型UID' : '模型名称'"
+        >
+          <a-input v-model="form.llm_name" placeholder="请输入模型名称"></a-input>
+        </a-form-item>
+        <a-form-item field="api_base" label="基础Url">
+          <a-input v-model="form.api_base" placeholder="请输入模型基础URL"></a-input>
+        </a-form-item>
+        <a-form-item field="api_key" label="API-Key">
+          <a-input v-model="form.api_key" placeholder="请输入apiKey(如果是本地部署的模型,请忽略它)"></a-input>
+        </a-form-item>
+        <a-form-item field="max_tokens" label="最大token数">
+          <a-input-number
+            v-model="form.max_tokens"
+            placeholder="这设置了模型输出的最大长度,以标记(单词或单词判断)的数量来衡量"
+            :min="0"
+          />
+        </a-form-item>
+      </a-form>
+    </div>
+  </a-modal>
+</template>
+
+<script setup>
+import { ref, computed, onMounted } from 'vue';
+import { Message } from '@arco-design/web-vue';
+import { addModelRG } from '../../../apis/rgflow-dify';
+const props = defineProps({
+  show_modal: Boolean,
+  model_data: Object
+});
+
+const emit = defineEmits(['ragClose', 'updateModelList']);
+
+const visible = computed(() => {
+  return props.show_modal;
+});
+
+const layout = ref('vertical');
+const loading = ref(false);
+const form = ref({
+  model_type: '',
+  llm_name: '',
+  api_base: '',
+  max_tokens: '',
+  api_key: '',
+  llm_factory: ''
+});
+
+const rules = {
+  model_type: [
+    {
+      required: true,
+      message: '模型类型必填'
+    }
+  ],
+  llm_name: [
+    {
+      required: true,
+      message: '模型名称必填'
+    }
+  ],
+  api_base: [
+    {
+      required: true,
+      message: '基础url必填'
+    }
+  ],
+  max_tokens: [
+    {
+      required: true,
+      message: '最大token数必填'
+    }
+  ]
+};
+
+const model_types = computed(() => {
+  return props.model_data.model_types;
+});
+
+const handleOk = async () => {
+  if (form.value.model_type && form.value.llm_name && form.value.api_base && form.value.max_tokens) {
+    loading.value = true;
+    const res = await addModelRGFun(form.value);
+    if (res.code / 1 === 0) {
+      Message.success('添加成功');
+      emit('updateModelList');
+      handleCancel();
+    } else {
+      Message.error(res.message);
+    }
+    loading.value = false;
+  } else {
+    Message.error('缺少必填项');
+  }
+};
+const handleCancel = () => {
+  emit('ragClose');
+};
+
+const addModelRGFun = async (data) => {
+  try {
+    const res = await addModelRG(data);
+    return res;
+  } catch (e) {
+    console.log(e);
+  }
+};
+const handleSubmit = () => {};
+
+onMounted(() => {
+  if (props.model_data.model_types.length) {
+    form.value.model_type = props.model_data.model_types[0];
+  }
+  form.value.llm_factory = props.model_data.name;
+});
+</script>
+<style scoped lang="css"></style>

+ 103 - 0
src/modules/model-manage/ragflow/ModalAdd_2.vue

@@ -0,0 +1,103 @@
+<template>
+  <a-modal
+    v-model:visible="visible"
+    title-align="start"
+    :align-center="false"
+    :esc-to-close="false"
+    top="15vh"
+    width="30vw"
+    :ok-loading="loading"
+    @before-cancel="handleCancel"
+    @ok="handleOk"
+  >
+    <template #title>
+      <div class="model-title">
+        <b>修改</b>
+      </div>
+    </template>
+    <div class="section">
+      <a-form :model="form" ref="formRef" :rules="rules" :layout="layout">
+        <a-form-item field="api_key" label="API-Key">
+          <a-input v-model="form.api_key" placeholder="请输入apiKey(如果是本地部署的模型,请忽略它)"></a-input>
+        </a-form-item>
+        <a-form-item v-if="model_data.name === 'OpenAI'" field="base_url" label="Base-Url">
+          <a-input v-model="form.base_url" placeholder="https://api.openai.com/v1"></a-input>
+        </a-form-item>
+      </a-form>
+    </div>
+  </a-modal>
+</template>
+
+<script setup>
+import { ref, computed, onMounted } from 'vue';
+import { Message } from '@arco-design/web-vue';
+import { updateModelRG } from '../../../apis/rgflow-dify';
+const props = defineProps({
+  show_modal: Boolean,
+  model_data: Object
+});
+
+const emit = defineEmits(['ragClose', 'updateModelList']);
+
+const visible = computed(() => {
+  return props.show_modal;
+});
+
+const layout = ref('vertical');
+const form = ref({
+  api_key: '',
+  base_url: '',
+  llm_factory: ''
+});
+
+const rules = {
+  api_key: [
+    {
+      required: true,
+      message: '请输入api key(如果是本地部署的模型,请忽略它)'
+    }
+  ]
+};
+
+const loading = ref(false);
+const model_types = computed(() => {
+  return props.model_data.model_types;
+});
+
+const handleOk = async () => {
+  if (form.value.api_key) {
+    loading.value = true;
+    const res = await updateModelRGFun(form.value);
+    if (res.code / 1 === 0) {
+      Message.success('添加成功');
+      emit('updateModelList');
+      handleCancel();
+    } else {
+      Message.error(res.message);
+    }
+    loading.value = false;
+  } else {
+    Message.error('缺少必填项');
+  }
+};
+const handleCancel = () => {
+  emit('ragClose');
+};
+
+const updateModelRGFun = async (data) => {
+  try {
+    const res = await updateModelRG(data);
+    return res;
+  } catch (e) {
+    console.log(e);
+  }
+};
+
+onMounted(() => {
+  if (props.model_data.model_types.length) {
+    form.value.model_type = props.model_data.model_types[0];
+  }
+  form.value.llm_factory = props.model_data.name;
+});
+</script>
+<style scoped lang="css"></style>

+ 168 - 0
src/modules/model-manage/ragflow/ModelList.vue

@@ -0,0 +1,168 @@
+<template>
+  <div class="model-list">
+    <div class="list-header">
+      <div class="list-info">
+        <img class="icon" :src="getIconModel(model_item.name)" alt="" />
+        <div class="info-desc">
+          <div class="name">{{ model_name }}</div>
+          <div class="desc">{{ model_item.tags }}</div>
+        </div>
+      </div>
+      <div class="list-tools">
+        <a-space>
+          <a-button
+            v-if="['OpenAI-API-Compatible', 'HuggingFace', 'Xinference', 'Ollama'].includes(model_item.name)"
+            @click="handlerAddModel"
+          >
+            <template #icon>
+              <icon-plus />
+            </template>
+            {{ $t('management.addModelBtn') }}
+          </a-button>
+          <a-button v-if="['OpenAI', 'DeepSeek'].includes(model_item.name)" @click="handlerAddModel">
+            <template #icon>
+              <icon-plus />
+            </template>
+            API_Key
+          </a-button>
+          <a-button @click="showMoreModel(model_item)">
+            {{ $t('management.showMoreModelBtn') }}
+            <template v-if="model_item.showMore">
+              <icon-down />
+            </template>
+            <template v-if="!model_item.showMore">
+              <icon-up />
+            </template>
+          </a-button>
+          <icon-delete class="delete-icon" @click="handlerDeleteModel(model_item.name)" />
+        </a-space>
+      </div>
+    </div>
+    <transition name="expand-collapse" @before-enter="beforeEnter" @enter="enter" @leave="leave">
+      <div v-if="model_item.showMore" class="list-collapse">
+        <CollapseItem v-for="(item, index) in model_item.llm" :key="index" :models="item" @toDeleteLLm="toDeleteLLm" />
+      </div>
+    </transition>
+  </div>
+  <DeleteModal :visible.sync="delete_visible" @ok="deleteModalOk" @cancel="delete_visible = false" />
+</template>
+
+<script setup>
+import { ref } from 'vue';
+import CollapseItem from './CollapseItem.vue';
+import { useCollapse } from '../use-collapse';
+import { getIconModel } from '../../common/icon-map';
+import DeleteModal from '../../../components/DeleteModal.vue';
+import { deleteFactoryRG, deleteFactoryLLmRG } from '../../../apis/rgflow-dify';
+import { Message } from '@arco-design/web-vue';
+
+const props = defineProps({
+  model_item: {
+    request: true,
+    type: Object
+  },
+  model_name: {
+    request: true,
+    type: String
+  }
+});
+const emit = defineEmits(['showMore', 'updateAddModelRG', 'toAddModel']);
+
+const delete_visible = ref(false);
+const delete_llm_name = ref('');
+
+const { beforeEnter, enter, leave } = useCollapse(500);
+
+const handlerAddModel = () => {
+  emit('toAddModel', props.model_name);
+};
+const handlerDeleteModel = () => {
+  delete_visible.value = true;
+};
+const deleteModalOk = () => {
+  if (delete_llm_name.value) {
+    deleteFactoryLLmRGFun();
+  } else {
+    deleteFactoryRGFun();
+  }
+};
+const showMoreModel = (item) => {
+  emit('showMore', item);
+};
+
+const deleteFactoryRGFun = async () => {
+  try {
+    const res = await deleteFactoryRG({ llm_factory: props.model_name });
+    if (res.code / 1 == 0 && res.data) {
+      Message.success('删除成功');
+      emit('updateAddModelRG');
+      delete_visible.value = false;
+    } else {
+      Message.error('删除失败');
+    }
+  } catch (e) {
+    console.log(e);
+  }
+};
+const deleteFactoryLLmRGFun = async () => {
+  try {
+    const res = await deleteFactoryLLmRG({ llm_factory: props.model_name, llm_name: delete_llm_name.value });
+    if (res.code / 1 == 0 && res.data) {
+      Message.success('删除成功');
+      emit('updateAddModelRG');
+      delete_llm_name.value = '';
+      delete_visible.value = false;
+    } else {
+      Message.error('删除失败');
+    }
+  } catch (e) {
+    console.log(e);
+  }
+};
+const toDeleteLLm = (name) => {
+  delete_llm_name.value = name;
+  delete_visible.value = true;
+};
+</script>
+<style scoped lang="css">
+.model-list {
+  padding: 20px 30px;
+  margin-top: 10px;
+  background-color: var(--color-bg-2);
+}
+.list-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+.list-info {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  gap: 20px;
+  .icon {
+    width: 44px;
+  }
+}
+.info-desc {
+  display: flex;
+  flex-direction: column;
+  gap: 10px;
+}
+.name {
+  font-size: 16px;
+  font-weight: bold;
+}
+.desc {
+  color: var(--color-text-3);
+}
+.delete-icon {
+  font-size: 20px;
+  color: rgb(var(--orange-5));
+  cursor: pointer;
+}
+.list-collapse {
+  padding: 20px 20px 0 20px;
+  max-height: 0;
+}
+</style>

+ 157 - 0
src/modules/model-manage/ragflow/SystemSetting.vue

@@ -0,0 +1,157 @@
+<template>
+  <a-modal
+    v-model:visible="visible"
+    title-align="start"
+    :align-center="false"
+    :esc-to-close="false"
+    top="15vh"
+    width="30vw"
+    :ok-loading="loading"
+    @before-cancel="handleCancel"
+    @ok="handleOk"
+  >
+    <template #title>
+      <div class="model-title">系统模型管理设置</div>
+    </template>
+    <div class="section">
+      <a-form :model="form" :layout="layout">
+        <a-form-item field="chatModal" label="聊天模型">
+          <a-select v-model="form.llm_id" allow-clear>
+            <a-option></a-option>
+          </a-select>
+        </a-form-item>
+        <a-form-item field="innerModal" label="嵌入模型">
+          <a-select v-model="form.embd_id" allow-clear>
+            <a-optgroup :label="index" v-for="(item, index) in innerModelList" :key="index">
+              <a-option v-for="obj in item" :key="obj.fid" :disabled="!obj.available" :value="obj.llm_name">
+                {{ obj.llm_name }}
+              </a-option>
+            </a-optgroup>
+          </a-select>
+        </a-form-item>
+        <a-form-item field="img2txt" label="Img2txt模型">
+          <a-select v-model="form.img2txt_id" allow-clear>
+            <a-option></a-option>
+          </a-select>
+        </a-form-item>
+        <a-form-item field="sequence2txt" label="Sequence2txt模型">
+          <a-select v-model="form.asr_id" allow-clear>
+            <a-option></a-option>
+          </a-select>
+        </a-form-item>
+        <a-form-item field="rerank" label="Rerank模型">
+          <a-select v-model="form.rerank_id" allow-clear>
+            <a-optgroup :label="index" v-for="(item, index) in rankModelList" :key="index">
+              <a-option v-for="obj in item" :key="obj.fid" :disabled="!obj.available" :value="obj.llm_name">
+                {{ obj.llm_name }}
+              </a-option>
+            </a-optgroup>
+          </a-select>
+        </a-form-item>
+        <a-form-item field="tts" label="TTS模型">
+          <a-select v-model="form.tts_id" allow-clear>
+            <a-option></a-option>
+          </a-select>
+        </a-form-item>
+      </a-form>
+    </div>
+  </a-modal>
+</template>
+
+<script setup>
+import { ref, computed, onMounted } from 'vue';
+import { getSystemModelSettingRG, queryModelList, updateSystemModelSettingRG } from '../../../apis/rgflow-dify';
+import { Message } from '@arco-design/web-vue';
+const props = defineProps({
+  show_modal: Boolean
+});
+
+const emit = defineEmits(['ragClose']);
+
+const visible = computed(() => {
+  return props.show_modal;
+});
+
+const layout = ref('vertical');
+const form = ref({
+  llm_id: '',
+  embd_id: '',
+  img2txt_id: '',
+  asr_id: '',
+  rerank_id: '',
+  tts_id: ''
+});
+const modelList = ref();
+const innerModelList = ref();
+const rankModelList = ref();
+const loading = ref(false);
+
+const getSystemModelSettingRGFun = async () => {
+  try {
+    const res = await getSystemModelSettingRG();
+    if (res.code / 1 === 0) {
+      form.value = { ...res.data };
+    }
+  } catch (e) {
+    console.log(e);
+  }
+};
+const queryModel = async () => {
+  try {
+    const data = await queryModelList();
+    const arrObj = {};
+    for (const key in data.data) {
+      if (Object.hasOwnProperty.call(data.data, key)) {
+        const element = data.data[key];
+        // console.log(element, 'element');
+        // arrObj[key] = element.filter((item) => item.available === true && item.model_type === 'chat');
+        // 先注释掉model_type过滤  加上暂无可用数据
+        arrObj[key] = element.filter((item) => item.available === true);
+      }
+    }
+    modelList.value = arrObj;
+    innerModelList.value = {
+      BAAI: [data.data.BAAI[1]],
+      FastEmbed: data.data.FastEmbed,
+      Xinference: data.data.Xinference,
+      youdao: data.data.Youdao
+    };
+    rankModelList.value = {
+      BAAI: [data.data.BAAI[1]],
+      youdao: data.data.Youdao
+    };
+  } catch (err) {
+    // you can report use errorHandler or other
+  } finally {
+  }
+};
+
+const updateSystemModelSettingRGFun = async () => {
+  try {
+    loading.value = true;
+    const res = await updateSystemModelSettingRG(form.value);
+    if (res.code / 1 == 0 && res.data) {
+      Message.success('更新成功');
+      handleCancel();
+    } else {
+      Message.error(res.message);
+    }
+  } catch (e) {
+    console.log(e);
+  } finally {
+    loading.value = false;
+  }
+};
+
+const handleOk = () => {
+  updateSystemModelSettingRGFun();
+};
+const handleCancel = () => {
+  emit('ragClose');
+};
+onMounted(async () => {
+  await queryModel();
+  await getSystemModelSettingRGFun();
+});
+</script>
+<style scoped lang="css"></style>

+ 12 - 3
src/views/Model.vue

@@ -3,18 +3,27 @@
     <ContentContainer>
       <HeadManage :title="$t('management.modelTitle')" :tips="$t('management.modelDesc')">
         <template v-slot:setting>
-          <TopSetting></TopSetting>
+          <TopSetting @changeType="changeType"></TopSetting>
         </template>
       </HeadManage>
-      <MessageBody> </MessageBody>
+      <MessageBodyRG v-if="current_type === 'assistant'"> </MessageBodyRG>
+      <MessageBodyDIFY v-if="current_type === 'agent'"></MessageBodyDIFY>
     </ContentContainer>
   </LayoutExperience>
 </template>
 
 <script setup>
+import { ref } from 'vue';
 import LayoutExperience from './layout/LayoutExperience.vue';
 import ContentContainer from './layout/ContentContainer.vue';
 import HeadManage from './manage/HeadManage.vue';
 import TopSetting from '../modules/model-manage/TopSetting.vue';
-import MessageBody from '../modules/model-manage/MessageBody.vue';
+import MessageBodyRG from '../modules/model-manage/ragflow/MessageBody.vue';
+import MessageBodyDIFY from '../modules/model-manage/dify/MessageBody.vue';
+
+const current_type = ref('assistant');
+const changeType = (value) => {
+  current_type.value = value;
+  console.log(current_type.value);
+};
 </script>

+ 3 - 2
vite.config.mjs

@@ -48,6 +48,7 @@ export default defineConfig(({ mode }) => {
 
         '/dify': {
           target: 'http://192.168.20.119:28003/',
+          // target: 'http://192.168.20.119:13002',
           changeOrigin: true,
           secure: false,
           ws: true,
@@ -55,8 +56,8 @@ export default defineConfig(({ mode }) => {
         },
         '/rgflow': {
           // target: 'http://192.168.20.119:11080',
-          // target: 'http://smartai.com:8294/',
-          target: 'http://192.168.20.119:28002/',
+          target: 'http://smartai.com:8294/', //119 rag
+          // target: 'http://192.168.20.116:28002/',
           // target: 'http://47.96.40.148:8162/',
           changeOrigin: true,
           secure: false,

Nem az összes módosított fájl került megjelenítésre, mert túl sok fájl változott