Compare commits
10 Commits
082cfaaada
...
aed0168795
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aed0168795 | ||
|
|
e715e244af | ||
|
|
c8ec7c5558 | ||
|
|
27aeba47c4 | ||
|
|
e9434b6ba6 | ||
|
|
8c5201b901 | ||
|
|
80f53ec8fb | ||
|
|
aac0f297df | ||
|
|
ee494a5bc0 | ||
|
|
c83462ef74 |
16
.github/workflows/build-and-push.yml
vendored
16
.github/workflows/build-and-push.yml
vendored
@ -38,6 +38,8 @@ jobs:
|
|||||||
if: ${{ contains(github.event.inputs.registry, 'fit2cloud') }}
|
if: ${{ contains(github.event.inputs.registry, 'fit2cloud') }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
- name: Clear Work Dir
|
||||||
|
run: rm -rf -- ./* ./.??*
|
||||||
- name: Check Disk Space
|
- name: Check Disk Space
|
||||||
run: df -h
|
run: df -h
|
||||||
- name: Free Disk Space (Ubuntu)
|
- name: Free Disk Space (Ubuntu)
|
||||||
@ -90,6 +92,12 @@ jobs:
|
|||||||
registry: ${{ secrets.FIT2CLOUD_REGISTRY_HOST }}
|
registry: ${{ secrets.FIT2CLOUD_REGISTRY_HOST }}
|
||||||
username: ${{ secrets.FIT2CLOUD_REGISTRY_USERNAME }}
|
username: ${{ secrets.FIT2CLOUD_REGISTRY_USERNAME }}
|
||||||
password: ${{ secrets.FIT2CLOUD_REGISTRY_PASSWORD }}
|
password: ${{ secrets.FIT2CLOUD_REGISTRY_PASSWORD }}
|
||||||
|
- name: Build Web
|
||||||
|
run: |
|
||||||
|
docker buildx build --no-cache --target web-build --output type=local,dest=./web-build-output . -f installer/Dockerfile
|
||||||
|
rm -rf ./ui
|
||||||
|
cp -r ./web-build-output/ui ./
|
||||||
|
rm -rf ./web-build-output
|
||||||
- name: Docker Buildx (build-and-push)
|
- name: Docker Buildx (build-and-push)
|
||||||
run: |
|
run: |
|
||||||
sudo sync && echo 3 | sudo tee /proc/sys/vm/drop_caches && free -m
|
sudo sync && echo 3 | sudo tee /proc/sys/vm/drop_caches && free -m
|
||||||
@ -99,6 +107,8 @@ jobs:
|
|||||||
if: ${{ contains(github.event.inputs.registry, 'dockerhub') }}
|
if: ${{ contains(github.event.inputs.registry, 'dockerhub') }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
- name: Clear Work Dir
|
||||||
|
run: rm -rf -- ./* ./.??*
|
||||||
- name: Check Disk Space
|
- name: Check Disk Space
|
||||||
run: df -h
|
run: df -h
|
||||||
- name: Free Disk Space (Ubuntu)
|
- name: Free Disk Space (Ubuntu)
|
||||||
@ -150,6 +160,12 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
- name: Build Web
|
||||||
|
run: |
|
||||||
|
docker buildx build --no-cache --target web-build --output type=local,dest=./web-build-output . -f installer/Dockerfile
|
||||||
|
rm -rf ./ui
|
||||||
|
cp -r ./web-build-output/ui ./
|
||||||
|
rm -rf ./web-build-output
|
||||||
- name: Docker Buildx (build-and-push)
|
- name: Docker Buildx (build-and-push)
|
||||||
run: |
|
run: |
|
||||||
sudo sync && echo 3 | sudo tee /proc/sys/vm/drop_caches && free -m
|
sudo sync && echo 3 | sudo tee /proc/sys/vm/drop_caches && free -m
|
||||||
|
|||||||
@ -8664,3 +8664,6 @@ msgstr ""
|
|||||||
|
|
||||||
msgid "The Qwen Audio based end-to-end speech recognition model supports audio recognition within 3 minutes. At present, it mainly supports Chinese and English recognition."
|
msgid "The Qwen Audio based end-to-end speech recognition model supports audio recognition within 3 minutes. At present, it mainly supports Chinese and English recognition."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "If not passed, the default value is 'zh'"
|
||||||
|
msgstr ""
|
||||||
@ -8790,3 +8790,6 @@ msgstr "资源授权"
|
|||||||
|
|
||||||
msgid "The Qwen Audio based end-to-end speech recognition model supports audio recognition within 3 minutes. At present, it mainly supports Chinese and English recognition."
|
msgid "The Qwen Audio based end-to-end speech recognition model supports audio recognition within 3 minutes. At present, it mainly supports Chinese and English recognition."
|
||||||
msgstr "基于Qwen-Audio的端到端语音识别大模型,支持3分钟以内的音频识别,目前主要支持中英文识别。"
|
msgstr "基于Qwen-Audio的端到端语音识别大模型,支持3分钟以内的音频识别,目前主要支持中英文识别。"
|
||||||
|
|
||||||
|
msgid "If not passed, the default value is 'zh'"
|
||||||
|
msgstr "如果未传递,则默认值为'zh'"
|
||||||
@ -8790,3 +8790,6 @@ msgstr "資源授權"
|
|||||||
|
|
||||||
msgid "The Qwen Audio based end-to-end speech recognition model supports audio recognition within 3 minutes. At present, it mainly supports Chinese and English recognition."
|
msgid "The Qwen Audio based end-to-end speech recognition model supports audio recognition within 3 minutes. At present, it mainly supports Chinese and English recognition."
|
||||||
msgstr "基於Qwen-Audio的端到端語音辨識大模型,支持3分鐘以內的音訊識別,現時主要支持中英文識別。"
|
msgstr "基於Qwen-Audio的端到端語音辨識大模型,支持3分鐘以內的音訊識別,現時主要支持中英文識別。"
|
||||||
|
|
||||||
|
msgid "If not passed, the default value is 'zh'"
|
||||||
|
msgstr "如果未傳遞,則預設值為'zh'"
|
||||||
@ -0,0 +1,62 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
import traceback
|
||||||
|
from typing import Dict
|
||||||
|
|
||||||
|
from django.utils.translation import gettext_lazy as _, gettext
|
||||||
|
from langchain_core.messages import HumanMessage
|
||||||
|
|
||||||
|
from common import forms
|
||||||
|
from common.exception.app_exception import AppApiException
|
||||||
|
from common.forms import BaseForm, TooltipLabel
|
||||||
|
from models_provider.base_model_provider import BaseModelCredential, ValidCode
|
||||||
|
|
||||||
|
|
||||||
|
class VLLMWhisperModelParams(BaseForm):
|
||||||
|
Language = forms.TextInputField(
|
||||||
|
TooltipLabel(_('Language'),
|
||||||
|
_("If not passed, the default value is 'zh'")),
|
||||||
|
required=True,
|
||||||
|
default_value='zh',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class VLLMWhisperModelCredential(BaseForm, BaseModelCredential):
|
||||||
|
api_url = forms.TextInputField('API URL', required=True)
|
||||||
|
api_key = forms.PasswordInputField('API Key', required=True)
|
||||||
|
|
||||||
|
def is_valid(self,
|
||||||
|
model_type: str,
|
||||||
|
model_name,
|
||||||
|
model_credential: Dict[str, object],
|
||||||
|
model_params,
|
||||||
|
provider,
|
||||||
|
raise_exception=False):
|
||||||
|
|
||||||
|
model_type_list = provider.get_model_type_list()
|
||||||
|
|
||||||
|
if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):
|
||||||
|
raise AppApiException(ValidCode.valid_error.value,
|
||||||
|
gettext('{model_type} Model type is not supported').format(model_type=model_type))
|
||||||
|
try:
|
||||||
|
model_list = provider.get_base_model_list(model_credential.get('api_url'), model_credential.get('api_key'))
|
||||||
|
except Exception as e:
|
||||||
|
raise AppApiException(ValidCode.valid_error.value, gettext('API domain name is invalid'))
|
||||||
|
exist = provider.get_model_info_by_name(model_list, model_name)
|
||||||
|
if len(exist) == 0:
|
||||||
|
raise AppApiException(ValidCode.valid_error.value,
|
||||||
|
gettext('The model does not exist, please download the model first'))
|
||||||
|
model = provider.get_model(model_type, model_name, model_credential, **model_params)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def encryption_dict(self, model_info: Dict[str, object]):
|
||||||
|
return {**model_info, 'api_key': super().encryption(model_info.get('api_key', ''))}
|
||||||
|
|
||||||
|
def build_model(self, model_info: Dict[str, object]):
|
||||||
|
for key in ['api_key', 'model']:
|
||||||
|
if key not in model_info:
|
||||||
|
raise AppApiException(500, gettext('{key} is required').format(key=key))
|
||||||
|
self.api_key = model_info.get('api_key')
|
||||||
|
return self
|
||||||
|
|
||||||
|
def get_model_params_setting_form(self, model_name):
|
||||||
|
return VLLMWhisperModelParams()
|
||||||
Binary file not shown.
@ -0,0 +1,64 @@
|
|||||||
|
import base64
|
||||||
|
import os
|
||||||
|
import traceback
|
||||||
|
from typing import Dict
|
||||||
|
|
||||||
|
from openai import OpenAI
|
||||||
|
|
||||||
|
from common.utils.logger import maxkb_logger
|
||||||
|
from models_provider.base_model_provider import MaxKBBaseModel
|
||||||
|
from models_provider.impl.base_stt import BaseSpeechToText
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class VllmWhisperSpeechToText(MaxKBBaseModel, BaseSpeechToText):
|
||||||
|
api_key: str
|
||||||
|
api_url: str
|
||||||
|
model: str
|
||||||
|
params: dict
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
self.api_key = kwargs.get('api_key')
|
||||||
|
self.model = kwargs.get('model')
|
||||||
|
self.params = kwargs.get('params')
|
||||||
|
self.api_url = kwargs.get('api_url')
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def is_cache_model():
|
||||||
|
return False
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):
|
||||||
|
return VllmWhisperSpeechToText(
|
||||||
|
model=model_name,
|
||||||
|
api_key=model_credential.get('api_key'),
|
||||||
|
api_url=model_credential.get('api_url'),
|
||||||
|
params=model_kwargs,
|
||||||
|
**model_kwargs
|
||||||
|
)
|
||||||
|
|
||||||
|
def check_auth(self):
|
||||||
|
cwd = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
with open(f'{cwd}/iat_mp3_16k.mp3', 'rb') as audio_file:
|
||||||
|
self.speech_to_text(audio_file)
|
||||||
|
|
||||||
|
def speech_to_text(self, audio_file):
|
||||||
|
base_url = f"{self.api_url}/v1"
|
||||||
|
try:
|
||||||
|
client = OpenAI(
|
||||||
|
api_key=self.api_key,
|
||||||
|
base_url=base_url
|
||||||
|
)
|
||||||
|
|
||||||
|
result = client.audio.transcriptions.create(
|
||||||
|
file=audio_file,
|
||||||
|
model=self.model,
|
||||||
|
language=self.params.get('Language'),
|
||||||
|
response_format="json"
|
||||||
|
)
|
||||||
|
|
||||||
|
return result.text
|
||||||
|
|
||||||
|
except Exception as err:
|
||||||
|
maxkb_logger.error(f":Error: {str(err)}: {traceback.format_exc()}")
|
||||||
@ -10,20 +10,27 @@ from models_provider.base_model_provider import IModelProvider, ModelProvideInfo
|
|||||||
from models_provider.impl.vllm_model_provider.credential.embedding import VllmEmbeddingCredential
|
from models_provider.impl.vllm_model_provider.credential.embedding import VllmEmbeddingCredential
|
||||||
from models_provider.impl.vllm_model_provider.credential.image import VllmImageModelCredential
|
from models_provider.impl.vllm_model_provider.credential.image import VllmImageModelCredential
|
||||||
from models_provider.impl.vllm_model_provider.credential.llm import VLLMModelCredential
|
from models_provider.impl.vllm_model_provider.credential.llm import VLLMModelCredential
|
||||||
|
from models_provider.impl.vllm_model_provider.credential.whisper_stt import VLLMWhisperModelCredential
|
||||||
from models_provider.impl.vllm_model_provider.model.embedding import VllmEmbeddingModel
|
from models_provider.impl.vllm_model_provider.model.embedding import VllmEmbeddingModel
|
||||||
from models_provider.impl.vllm_model_provider.model.image import VllmImage
|
from models_provider.impl.vllm_model_provider.model.image import VllmImage
|
||||||
from models_provider.impl.vllm_model_provider.model.llm import VllmChatModel
|
from models_provider.impl.vllm_model_provider.model.llm import VllmChatModel
|
||||||
from maxkb.conf import PROJECT_DIR
|
from maxkb.conf import PROJECT_DIR
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
|
from models_provider.impl.vllm_model_provider.model.whisper_sst import VllmWhisperSpeechToText
|
||||||
|
|
||||||
v_llm_model_credential = VLLMModelCredential()
|
v_llm_model_credential = VLLMModelCredential()
|
||||||
image_model_credential = VllmImageModelCredential()
|
image_model_credential = VllmImageModelCredential()
|
||||||
embedding_model_credential = VllmEmbeddingCredential()
|
embedding_model_credential = VllmEmbeddingCredential()
|
||||||
|
whisper_model_credential = VLLMWhisperModelCredential()
|
||||||
|
|
||||||
model_info_list = [
|
model_info_list = [
|
||||||
ModelInfo('facebook/opt-125m', _('Facebook’s 125M parameter model'), ModelTypeConst.LLM, v_llm_model_credential, VllmChatModel),
|
ModelInfo('facebook/opt-125m', _('Facebook’s 125M parameter model'), ModelTypeConst.LLM, v_llm_model_credential,
|
||||||
ModelInfo('BAAI/Aquila-7B', _('BAAI’s 7B parameter model'), ModelTypeConst.LLM, v_llm_model_credential, VllmChatModel),
|
VllmChatModel),
|
||||||
ModelInfo('BAAI/AquilaChat-7B', _('BAAI’s 13B parameter mode'), ModelTypeConst.LLM, v_llm_model_credential, VllmChatModel),
|
ModelInfo('BAAI/Aquila-7B', _('BAAI’s 7B parameter model'), ModelTypeConst.LLM, v_llm_model_credential,
|
||||||
|
VllmChatModel),
|
||||||
|
ModelInfo('BAAI/AquilaChat-7B', _('BAAI’s 13B parameter mode'), ModelTypeConst.LLM, v_llm_model_credential,
|
||||||
|
VllmChatModel),
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -32,7 +39,15 @@ image_model_info_list = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
embedding_model_info_list = [
|
embedding_model_info_list = [
|
||||||
ModelInfo('HIT-TMG/KaLM-embedding-multilingual-mini-instruct-v1.5', '', ModelTypeConst.EMBEDDING, embedding_model_credential, VllmEmbeddingModel),
|
ModelInfo('HIT-TMG/KaLM-embedding-multilingual-mini-instruct-v1.5', '', ModelTypeConst.EMBEDDING,
|
||||||
|
embedding_model_credential, VllmEmbeddingModel),
|
||||||
|
]
|
||||||
|
|
||||||
|
whisper_model_info_list = [
|
||||||
|
ModelInfo('whisper-tiny', '', ModelTypeConst.STT, whisper_model_credential, VllmWhisperSpeechToText),
|
||||||
|
ModelInfo('whisper-large-v3-turbo', '', ModelTypeConst.STT, whisper_model_credential, VllmWhisperSpeechToText),
|
||||||
|
ModelInfo('whisper-small', '', ModelTypeConst.STT, whisper_model_credential, VllmWhisperSpeechToText),
|
||||||
|
ModelInfo('whisper-large-v3', '', ModelTypeConst.STT, whisper_model_credential, VllmWhisperSpeechToText),
|
||||||
]
|
]
|
||||||
|
|
||||||
model_info_manage = (
|
model_info_manage = (
|
||||||
@ -45,6 +60,8 @@ model_info_manage = (
|
|||||||
.append_default_model_info(image_model_info_list[0])
|
.append_default_model_info(image_model_info_list[0])
|
||||||
.append_model_info_list(embedding_model_info_list)
|
.append_model_info_list(embedding_model_info_list)
|
||||||
.append_default_model_info(embedding_model_info_list[0])
|
.append_default_model_info(embedding_model_info_list[0])
|
||||||
|
.append_model_info_list(whisper_model_info_list)
|
||||||
|
.append_default_model_info(whisper_model_info_list[0])
|
||||||
.build()
|
.build()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -106,11 +106,11 @@ class WenxinLLMModelCredential(BaseForm, BaseModelCredential):
|
|||||||
method='', )
|
method='', )
|
||||||
|
|
||||||
# v2版本字段
|
# v2版本字段
|
||||||
api_base = forms.TextInputField("API Base", required=False, relation_show_field_dict={"api_version": ["v2"]})
|
api_base = forms.TextInputField("API URL", required=True, relation_show_field_dict={"api_version": ["v2"]})
|
||||||
|
|
||||||
# v1版本字段
|
# v1版本字段
|
||||||
api_key = forms.PasswordInputField('API Key', required=False)
|
api_key = forms.PasswordInputField('API Key', required=True)
|
||||||
secret_key = forms.PasswordInputField("Secret Key", required=False,
|
secret_key = forms.PasswordInputField("Secret Key", required=True,
|
||||||
relation_show_field_dict={"api_version": ["v1"]})
|
relation_show_field_dict={"api_version": ["v1"]})
|
||||||
|
|
||||||
def get_model_params_setting_form(self, model_name):
|
def get_model_params_setting_form(self, model_name):
|
||||||
|
|||||||
@ -381,7 +381,7 @@ class ModelSerializer(serializers.Serializer):
|
|||||||
return [
|
return [
|
||||||
self._build_model_data(
|
self._build_model_data(
|
||||||
model
|
model
|
||||||
) for model in query_params.get('model_query_set').order_by("-create_time")
|
) for model in query_params.get('model_query_set')
|
||||||
]
|
]
|
||||||
|
|
||||||
def model_list(self, workspace_id, with_valid=True):
|
def model_list(self, workspace_id, with_valid=True):
|
||||||
@ -398,7 +398,7 @@ class ModelSerializer(serializers.Serializer):
|
|||||||
shared_queryset = get_authorized_model(shared_queryset, workspace_id)
|
shared_queryset = get_authorized_model(shared_queryset, workspace_id)
|
||||||
|
|
||||||
# 构建共享模型和普通模型列表
|
# 构建共享模型和普通模型列表
|
||||||
shared_model = [self._build_model_data(model) for model in shared_queryset.order_by("-create_time")]
|
shared_model = [self._build_model_data(model) for model in shared_queryset]
|
||||||
|
|
||||||
is_x_pack_ee = self.is_x_pack_ee()
|
is_x_pack_ee = self.is_x_pack_ee()
|
||||||
normal_model = native_search(
|
normal_model = native_search(
|
||||||
@ -429,6 +429,7 @@ class ModelSerializer(serializers.Serializer):
|
|||||||
queryset = queryset.filter(user_id=value)
|
queryset = queryset.filter(user_id=value)
|
||||||
else:
|
else:
|
||||||
queryset = queryset.filter(**{field: value})
|
queryset = queryset.filter(**{field: value})
|
||||||
|
queryset = queryset.order_by("-create_time")
|
||||||
return {
|
return {
|
||||||
'model_query_set': queryset,
|
'model_query_set': queryset,
|
||||||
'workspace_user_resource_permission_query_set': QuerySet(WorkspaceUserResourcePermission).filter(
|
'workspace_user_resource_permission_query_set': QuerySet(WorkspaceUserResourcePermission).filter(
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
FROM node:24-alpine AS web-build
|
FROM node:24-alpine AS web-build
|
||||||
COPY ui ui
|
COPY ui ui
|
||||||
RUN cd ui && \
|
RUN cd ui && ls -la && if [ -d "dist" ]; then exit 0; fi && \
|
||||||
npm install --prefer-offline --no-audit && \
|
npm install --prefer-offline --no-audit && \
|
||||||
npm install -D concurrently && \
|
npm install -D concurrently && \
|
||||||
NODE_OPTIONS="--max-old-space-size=4096" npx concurrently "npm run build" "npm run build-chat" && \
|
NODE_OPTIONS="--max-old-space-size=4096" npx concurrently "npm run build" "npm run build-chat" && \
|
||||||
|
|||||||
@ -421,16 +421,29 @@ const uploadFile = async (file: any, fileList: any) => {
|
|||||||
uploadAudioList.value.length +
|
uploadAudioList.value.length +
|
||||||
uploadVideoList.value.length +
|
uploadVideoList.value.length +
|
||||||
uploadOtherList.value.length
|
uploadOtherList.value.length
|
||||||
|
|
||||||
if (file_limit_once >= maxFiles) {
|
if (file_limit_once >= maxFiles) {
|
||||||
MsgWarning(t('chat.uploadFile.limitMessage1') + maxFiles + t('chat.uploadFile.limitMessage2'))
|
MsgWarning(t('chat.uploadFile.limitMessage1') + maxFiles + t('chat.uploadFile.limitMessage2'))
|
||||||
fileList.splice(0, fileList.length, ...fileList.slice(0, maxFiles))
|
fileList.splice(0, fileList.length, ...fileList.slice(0, maxFiles))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
console.log(fileList)
|
||||||
|
if (fileList.filter((f: any) => f.size == 0).length > 0) {
|
||||||
|
// MB
|
||||||
|
MsgWarning(t('chat.uploadFile.sizeLimit2') + fileLimit + 'MB')
|
||||||
|
// 只保留未超出大小限制的文件
|
||||||
|
fileList.splice(0, fileList.length, ...fileList.filter((f: any) => f.size > 0))
|
||||||
|
return
|
||||||
|
}
|
||||||
if (fileList.filter((f: any) => f.size > fileLimit * 1024 * 1024).length > 0) {
|
if (fileList.filter((f: any) => f.size > fileLimit * 1024 * 1024).length > 0) {
|
||||||
// MB
|
// MB
|
||||||
MsgWarning(t('chat.uploadFile.sizeLimit') + fileLimit + 'MB')
|
MsgWarning(t('chat.uploadFile.sizeLimit') + fileLimit + 'MB')
|
||||||
// 只保留未超出大小限制的文件
|
// 只保留未超出大小限制的文件
|
||||||
fileList.splice(0, fileList.length, ...fileList.filter((f: any) => f.size <= fileLimit * 1024 * 1024))
|
fileList.splice(
|
||||||
|
0,
|
||||||
|
fileList.length,
|
||||||
|
...fileList.filter((f: any) => f.size <= fileLimit * 1024 * 1024),
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const inner = reactive(file)
|
const inner = reactive(file)
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-upload
|
<el-upload
|
||||||
style="width: 80%"
|
style="width: 100%"
|
||||||
v-loading="loading"
|
v-loading="loading"
|
||||||
action="#"
|
action="#"
|
||||||
v-bind="$attrs"
|
v-bind="$attrs"
|
||||||
@ -10,26 +10,26 @@
|
|||||||
multiple
|
multiple
|
||||||
>
|
>
|
||||||
<el-button type="primary">{{ $t('chat.uploadFile.label') }}</el-button>
|
<el-button type="primary">{{ $t('chat.uploadFile.label') }}</el-button>
|
||||||
<template #file="{ file, index }"
|
<template #file="{ file }">
|
||||||
><el-card style="--el-card-padding: 0" shadow="never">
|
<el-card style="--el-card-padding: 0" shadow="never">
|
||||||
<div
|
<div
|
||||||
|
class="flex-between"
|
||||||
:class="[inputDisabled ? 'is-disabled' : '']"
|
:class="[inputDisabled ? 'is-disabled' : '']"
|
||||||
style="
|
style="padding: 0 8px 0 8px"
|
||||||
padding: 0 8px 0 8px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
align-content: center;
|
|
||||||
"
|
|
||||||
>
|
>
|
||||||
<el-tooltip class="box-item" effect="dark" :content="file.name" placement="top-start">
|
<div class="flex align-center" style="width: 70%">
|
||||||
<div style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis; width: 40%">
|
<img :src="getImgUrl(file && file?.name)" alt="" width="24" class="mr-4" />
|
||||||
|
<span class="ellipsis-1" :title="file.name">
|
||||||
{{ file.name }}
|
{{ file.name }}
|
||||||
</div></el-tooltip
|
</span>
|
||||||
>
|
</div>
|
||||||
|
<div class="flex align-center">
|
||||||
<div>{{ formatSize(file.size) }}</div>
|
<div>{{ formatSize(file.size) }}</div>
|
||||||
<el-icon @click="deleteFile(file)" style="cursor: pointer"><DeleteFilled /></el-icon>
|
|
||||||
|
<el-button link class="ml-8" @click="deleteFile(file)">
|
||||||
|
<AppIcon iconName="app-delete"></AppIcon>
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
</template>
|
</template>
|
||||||
@ -39,6 +39,7 @@
|
|||||||
import { computed, inject, ref, useAttrs } from 'vue'
|
import { computed, inject, ref, useAttrs } from 'vue'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
import type { FormField } from '@/components/dynamics-form/type'
|
import type { FormField } from '@/components/dynamics-form/type'
|
||||||
|
import { getImgUrl } from '@/utils/common'
|
||||||
import { t } from '@/locales'
|
import { t } from '@/locales'
|
||||||
import { useFormDisabled } from 'element-plus'
|
import { useFormDisabled } from 'element-plus'
|
||||||
const inputDisabled = useFormDisabled()
|
const inputDisabled = useFormDisabled()
|
||||||
@ -71,7 +72,7 @@ const deleteFile = (file: any) => {
|
|||||||
|
|
||||||
const model_value = computed({
|
const model_value = computed({
|
||||||
get: () => {
|
get: () => {
|
||||||
if (!model_value) {
|
if (!model_value.value) {
|
||||||
emit('update:modelValue', [])
|
emit('update:modelValue', [])
|
||||||
}
|
}
|
||||||
return props.modelValue
|
return props.modelValue
|
||||||
|
|||||||
@ -66,6 +66,7 @@ export default {
|
|||||||
limitMessage1: 'You can upload up to',
|
limitMessage1: 'You can upload up to',
|
||||||
limitMessage2: 'files',
|
limitMessage2: 'files',
|
||||||
sizeLimit: 'Each file must not exceed',
|
sizeLimit: 'Each file must not exceed',
|
||||||
|
sizeLimit2: 'Empty files are not supported for upload',
|
||||||
imageMessage: 'Please process the image content',
|
imageMessage: 'Please process the image content',
|
||||||
fileMessage: 'Please process the file content',
|
fileMessage: 'Please process the file content',
|
||||||
errorMessage: 'Upload Failed',
|
errorMessage: 'Upload Failed',
|
||||||
|
|||||||
@ -3,8 +3,10 @@ export default {
|
|||||||
syncUsers: 'Sync Users',
|
syncUsers: 'Sync Users',
|
||||||
syncUsersTip: 'Only sync newly added users',
|
syncUsersTip: 'Only sync newly added users',
|
||||||
setUserGroups: 'Configure User Groups',
|
setUserGroups: 'Configure User Groups',
|
||||||
knowledgeTitleTip: 'This configuration will only take effect after enabling chat user login authentication in the associated application',
|
knowledgeTitleTip:
|
||||||
applicationTitleTip: 'This configuration requires login authentication to be enabled in the application',
|
'This configuration will only take effect after enabling chat user login authentication in the associated application',
|
||||||
|
applicationTitleTip:
|
||||||
|
'This configuration requires login authentication to be enabled in the application',
|
||||||
autoAuthorization: 'Auto Authorization',
|
autoAuthorization: 'Auto Authorization',
|
||||||
authorization: 'Authorization',
|
authorization: 'Authorization',
|
||||||
batchDeleteUser: 'Delete selected {count} users?',
|
batchDeleteUser: 'Delete selected {count} users?',
|
||||||
@ -14,10 +16,12 @@ export default {
|
|||||||
group: {
|
group: {
|
||||||
title: 'User Groups',
|
title: 'User Groups',
|
||||||
name: 'User Group Name',
|
name: 'User Group Name',
|
||||||
|
requiredMessage: 'Please select user group',
|
||||||
usernameOrName: 'Username/Name',
|
usernameOrName: 'Username/Name',
|
||||||
delete: {
|
delete: {
|
||||||
confirmTitle: 'Confirm to delete user group:',
|
confirmTitle: 'Confirm to delete user group:',
|
||||||
confirmMessage: 'All members in this group will be removed after deletion. Proceed with caution!',
|
confirmMessage:
|
||||||
|
'All members in this group will be removed after deletion. Proceed with caution!',
|
||||||
},
|
},
|
||||||
batchDeleteMember: 'Remove selected {count} members?',
|
batchDeleteMember: 'Remove selected {count} members?',
|
||||||
},
|
},
|
||||||
@ -25,5 +29,5 @@ export default {
|
|||||||
title: 'Successfully synced {count} users',
|
title: 'Successfully synced {count} users',
|
||||||
usernameExist: 'The following usernames already exist:',
|
usernameExist: 'The following usernames already exist:',
|
||||||
nicknameExist: 'The following nicknames already exist:',
|
nicknameExist: 'The following nicknames already exist:',
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,8 +3,11 @@ export default {
|
|||||||
all: 'All',
|
all: 'All',
|
||||||
createTool: 'Create Tool',
|
createTool: 'Create Tool',
|
||||||
editTool: 'Edit Tool',
|
editTool: 'Edit Tool',
|
||||||
|
createMcpTool: 'Create MCP',
|
||||||
|
editMcpTool: 'Edit MCP',
|
||||||
copyTool: 'Copy Tool',
|
copyTool: 'Copy Tool',
|
||||||
importTool: 'Import Tool',
|
importTool: 'Import Tool',
|
||||||
|
settingTool: 'Set Tool',
|
||||||
toolStore: {
|
toolStore: {
|
||||||
title: 'Tool Store',
|
title: 'Tool Store',
|
||||||
createFromToolStore: 'Create from Tool Store',
|
createFromToolStore: 'Create from Tool Store',
|
||||||
|
|||||||
@ -68,6 +68,7 @@ export default {
|
|||||||
limitMessage1: '最多上传',
|
limitMessage1: '最多上传',
|
||||||
limitMessage2: '个文件',
|
limitMessage2: '个文件',
|
||||||
sizeLimit: '单个文件大小不能超过',
|
sizeLimit: '单个文件大小不能超过',
|
||||||
|
sizeLimit2: '空文件不支持上传',
|
||||||
imageMessage: '请解析图片内容',
|
imageMessage: '请解析图片内容',
|
||||||
fileMessage: '请解析文件内容',
|
fileMessage: '请解析文件内容',
|
||||||
errorMessage: '上传失败',
|
errorMessage: '上传失败',
|
||||||
|
|||||||
@ -13,6 +13,7 @@ export default {
|
|||||||
replace: '替换',
|
replace: '替换',
|
||||||
group: {
|
group: {
|
||||||
title: '用户组',
|
title: '用户组',
|
||||||
|
requiredMessage: '请选择用户组',
|
||||||
name: '用户组名称',
|
name: '用户组名称',
|
||||||
usernameOrName: '用户名/姓名',
|
usernameOrName: '用户名/姓名',
|
||||||
delete: {
|
delete: {
|
||||||
@ -25,5 +26,5 @@ export default {
|
|||||||
title: '成功同步 {count} 个用户',
|
title: '成功同步 {count} 个用户',
|
||||||
usernameExist: '以下用户名已存在:',
|
usernameExist: '以下用户名已存在:',
|
||||||
nicknameExist: '以下姓名已存在:',
|
nicknameExist: '以下姓名已存在:',
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,6 +7,7 @@ export default {
|
|||||||
editMcpTool: '编辑MCP',
|
editMcpTool: '编辑MCP',
|
||||||
copyTool: '复制工具',
|
copyTool: '复制工具',
|
||||||
importTool: '导入工具',
|
importTool: '导入工具',
|
||||||
|
settingTool: '设置工具',
|
||||||
toolStore: {
|
toolStore: {
|
||||||
title: '工具商店',
|
title: '工具商店',
|
||||||
createFromToolStore: '从工具商店创建',
|
createFromToolStore: '从工具商店创建',
|
||||||
|
|||||||
@ -64,6 +64,7 @@ export default {
|
|||||||
limitMessage1: '最多上傳',
|
limitMessage1: '最多上傳',
|
||||||
limitMessage2: '個文件',
|
limitMessage2: '個文件',
|
||||||
sizeLimit: '單個文件大小不能超過',
|
sizeLimit: '單個文件大小不能超過',
|
||||||
|
sizeLimit2: '空文件不支持上傳',
|
||||||
imageMessage: '請解析圖片內容',
|
imageMessage: '請解析圖片內容',
|
||||||
fileMessage: '請解析文件內容',
|
fileMessage: '請解析文件內容',
|
||||||
errorMessage: '上傳失敗',
|
errorMessage: '上傳失敗',
|
||||||
|
|||||||
@ -13,6 +13,7 @@ export default {
|
|||||||
replace: '替換',
|
replace: '替換',
|
||||||
group: {
|
group: {
|
||||||
title: '用戶組',
|
title: '用戶組',
|
||||||
|
requiredMessage: '請選擇用戶組',
|
||||||
name: '用戶組名稱',
|
name: '用戶組名稱',
|
||||||
usernameOrName: '用戶名/姓名',
|
usernameOrName: '用戶名/姓名',
|
||||||
delete: {
|
delete: {
|
||||||
@ -25,5 +26,5 @@ export default {
|
|||||||
title: '成功同步 {count} 個用戶',
|
title: '成功同步 {count} 個用戶',
|
||||||
usernameExist: '以下用戶名已存在:',
|
usernameExist: '以下用戶名已存在:',
|
||||||
nicknameExist: '以下姓名已存在:',
|
nicknameExist: '以下姓名已存在:',
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,8 +3,11 @@ export default {
|
|||||||
all: '全部',
|
all: '全部',
|
||||||
createTool: '建立工具',
|
createTool: '建立工具',
|
||||||
editTool: '編輯工具',
|
editTool: '編輯工具',
|
||||||
|
createMcpTool: '建立MCP',
|
||||||
|
editMcpTool: '編輯MCP',
|
||||||
copyTool: '複製工具',
|
copyTool: '複製工具',
|
||||||
importTool: '匯入工具',
|
importTool: '匯入工具',
|
||||||
|
settingTool: '設定工具',
|
||||||
toolStore: {
|
toolStore: {
|
||||||
title: '工具商店',
|
title: '工具商店',
|
||||||
createFromToolStore: '從工具商店創建',
|
createFromToolStore: '從工具商店創建',
|
||||||
|
|||||||
@ -56,7 +56,7 @@ div:focus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ul {
|
ul {
|
||||||
list-style: circle;
|
list-style: none;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -287,3 +287,13 @@
|
|||||||
.el-input {
|
.el-input {
|
||||||
--el-input-text-color: var(--el-text-color-primary);
|
--el-input-text-color: var(--el-text-color-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.el-input-group__prepend div.el-select .el-select__wrapper {
|
||||||
|
background: #ffffff;
|
||||||
|
&:hover {
|
||||||
|
background: #ffffff;
|
||||||
|
}
|
||||||
|
.el-select__placeholder {
|
||||||
|
color: var(--el-text-color-regular);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -21,6 +21,9 @@
|
|||||||
border: 0 !important;
|
border: 0 !important;
|
||||||
max-width: 360px !important;
|
max-width: 360px !important;
|
||||||
}
|
}
|
||||||
|
ul {
|
||||||
|
list-style: circle;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: 768px) {
|
@media only screen and (max-width: 768px) {
|
||||||
|
|||||||
@ -17,7 +17,7 @@
|
|||||||
@submit.prevent
|
@submit.prevent
|
||||||
>
|
>
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<el-radio-group v-model="form.mcp_source">
|
<el-radio-group v-model="form.mcp_source" @change="mcpSourceChange">
|
||||||
<el-radio value="referencing">
|
<el-radio value="referencing">
|
||||||
{{ $t('views.applicationWorkflow.nodes.mcpNode.reference') }}
|
{{ $t('views.applicationWorkflow.nodes.mcpNode.reference') }}
|
||||||
</el-radio>
|
</el-radio>
|
||||||
@ -100,17 +100,8 @@ import { computed, inject, onMounted, ref, watch } from 'vue'
|
|||||||
import { loadSharedApi } from '@/utils/dynamics-api/shared-api.ts'
|
import { loadSharedApi } from '@/utils/dynamics-api/shared-api.ts'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
|
|
||||||
const getApplicationDetail = inject('getApplicationDetail') as any
|
|
||||||
const applicationDetail = getApplicationDetail()
|
|
||||||
const emit = defineEmits(['refresh'])
|
const emit = defineEmits(['refresh'])
|
||||||
const route = useRoute()
|
|
||||||
const apiType = computed(() => {
|
|
||||||
if (route.path.includes('resource-management')) {
|
|
||||||
return 'systemManage'
|
|
||||||
} else {
|
|
||||||
return 'workspace'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
const paramFormRef = ref()
|
const paramFormRef = ref()
|
||||||
|
|
||||||
const mcpServerJson = `{
|
const mcpServerJson = `{
|
||||||
@ -143,32 +134,20 @@ watch(dialogVisible, (bool) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
function getMcpToolSelectOptions() {
|
function mcpSourceChange() {
|
||||||
const obj =
|
if (form.value.mcp_source === 'referencing') {
|
||||||
apiType.value === 'systemManage'
|
form.value.mcp_servers = ''
|
||||||
? {
|
} else {
|
||||||
scope: 'WORKSPACE',
|
form.value.mcp_tool_id = ''
|
||||||
tool_type: 'MCP',
|
|
||||||
workspace_id: applicationDetail.value?.workspace_id,
|
|
||||||
}
|
}
|
||||||
: {
|
|
||||||
scope: 'WORKSPACE',
|
|
||||||
tool_type: 'MCP',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
loadSharedApi({ type: 'tool', systemType: apiType.value })
|
|
||||||
.getAllToolList(obj, loading)
|
|
||||||
.then((res: any) => {
|
|
||||||
mcpToolSelectOptions.value = [...res.data.shared_tools, ...res.data.tools].filter(
|
|
||||||
(item: any) => item.is_active,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const open = (data: any) => {
|
const open = (data: any, selectOptions: any) => {
|
||||||
form.value = { ...form.value, ...data }
|
form.value = { ...form.value, ...data }
|
||||||
form.value.mcp_source = data.mcp_source || 'referencing'
|
form.value.mcp_source = data.mcp_source || 'referencing'
|
||||||
dialogVisible.value = true
|
dialogVisible.value = true
|
||||||
|
mcpToolSelectOptions.value = selectOptions || []
|
||||||
}
|
}
|
||||||
|
|
||||||
const submit = () => {
|
const submit = () => {
|
||||||
@ -180,10 +159,6 @@ const submit = () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
getMcpToolSelectOptions()
|
|
||||||
})
|
|
||||||
|
|
||||||
defineExpose({ open })
|
defineExpose({ open })
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped></style>
|
<style lang="scss" scoped></style>
|
||||||
|
|||||||
@ -1,89 +1,227 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-dialog
|
<el-dialog
|
||||||
align-center
|
|
||||||
:title="$t('common.setting')"
|
|
||||||
v-model="dialogVisible"
|
v-model="dialogVisible"
|
||||||
style="width: 550px"
|
width="1000"
|
||||||
append-to-body
|
append-to-body
|
||||||
|
class="addTool-dialog"
|
||||||
|
align-center
|
||||||
:close-on-click-modal="false"
|
:close-on-click-modal="false"
|
||||||
:close-on-press-escape="false"
|
:close-on-press-escape="false"
|
||||||
>
|
>
|
||||||
<el-form
|
<template #header="{ titleId, titleClass }">
|
||||||
label-position="top"
|
<div class="flex-between mb-8">
|
||||||
ref="paramFormRef"
|
<div class="flex">
|
||||||
:model="form"
|
<h4 :id="titleId" :class="titleClass" class="mr-8">
|
||||||
require-asterisk-position="right"
|
{{ $t('views.tool.settingTool') }}
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-button link class="mr-24" @click="refresh">
|
||||||
|
<el-icon :size="18"><Refresh /></el-icon>
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<LayoutContainer class="application-manage">
|
||||||
|
<template #left>
|
||||||
|
<div class="p-8">
|
||||||
|
<folder-tree
|
||||||
|
:data="folderList"
|
||||||
|
:currentNodeKey="currentFolder?.id"
|
||||||
|
@handleNodeClick="folderClickHandle"
|
||||||
|
v-loading="folderLoading"
|
||||||
|
:canOperation="false"
|
||||||
|
showShared
|
||||||
|
:shareTitle="$t('views.shared.shared_tool')"
|
||||||
|
:treeStyle="{ height: 'calc(100vh - 240px)' }"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div class="layout-bg">
|
||||||
|
<div class="flex-between p-16 ml-8">
|
||||||
|
<h4>{{ currentFolder?.name }}</h4>
|
||||||
|
<el-input
|
||||||
|
v-model="searchValue"
|
||||||
|
:placeholder="$t('common.search')"
|
||||||
|
prefix-icon="Search"
|
||||||
|
class="w-240 mr-8"
|
||||||
|
clearable
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-scrollbar>
|
||||||
|
<div class="p-16-24 pt-0" style="height: calc(100vh - 200px)">
|
||||||
|
<el-row :gutter="12" v-loading="apiLoading" v-if="searchData.length">
|
||||||
|
<el-col :span="12" v-for="(item, index) in searchData" :key="index" class="mb-16">
|
||||||
|
<CardCheckbox
|
||||||
|
value-field="id"
|
||||||
|
:data="item"
|
||||||
|
v-model="checkList"
|
||||||
|
@change="changeHandle"
|
||||||
>
|
>
|
||||||
<el-form-item>
|
<template #icon>
|
||||||
<el-select v-model="form.tool_ids" filterable multiple>
|
<el-avatar
|
||||||
<el-option
|
v-if="item?.icon"
|
||||||
v-for="mcpTool in toolSelectOptions"
|
shape="square"
|
||||||
:key="mcpTool.id"
|
:size="32"
|
||||||
:label="mcpTool.name"
|
style="background: none"
|
||||||
:value="mcpTool.id"
|
class="mr-8"
|
||||||
>
|
>
|
||||||
<span>{{ mcpTool.name }}</span>
|
<img :src="resetUrl(item?.icon)" alt="" />
|
||||||
<el-tag v-if="mcpTool.scope === 'SHARED'" type="info" class="info-tag ml-8 mt-4">
|
</el-avatar>
|
||||||
{{ $t('views.shared.title') }}
|
<ToolIcon v-else :size="32" :type="item?.tool_type" />
|
||||||
</el-tag>
|
</template>
|
||||||
</el-option>
|
<span class="ellipsis cursor ml-12" :title="item.name"> {{ item.name }}</span>
|
||||||
</el-select>
|
</CardCheckbox>
|
||||||
</el-form-item>
|
</el-col>
|
||||||
</el-form>
|
</el-row>
|
||||||
|
<el-empty :description="$t('common.noData')" v-else />
|
||||||
|
</div>
|
||||||
|
</el-scrollbar>
|
||||||
|
</div>
|
||||||
|
</LayoutContainer>
|
||||||
|
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<span class="dialog-footer">
|
<div class="flex-between">
|
||||||
<el-button @click.prevent="dialogVisible = false">{{ $t('common.cancel') }}</el-button>
|
<div class="flex">
|
||||||
<el-button type="primary" @click="submit()" :loading="loading">
|
<el-text type="info" class="color-secondary mr-8" v-if="checkList.length > 0">
|
||||||
{{ $t('common.save') }}
|
{{ $t('common.selected') }} {{ checkList.length }}
|
||||||
|
</el-text>
|
||||||
|
<el-button link type="primary" v-if="checkList.length > 0" @click="clearCheck">
|
||||||
|
{{ $t('common.clear') }}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
<span>
|
||||||
|
<el-button @click.prevent="dialogVisible = false">
|
||||||
|
{{ $t('common.cancel') }}
|
||||||
|
</el-button>
|
||||||
|
<el-button type="primary" @click="submitHandle">
|
||||||
|
{{ $t('common.add') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
</span>
|
</span>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {ref, watch} from 'vue'
|
import { computed, ref, watch } from 'vue'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
import useStore from '@/stores'
|
||||||
|
import { loadSharedApi } from '@/utils/dynamics-api/shared-api'
|
||||||
|
import { uniqueArray } from '@/utils/array'
|
||||||
|
import { resetUrl } from '@/utils/common'
|
||||||
|
const route = useRoute()
|
||||||
|
|
||||||
const emit = defineEmits(['refresh'])
|
const emit = defineEmits(['refresh'])
|
||||||
|
const { folder, user } = useStore()
|
||||||
const paramFormRef = ref()
|
const apiType = computed(() => {
|
||||||
|
if (route.path.includes('shared')) {
|
||||||
const form = ref<any>({
|
return 'systemShare'
|
||||||
tool_ids: [],
|
} else if (route.path.includes('resource-management')) {
|
||||||
|
return 'systemManage'
|
||||||
|
} else {
|
||||||
|
return 'workspace'
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const toolSelectOptions = ref<any[]>([])
|
|
||||||
|
|
||||||
const dialogVisible = ref<boolean>(false)
|
const dialogVisible = ref<boolean>(false)
|
||||||
|
const checkList = ref<Array<string>>([])
|
||||||
const loading = ref(false)
|
const searchValue = ref('')
|
||||||
|
const searchData = ref<Array<any>>([])
|
||||||
|
const toolList = ref<Array<any>>([])
|
||||||
|
const apiLoading = ref(false)
|
||||||
|
|
||||||
watch(dialogVisible, (bool) => {
|
watch(dialogVisible, (bool) => {
|
||||||
if (!bool) {
|
if (!bool) {
|
||||||
form.value = {
|
checkList.value = []
|
||||||
tool_ids: [],
|
searchValue.value = ''
|
||||||
}
|
searchData.value = []
|
||||||
|
toolList.value = []
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
watch(searchValue, (val) => {
|
||||||
|
if (val) {
|
||||||
|
searchData.value = toolList.value.filter((v) => v.name.includes(val))
|
||||||
|
} else {
|
||||||
|
searchData.value = toolList.value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const open = (data: any, selectOptions: any) => {
|
function changeHandle() {}
|
||||||
form.value = {...form.value, ...data}
|
function clearCheck() {
|
||||||
dialogVisible.value = true
|
checkList.value = []
|
||||||
toolSelectOptions.value = selectOptions
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const submit = () => {
|
const open = (checked: any) => {
|
||||||
paramFormRef.value.validate().then((valid: any) => {
|
checkList.value = checked || []
|
||||||
if (valid) {
|
getFolder()
|
||||||
emit('refresh', form.value)
|
dialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const submitHandle = () => {
|
||||||
|
emit('refresh', {
|
||||||
|
tool_ids: checkList.value,
|
||||||
|
})
|
||||||
dialogVisible.value = false
|
dialogVisible.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const refresh = () => {
|
||||||
|
searchValue.value = ''
|
||||||
|
toolList.value = []
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
|
||||||
|
const folderList = ref<any[]>([])
|
||||||
|
const currentFolder = ref<any>({})
|
||||||
|
const folderLoading = ref(false)
|
||||||
|
// 文件
|
||||||
|
function folderClickHandle(row: any) {
|
||||||
|
if (row.id === currentFolder.value?.id) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
currentFolder.value = row
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFolder() {
|
||||||
|
const params = {}
|
||||||
|
folder.asyncGetFolder('TOOL', params, folderLoading).then((res: any) => {
|
||||||
|
folderList.value = res.data
|
||||||
|
currentFolder.value = res.data?.[0] || {}
|
||||||
|
getList()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getList() {
|
||||||
|
const folder_id = currentFolder.value?.id || user.getWorkspaceId()
|
||||||
|
loadSharedApi({
|
||||||
|
type: 'tool',
|
||||||
|
systemType: apiType.value,
|
||||||
|
})
|
||||||
|
.getToolList({ folder_id }, apiLoading)
|
||||||
|
.then((res: any) => {
|
||||||
|
toolList.value = uniqueArray([...toolList.value, ...res.data.tools], 'id')
|
||||||
|
searchData.value = res.data.tools
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
defineExpose({ open })
|
defineExpose({ open })
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped></style>
|
<style lang="scss">
|
||||||
|
.addTool-dialog {
|
||||||
|
padding: 0;
|
||||||
|
.el-dialog__header {
|
||||||
|
padding: 12px 20px 4px 24px;
|
||||||
|
border-bottom: 1px solid var(--el-border-color-light);
|
||||||
|
}
|
||||||
|
.el-dialog__footer {
|
||||||
|
padding: 12px 24px 12px 24px;
|
||||||
|
border-top: 1px solid var(--el-border-color-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-dialog__headerbtn {
|
||||||
|
top: 2px;
|
||||||
|
right: 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@ -22,7 +22,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<el-scrollbar>
|
<el-scrollbar>
|
||||||
<div class="hit-test-height">
|
<div :style="{ height: user.isExpire() ? 'calc(100vh - 340px)' : 'calc(100vh - 300px)' }">
|
||||||
<el-empty
|
<el-empty
|
||||||
v-if="first"
|
v-if="first"
|
||||||
:image="emptyImg"
|
:image="emptyImg"
|
||||||
@ -231,6 +231,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { nextTick, ref, onMounted, computed } from 'vue'
|
import { nextTick, ref, onMounted, computed } from 'vue'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
|
import useStore from '@/stores'
|
||||||
import { cloneDeep } from 'lodash'
|
import { cloneDeep } from 'lodash'
|
||||||
import ParagraphDialog from '@/views/paragraph/component/ParagraphDialog.vue'
|
import ParagraphDialog from '@/views/paragraph/component/ParagraphDialog.vue'
|
||||||
import { arraySort } from '@/utils/array'
|
import { arraySort } from '@/utils/array'
|
||||||
@ -241,6 +242,7 @@ const route = useRoute()
|
|||||||
const {
|
const {
|
||||||
params: { id },
|
params: { id },
|
||||||
} = route as any
|
} = route as any
|
||||||
|
const { user } = useStore()
|
||||||
const apiType = computed(() => {
|
const apiType = computed(() => {
|
||||||
if (route.path.includes('shared')) {
|
if (route.path.includes('shared')) {
|
||||||
return 'systemShare'
|
return 'systemShare'
|
||||||
@ -408,10 +410,6 @@ onMounted(() => {})
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
right: calc(var(--app-base-px) * 3);
|
right: calc(var(--app-base-px) * 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.hit-test-height {
|
|
||||||
height: calc(100vh - 300px);
|
|
||||||
}
|
|
||||||
.document-card {
|
.document-card {
|
||||||
height: 210px;
|
height: 210px;
|
||||||
border: 1px solid var(--app-layout-bg-color);
|
border: 1px solid var(--app-layout-bg-color);
|
||||||
|
|||||||
@ -160,7 +160,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</LayoutContainer>
|
</LayoutContainer>
|
||||||
|
|
||||||
<div class="mul-operation border-t w-full" v-if="isBatch === true">
|
<div class="mul-operation border-t w-full flex align-center" v-if="isBatch === true">
|
||||||
<el-button :disabled="multipleSelection.length === 0" @click="openGenerateDialog()">
|
<el-button :disabled="multipleSelection.length === 0" @click="openGenerateDialog()">
|
||||||
{{ $t('views.document.generateQuestion.title') }}
|
{{ $t('views.document.generateQuestion.title') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
@ -171,7 +171,7 @@
|
|||||||
<el-button :disabled="multipleSelection.length === 0" @click="deleteMulParagraph">
|
<el-button :disabled="multipleSelection.length === 0" @click="deleteMulParagraph">
|
||||||
{{ $t('common.delete') }}
|
{{ $t('common.delete') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
<span class="ml-8">
|
<span class="ml-24">
|
||||||
{{ $t('common.selected') }} {{ multipleSelection.length }}
|
{{ $t('common.selected') }} {{ multipleSelection.length }}
|
||||||
{{ $t('views.document.items') }}
|
{{ $t('views.document.items') }}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@ -51,7 +51,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, reactive, watch } from 'vue'
|
import { ref, reactive, watch } from 'vue'
|
||||||
import type { FormInstance } from 'element-plus'
|
import type { FormInstance } from 'element-plus'
|
||||||
import chatUserApi from '@/api/system/chat-user'
|
|
||||||
import userManageApi from '@/api/system/user-manage'
|
import userManageApi from '@/api/system/user-manage'
|
||||||
import { MsgSuccess } from '@/utils/message'
|
import { MsgSuccess } from '@/utils/message'
|
||||||
import { t } from '@/locales'
|
import { t } from '@/locales'
|
||||||
@ -111,6 +110,14 @@ const rules = reactive({
|
|||||||
trigger: 'blur',
|
trigger: 'blur',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
user_group_ids: [
|
||||||
|
{
|
||||||
|
type: 'array',
|
||||||
|
required: true,
|
||||||
|
message: t('views.chatUser.group.requiredMessage'),
|
||||||
|
trigger: 'change',
|
||||||
|
},
|
||||||
|
],
|
||||||
})
|
})
|
||||||
const visible = ref<boolean>(false)
|
const visible = ref<boolean>(false)
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
|
|||||||
@ -235,7 +235,7 @@
|
|||||||
{{ $t('common.edit') }}
|
{{ $t('common.edit') }}
|
||||||
</el-dropdown-item>
|
</el-dropdown-item>
|
||||||
<el-dropdown-item
|
<el-dropdown-item
|
||||||
v-if="!item.template_id && permissionPrecise.copy(item.id)"
|
v-if="!item.template_id && permissionPrecise.copy(item.id) && item.tool_type!== 'MCP'"
|
||||||
@click.stop="copyTool(item)"
|
@click.stop="copyTool(item)"
|
||||||
>
|
>
|
||||||
<AppIcon iconName="app-copy" class="color-secondary"></AppIcon>
|
<AppIcon iconName="app-copy" class="color-secondary"></AppIcon>
|
||||||
@ -275,7 +275,7 @@
|
|||||||
{{ $t('views.shared.authorized_workspace') }}</el-dropdown-item
|
{{ $t('views.shared.authorized_workspace') }}</el-dropdown-item
|
||||||
>
|
>
|
||||||
<el-dropdown-item
|
<el-dropdown-item
|
||||||
v-if="!item.template_id && permissionPrecise.export(item.id)"
|
v-if="!item.template_id && permissionPrecise.export(item.id) && item.tool_type!== 'MCP'"
|
||||||
@click.stop="exportTool(item)"
|
@click.stop="exportTool(item)"
|
||||||
>
|
>
|
||||||
<AppIcon iconName="app-export" class="color-secondary"></AppIcon>
|
<AppIcon iconName="app-export" class="color-secondary"></AppIcon>
|
||||||
|
|||||||
@ -114,6 +114,7 @@
|
|||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
|
<!-- MCP-->
|
||||||
<div class="flex-between mb-16">
|
<div class="flex-between mb-16">
|
||||||
<div class="lighter">MCP</div>
|
<div class="lighter">MCP</div>
|
||||||
<div>
|
<div>
|
||||||
@ -129,6 +130,24 @@
|
|||||||
<el-switch size="small" v-model="chat_data.mcp_enable" />
|
<el-switch size="small" v-model="chat_data.mcp_enable" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="w-full" v-if="
|
||||||
|
(chat_data.mcp_tool_id) ||
|
||||||
|
(chat_data.mcp_servers && chat_data.mcp_servers.length > 0)"
|
||||||
|
>
|
||||||
|
<div class="flex-between border border-r-6 white-bg mb-4" style="padding: 5px 8px">
|
||||||
|
<div class="flex align-center" style="line-height: 20px">
|
||||||
|
<ToolIcon type="MCP" class="mr-8" :size="20" />
|
||||||
|
|
||||||
|
<div class="ellipsis" :title="relatedObject(toolSelectOptions, chat_data.mcp_tool_id, 'id')?.name">
|
||||||
|
{{ relatedObject(mcpToolSelectOptions, chat_data.mcp_tool_id, 'id')?.name || $t('common.custom') + ' MCP' }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<el-button text @click="chat_data.mcp_tool_id = ''">
|
||||||
|
<el-icon><Close /></el-icon>
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 工具 -->
|
||||||
<div class="flex-between mb-16">
|
<div class="flex-between mb-16">
|
||||||
<div class="lighter">{{ $t('views.applicationWorkflow.nodes.mcpNode.tool') }}</div>
|
<div class="lighter">{{ $t('views.applicationWorkflow.nodes.mcpNode.tool') }}</div>
|
||||||
<div>
|
<div>
|
||||||
@ -144,6 +163,22 @@
|
|||||||
<el-switch size="small" v-model="chat_data.tool_enable" />
|
<el-switch size="small" v-model="chat_data.tool_enable" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="w-full" v-if="chat_data.tool_ids?.length > 0">
|
||||||
|
<template v-for="(item, index) in chat_data.tool_ids" :key="index">
|
||||||
|
<div class="flex-between border border-r-6 white-bg mb-4" style="padding: 5px 8px">
|
||||||
|
<div class="flex align-center" style="line-height: 20px">
|
||||||
|
<ToolIcon type="CUSTOM" class="mr-8" :size="20" />
|
||||||
|
|
||||||
|
<div class="ellipsis" :title="relatedObject(toolSelectOptions, item, 'id')?.name">
|
||||||
|
{{ relatedObject(toolSelectOptions, item, 'id')?.name }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<el-button text @click="removeTool(item)">
|
||||||
|
<el-icon><Close /></el-icon>
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
|
||||||
<el-form-item @click.prevent>
|
<el-form-item @click.prevent>
|
||||||
<template #label>
|
<template #label>
|
||||||
@ -206,6 +241,7 @@ import McpServersDialog from '@/views/application/component/McpServersDialog.vue
|
|||||||
import { loadSharedApi } from '@/utils/dynamics-api/shared-api'
|
import { loadSharedApi } from '@/utils/dynamics-api/shared-api'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
import ToolDialog from '@/views/application/component/ToolDialog.vue'
|
import ToolDialog from '@/views/application/component/ToolDialog.vue'
|
||||||
|
import {relatedObject} from "@/utils/array.ts";
|
||||||
const getApplicationDetail = inject('getApplicationDetail') as any
|
const getApplicationDetail = inject('getApplicationDetail') as any
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|
||||||
@ -247,7 +283,6 @@ const model_change = (model_id?: string) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
const defaultPrompt = `${t('views.applicationWorkflow.nodes.aiChatNode.defaultPrompt')}:
|
const defaultPrompt = `${t('views.applicationWorkflow.nodes.aiChatNode.defaultPrompt')}:
|
||||||
{{${t('views.applicationWorkflow.nodes.searchKnowledgeNode.label')}.data}}
|
{{${t('views.applicationWorkflow.nodes.searchKnowledgeNode.label')}.data}}
|
||||||
${t('views.problem.title')}:
|
${t('views.problem.title')}:
|
||||||
@ -353,7 +388,7 @@ function openMcpServersDialog() {
|
|||||||
mcp_tool_id: chat_data.value.mcp_tool_id,
|
mcp_tool_id: chat_data.value.mcp_tool_id,
|
||||||
mcp_source: chat_data.value.mcp_source,
|
mcp_source: chat_data.value.mcp_source,
|
||||||
}
|
}
|
||||||
mcpServersDialogRef.value.open(config)
|
mcpServersDialogRef.value.open(config, mcpToolSelectOptions.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
function submitMcpServersDialog(config: any) {
|
function submitMcpServersDialog(config: any) {
|
||||||
@ -364,14 +399,15 @@ function submitMcpServersDialog(config: any) {
|
|||||||
|
|
||||||
const toolDialogRef = ref()
|
const toolDialogRef = ref()
|
||||||
function openToolDialog() {
|
function openToolDialog() {
|
||||||
const config = {
|
toolDialogRef.value.open(chat_data.value.tool_ids)
|
||||||
tool_ids: chat_data.value.tool_ids,
|
|
||||||
}
|
|
||||||
toolDialogRef.value.open(config, toolSelectOptions.value)
|
|
||||||
}
|
}
|
||||||
function submitToolDialog(config: any) {
|
function submitToolDialog(config: any) {
|
||||||
set(props.nodeModel.properties.node_data, 'tool_ids', config.tool_ids)
|
set(props.nodeModel.properties.node_data, 'tool_ids', config.tool_ids)
|
||||||
}
|
}
|
||||||
|
function removeTool(id: any) {
|
||||||
|
const list = props.nodeModel.properties.node_data.tool_ids.filter((v: any) => v !== id)
|
||||||
|
set(props.nodeModel.properties.node_data, 'tool_ids', list)
|
||||||
|
}
|
||||||
|
|
||||||
const toolSelectOptions = ref<any[]>([])
|
const toolSelectOptions = ref<any[]>([])
|
||||||
function getToolSelectOptions() {
|
function getToolSelectOptions() {
|
||||||
@ -396,6 +432,29 @@ function getToolSelectOptions() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const mcpToolSelectOptions = ref<any[]>([])
|
||||||
|
function getMcpToolSelectOptions() {
|
||||||
|
const obj =
|
||||||
|
apiType.value === 'systemManage'
|
||||||
|
? {
|
||||||
|
scope: 'WORKSPACE',
|
||||||
|
tool_type: 'MCP',
|
||||||
|
workspace_id: application.value?.workspace_id,
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
scope: 'WORKSPACE',
|
||||||
|
tool_type: 'MCP',
|
||||||
|
}
|
||||||
|
|
||||||
|
loadSharedApi({ type: 'tool', systemType: apiType.value })
|
||||||
|
.getAllToolList(obj)
|
||||||
|
.then((res: any) => {
|
||||||
|
mcpToolSelectOptions.value = [...res.data.shared_tools, ...res.data.tools].filter(
|
||||||
|
(item: any) => item.is_active,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getSelectModel()
|
getSelectModel()
|
||||||
if (typeof props.nodeModel.properties.node_data?.is_result === 'undefined') {
|
if (typeof props.nodeModel.properties.node_data?.is_result === 'undefined') {
|
||||||
@ -409,6 +468,7 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getToolSelectOptions()
|
getToolSelectOptions()
|
||||||
|
getMcpToolSelectOptions()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped></style>
|
<style lang="scss" scoped></style>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user