mirror of
https://github.com/1Panel-dev/MaxKB.git
synced 2025-12-26 01:33:05 +00:00
Merge branch 'pr@main@application_flow' of github.com:1Panel-dev/MaxKB into pr@main@application_flow
This commit is contained in:
commit
2ff62c151b
|
|
@ -26,6 +26,8 @@ docker run -d --name=maxkb -p 8080:8080 -v ~/.maxkb:/var/lib/postgresql/data 1pa
|
|||
|
||||
你也可以通过 [1Panel 应用商店](https://apps.fit2cloud.com/1panel) 快速部署 MaxKB + Ollama + Llama 2,30 分钟内即可上线基于本地大模型的知识库问答系统,并嵌入到第三方业务系统中。
|
||||
|
||||
如果是内网环境,推荐使用 [离线安装包](https://community.fit2cloud.com/#/products/maxkb/downloads) 进行安装部署。
|
||||
|
||||
你也可以在线体验:[DataEase 小助手](https://dataease.io/docs/v2/),它是基于 MaxKB 搭建的智能问答系统,已经嵌入到 DataEase 产品及在线文档中。
|
||||
|
||||
如你有更多问题,可以查看使用手册,或者通过论坛与我们交流。
|
||||
|
|
|
|||
|
|
@ -317,9 +317,9 @@ class ApplicationSerializer(serializers.Serializer):
|
|||
id = serializers.CharField(required=True, error_messages=ErrMessage.uuid("应用id"))
|
||||
user_id = serializers.UUIDField(required=False, error_messages=ErrMessage.uuid("用户id"))
|
||||
query_text = serializers.CharField(required=True, error_messages=ErrMessage.char("查询文本"))
|
||||
top_number = serializers.IntegerField(required=True, max_value=10, min_value=1,
|
||||
top_number = serializers.IntegerField(required=True, max_value=100, min_value=1,
|
||||
error_messages=ErrMessage.integer("topN"))
|
||||
similarity = serializers.FloatField(required=True, max_value=1, min_value=0,
|
||||
similarity = serializers.FloatField(required=True, max_value=2, min_value=0,
|
||||
error_messages=ErrMessage.float("相关度"))
|
||||
search_mode = serializers.CharField(required=True, validators=[
|
||||
validators.RegexValidator(regex=re.compile("^embedding|keywords|blend$"),
|
||||
|
|
|
|||
|
|
@ -179,13 +179,12 @@ class ChatMessageSerializer(serializers.Serializer):
|
|||
return chat_info
|
||||
|
||||
def chat(self):
|
||||
self.is_valid(raise_exception=True)
|
||||
chat_info = self.is_valid(raise_exception=True)
|
||||
message = self.data.get('message')
|
||||
re_chat = self.data.get('re_chat')
|
||||
stream = self.data.get('stream')
|
||||
client_id = self.data.get('client_id')
|
||||
client_type = self.data.get('client_type')
|
||||
chat_info = self.is_valid(raise_exception=True)
|
||||
pipeline_manage_builder = PipelineManage.builder()
|
||||
# 如果开启了问题优化,则添加上问题优化步骤
|
||||
if chat_info.application.problem_optimization:
|
||||
|
|
|
|||
|
|
@ -422,11 +422,11 @@ class ChatRecordSerializer(serializers.Serializer):
|
|||
return True
|
||||
|
||||
class ImproveSerializer(serializers.Serializer):
|
||||
title = serializers.CharField(required=False, allow_null=True, allow_blank=True,
|
||||
title = serializers.CharField(required=False, max_length=256, allow_null=True, allow_blank=True,
|
||||
error_messages=ErrMessage.char("段落标题"))
|
||||
content = serializers.CharField(required=True, error_messages=ErrMessage.char("段落内容"))
|
||||
|
||||
problem_text = serializers.CharField(required=False, allow_null=True, allow_blank=True,
|
||||
problem_text = serializers.CharField(required=False, max_length=256, allow_null=True, allow_blank=True,
|
||||
error_messages=ErrMessage.char("问题"))
|
||||
|
||||
class ParagraphModel(serializers.ModelSerializer):
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
@date:2023/10/20 14:01
|
||||
@desc:
|
||||
"""
|
||||
import datetime
|
||||
import logging
|
||||
import os
|
||||
import traceback
|
||||
|
|
@ -143,7 +144,8 @@ class ListenerManagement:
|
|||
status = Status.error
|
||||
finally:
|
||||
# 修改状态
|
||||
QuerySet(Document).filter(id=document_id).update(**{'status': status})
|
||||
QuerySet(Document).filter(id=document_id).update(
|
||||
**{'status': status, 'update_time': datetime.datetime.now()})
|
||||
QuerySet(Paragraph).filter(document_id=document_id).update(**{'status': status})
|
||||
max_kb.info(f"结束--->向量化文档:{document_id}")
|
||||
|
||||
|
|
|
|||
|
|
@ -37,14 +37,9 @@ def get_encoding(buffer):
|
|||
|
||||
class HTMLSplitHandle(BaseSplitHandle):
|
||||
def support(self, file, get_buffer):
|
||||
buffer = get_buffer(file)
|
||||
file_name: str = file.name.lower()
|
||||
if file_name.endswith(".html"):
|
||||
return True
|
||||
result = detect(buffer)
|
||||
if result['encoding'] is not None and result['confidence'] is not None and result['encoding'] != 'ascii' and \
|
||||
result['confidence'] > 0.5:
|
||||
return True
|
||||
return False
|
||||
|
||||
def handle(self, file, pattern_list: List, with_filter: bool, limit: int, get_buffer, save_image):
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
"""
|
||||
import hashlib
|
||||
|
||||
from django.urls import re_path, path
|
||||
from django.urls import re_path, path, URLPattern
|
||||
from drf_yasg import openapi
|
||||
from drf_yasg.views import get_schema_view
|
||||
from rest_framework import permissions
|
||||
|
|
@ -46,11 +46,15 @@ def init_chat_doc(application_urlpatterns, patterns):
|
|||
public=True,
|
||||
permission_classes=[permissions.AllowAny],
|
||||
authentication_classes=[AnonymousAuthentication],
|
||||
patterns=[url for url in patterns if
|
||||
url.name is not None and ['application/message', 'application/open',
|
||||
'application/profile'].__contains__(
|
||||
url.name)]
|
||||
patterns=[
|
||||
URLPattern(pattern='api/' + str(url.pattern), callback=url.callback, default_args=url.default_args,
|
||||
name=url.name)
|
||||
for url in patterns if
|
||||
url.name is not None and ['application/message', 'application/open',
|
||||
'application/profile'].__contains__(
|
||||
url.name)]
|
||||
)
|
||||
|
||||
application_urlpatterns += [
|
||||
path('doc/chat/', chat_schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
|
||||
path('redoc/chat/', chat_schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'),
|
||||
|
|
|
|||
|
|
@ -163,7 +163,7 @@ def parse_level(text, pattern: str):
|
|||
:param pattern: 正则
|
||||
:return: 符合正则的文本
|
||||
"""
|
||||
level_content_list = list(map(to_tree_obj, re_findall(pattern, text)))
|
||||
level_content_list = list(map(to_tree_obj, [r[0:255] for r in re_findall(pattern, text) if r is not None]))
|
||||
return list(map(filter_special_symbol, level_content_list))
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -535,9 +535,9 @@ class DataSetSerializers(serializers.ModelSerializer):
|
|||
id = serializers.CharField(required=True, error_messages=ErrMessage.char("id"))
|
||||
user_id = serializers.UUIDField(required=False, error_messages=ErrMessage.char("用户id"))
|
||||
query_text = serializers.CharField(required=True, error_messages=ErrMessage.char("查询文本"))
|
||||
top_number = serializers.IntegerField(required=True, max_value=10, min_value=1,
|
||||
top_number = serializers.IntegerField(required=True, max_value=100, min_value=1,
|
||||
error_messages=ErrMessage.char("响应Top"))
|
||||
similarity = serializers.FloatField(required=True, max_value=1, min_value=0,
|
||||
similarity = serializers.FloatField(required=True, max_value=2, min_value=0,
|
||||
error_messages=ErrMessage.char("相似度"))
|
||||
search_mode = serializers.CharField(required=True, validators=[
|
||||
validators.RegexValidator(regex=re.compile("^embedding|keywords|blend$"),
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import os
|
|||
import re
|
||||
from importlib import import_module
|
||||
from urllib.parse import urljoin, urlparse
|
||||
|
||||
import yaml
|
||||
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
|
@ -75,25 +76,18 @@ class DoesNotExist(Exception):
|
|||
class Config(dict):
|
||||
defaults = {
|
||||
# 数据库相关配置
|
||||
"DB_HOST": "",
|
||||
"DB_PORT": "",
|
||||
"DB_USER": "",
|
||||
"DB_PASSWORD": "",
|
||||
"DB_HOST": "127.0.0.1",
|
||||
"DB_PORT": 5432,
|
||||
"DB_USER": "root",
|
||||
"DB_PASSWORD": "Password123@postgres",
|
||||
"DB_ENGINE": "django.db.backends.postgresql_psycopg2",
|
||||
# 邮件相关配置
|
||||
"EMAIL_ADDRESS": "",
|
||||
"EMAIL_USE_TLS": False,
|
||||
"EMAIL_USE_SSL": True,
|
||||
"EMAIL_HOST": "",
|
||||
"EMAIL_PORT": 465,
|
||||
"EMAIL_HOST_USER": "",
|
||||
"EMAIL_HOST_PASSWORD": "",
|
||||
# 向量模型
|
||||
"EMBEDDING_MODEL_NAME": "shibing624/text2vec-base-chinese",
|
||||
"EMBEDDING_DEVICE": "cpu",
|
||||
"EMBEDDING_MODEL_PATH": os.path.join(PROJECT_DIR, 'models'),
|
||||
# 向量库配置
|
||||
"VECTOR_STORE_NAME": 'pg_vector'
|
||||
"VECTOR_STORE_NAME": 'pg_vector',
|
||||
"DEBUG": False
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -180,8 +174,36 @@ class ConfigManager:
|
|||
loaded = self.from_yaml(i)
|
||||
if loaded:
|
||||
return True
|
||||
msg = f"""
|
||||
|
||||
return False
|
||||
Error: No config file found.
|
||||
|
||||
You can run `cp config_example.yml {self.root_path}/config.yml`, and edit it.
|
||||
|
||||
"""
|
||||
raise ImportError(msg)
|
||||
|
||||
def load_from_env(self):
|
||||
keys = os.environ.keys()
|
||||
config = {key.replace('MAXKB_', ''): os.environ.get(key) for key in keys if key.startswith('MAXKB_')}
|
||||
if len(config.keys()) <= 1:
|
||||
msg = f"""
|
||||
|
||||
Error: No config env found.
|
||||
|
||||
Please set environment variables
|
||||
MAXKB_CONFIG_TYPE: 配置文件读取方式 FILE: 使用配置文件配置 ENV: 使用ENV配置
|
||||
MAXKB_DB_NAME: 数据库名称
|
||||
MAXKB_DB_HOST: 数据库主机
|
||||
MAXKB_DB_PORT: 数据库端口
|
||||
MAXKB_DB_USER: 数据库用户名
|
||||
MAXKB_DB_PASSWORD: 数据库密码
|
||||
MAXKB_EMBEDDING_MODEL_PATH: 向量模型目录
|
||||
MAXKB_EMBEDDING_MODEL_NAME: 向量模型名称
|
||||
"""
|
||||
raise ImportError(msg)
|
||||
self.from_mapping(config)
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def load_user_config(cls, root_path=None, config_class=None):
|
||||
|
|
@ -190,15 +212,10 @@ class ConfigManager:
|
|||
if not root_path:
|
||||
root_path = PROJECT_DIR
|
||||
manager = cls(root_path=root_path)
|
||||
if manager.load_from_yml():
|
||||
config = manager.config
|
||||
config_type = os.environ.get('MAXKB_CONFIG_TYPE')
|
||||
if config_type is None or config_type != 'ENV':
|
||||
manager.load_from_yml()
|
||||
else:
|
||||
msg = f"""
|
||||
|
||||
Error: No config file found.
|
||||
|
||||
You can run `cp config_example.yml {root_path}/config.yml`, and edit it.
|
||||
|
||||
"""
|
||||
raise ImportError(msg)
|
||||
manager.load_from_env()
|
||||
config = manager.config
|
||||
return config
|
||||
|
|
|
|||
|
|
@ -1,12 +1,3 @@
|
|||
# 邮箱配置
|
||||
EMAIL_ADDRESS:
|
||||
EMAIL_USE_TLS: False
|
||||
EMAIL_USE_SSL: True
|
||||
EMAIL_HOST: smtp.qq.com
|
||||
EMAIL_PORT: 465
|
||||
EMAIL_HOST_USER:
|
||||
EMAIL_HOST_PASSWORD:
|
||||
|
||||
# 数据库链接信息
|
||||
DB_NAME: maxkb
|
||||
DB_HOST: localhost
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ RUN apt-get update && \
|
|||
|
||||
COPY . /opt/maxkb/app
|
||||
RUN mkdir -p /opt/maxkb/app /opt/maxkb/model /opt/maxkb/conf && \
|
||||
cp -f /opt/maxkb/app/installer/config.yaml /opt/maxkb/conf && \
|
||||
rm -rf /opt/maxkb/app/ui
|
||||
COPY --from=web-build ui /opt/maxkb/app/ui
|
||||
WORKDIR /opt/maxkb/app
|
||||
|
|
@ -33,7 +32,16 @@ ARG DOCKER_IMAGE_TAG=dev \
|
|||
BUILD_AT \
|
||||
GITHUB_COMMIT
|
||||
|
||||
ENV MAXKB_VERSION ${DOCKER_IMAGE_TAG} (build at ${BUILD_AT}, commit: ${GITHUB_COMMIT})
|
||||
ENV MAXKB_VERSION="${DOCKER_IMAGE_TAG} (build at ${BUILD_AT}, commit: ${GITHUB_COMMIT})" \
|
||||
MAXKB_CONFIG_TYPE=ENV \
|
||||
MAXKB_DB_NAME=maxkb \
|
||||
MAXKB_DB_HOST=127.0.0.1 \
|
||||
MAXKB_DB_PORT=5432 \
|
||||
MAXKB_DB_USER=root \
|
||||
MAXKB_DB_PASSWORD=Password123@postgres \
|
||||
MAXKB_EMBEDDING_MODEL_NAME=/opt/maxkb/model/embedding/shibing624_text2vec-base-chinese \
|
||||
MAXKB_EMBEDDING_MODEL_PATH=/opt/maxkb/model/embedding
|
||||
|
||||
WORKDIR /opt/maxkb/app
|
||||
COPY --from=stage-build /opt/maxkb /opt/maxkb
|
||||
COPY --from=stage-build /opt/py3 /opt/py3
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.00024 2.5C4.00024 1.94772 4.44796 1.5 5.00024 1.5H14.7931C14.9257 1.5 15.0529 1.55268 15.1467 1.64645L19.8538 6.35355C19.9476 6.44732 20.0002 6.5745 20.0002 6.70711V21.5C20.0002 22.0523 19.5525 22.5 19.0002 22.5H5.00024C4.44796 22.5 4.00024 22.0523 4.00024 21.5V2.5Z" fill="#D136D1"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.00024 2.5C4.00024 1.94772 4.44796 1.5 5.00024 1.5H14.7931C14.9257 1.5 15.0529 1.55268 15.1467 1.64645L19.8538 6.35355C19.9476 6.44732 20.0002 6.5745 20.0002 6.70711V21.5C20.0002 22.0523 19.5525 22.5 19.0002 22.5H5.00024C4.44796 22.5 4.00024 22.0523 4.00024 21.5V2.5Z" fill="#3370FF"/>
|
||||
<path d="M10.2966 11.4764L7.76958 14.1363L10.2966 16.7961C10.3873 16.8916 10.3857 17.0447 10.293 17.1381L10.292 17.1391L9.95252 17.4774C9.8597 17.5698 9.7118 17.5677 9.62149 17.4727L6.61239 14.3054C6.52308 14.2114 6.52308 14.0611 6.61239 13.9671L9.62149 10.7999C9.7118 10.7048 9.8597 10.7027 9.95252 10.7952L10.292 11.1335C10.3852 11.2263 10.3877 11.3794 10.2976 11.4754L10.2966 11.4764ZM16.3178 14.1363L13.9712 11.4764C13.887 11.381 13.8885 11.2278 13.9746 11.1344L13.9755 11.1335L14.2908 10.7952C14.3769 10.7027 14.5143 10.7048 14.5981 10.7999L17.3923 13.9671C17.4752 14.0611 17.4752 14.2114 17.3923 14.3054L14.5981 17.4727C14.5143 17.5677 14.3769 17.5698 14.2908 17.4774L13.9755 17.1391C13.8889 17.0462 13.8866 16.8931 13.9704 16.7971L13.9712 16.7961L16.3178 14.1363ZM12.6285 9.09234L13.1203 9.14509C13.2546 9.15949 13.3509 9.27213 13.3353 9.39669L12.1614 18.7083C12.1457 18.8327 12.0244 18.9219 11.8902 18.9075L11.3984 18.8547C11.2642 18.8403 11.1679 18.7277 11.1834 18.6031L12.3574 9.2915C12.373 9.16708 12.4944 9.07796 12.6285 9.09234Z" fill="white"/>
|
||||
<path d="M15 1.54492C15.054 1.56949 15.1037 1.6037 15.1464 1.64646L19.8536 6.35357C19.8963 6.39632 19.9305 6.44602 19.9551 6.50001H16C15.4477 6.50001 15 6.0523 15 5.50001V1.54492Z" fill="#2B5FD9"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M14.2404 11.9757C14.2022 11.8917 14.1406 11.8206 14.0629 11.7709C13.9853 11.7213 13.8949 11.6951 13.8027 11.6957H12.6518V9.16918C12.6518 7.44347 11.069 6.00118 8.96725 5.65376V1.63242C8.96725 1.37615 8.86544 1.13037 8.68423 0.949156C8.50302 0.767942 8.25724 0.666138 8.00096 0.666138C7.74469 0.666138 7.49891 0.767942 7.3177 0.949156C7.13648 1.13037 7.03468 1.37615 7.03468 1.63242V5.65604C4.93411 6.00233 3.34896 7.44576 3.34896 9.17033V11.6945H2.19811C2.07221 11.6959 1.95172 11.746 1.86184 11.8341C1.77197 11.9223 1.71965 12.0418 1.71582 12.1677V12.1837C1.71582 12.2797 1.74782 12.3779 1.80953 12.4614L3.76496 15.1345C3.80786 15.1932 3.86352 15.2413 3.92775 15.2754C3.99197 15.3094 4.06308 15.3284 4.13572 15.3309C4.20836 15.3334 4.28062 15.3195 4.34705 15.29C4.41349 15.2605 4.47237 15.2163 4.51925 15.1608L6.80153 12.4899C6.86177 12.42 6.9005 12.3341 6.91307 12.2427C6.92564 12.1512 6.91151 12.0581 6.87239 11.9745C6.83423 11.8906 6.7726 11.8195 6.69493 11.7698C6.61726 11.7201 6.52688 11.694 6.43468 11.6945H5.28382V9.16918C5.28382 8.3829 6.39925 7.50747 8.00039 7.50747C9.60153 7.50747 10.7192 8.3829 10.7192 9.16918V11.6922H9.56611C9.47671 11.6918 9.38902 11.7166 9.31303 11.7637C9.23705 11.8108 9.17583 11.8783 9.13639 11.9585C9.10152 12.026 9.08349 12.1009 9.08382 12.1768C9.08444 12.2795 9.11721 12.3794 9.17754 12.4625L11.133 15.1368C11.1759 15.1955 11.2315 15.2436 11.2957 15.2777C11.36 15.3117 11.4311 15.3307 11.5037 15.3332C11.5764 15.3357 11.6486 15.3217 11.7151 15.2923C11.7815 15.2628 11.8404 15.2186 11.8872 15.1631L14.1684 12.4888C14.242 12.404 14.2833 12.2959 14.285 12.1837V12.1688C14.2839 12.1015 14.2683 12.0352 14.2392 11.9745L14.2404 11.9757Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
|
|
@ -0,0 +1,5 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.78395 12.7399C8.87529 12.6625 8.93678 12.5557 8.9578 12.4378L10.4173 3.95194C10.4294 3.87835 10.4253 3.803 10.4054 3.73113C10.3855 3.65926 10.3502 3.59258 10.3019 3.53572C10.2536 3.47887 10.1936 3.43318 10.1259 3.40185C10.0582 3.37051 9.98452 3.35427 9.90994 3.35425H8.04041C7.92032 3.35426 7.80413 3.39687 7.7125 3.4745C7.62088 3.55212 7.55976 3.65974 7.54002 3.77819L7.01877 6.78751H4.5168L5.01025 3.95194C5.02234 3.87835 5.01828 3.803 4.99836 3.73113C4.97843 3.65926 4.94311 3.59258 4.89485 3.53572C4.84658 3.47887 4.78653 3.43318 4.71885 3.40185C4.65117 3.37051 4.57748 3.35427 4.5029 3.35425H2.63337C2.51366 3.35562 2.39818 3.39872 2.30683 3.47611C2.21549 3.5535 2.154 3.66033 2.13298 3.77819L0.673494 12.264C0.6614 12.3376 0.665456 12.413 0.685383 12.4849C0.70531 12.5567 0.740631 12.6234 0.788895 12.6803C0.837159 12.7371 0.897213 12.7828 0.964892 12.8141C1.03257 12.8455 1.10626 12.8617 1.18084 12.8617H3.02257C3.14266 12.8617 3.25885 12.8191 3.35048 12.7415C3.4421 12.6639 3.50322 12.5563 3.52296 12.4378L4.07201 9.42848H6.57398L6.08053 12.264C6.06844 12.3376 6.0725 12.413 6.09243 12.4849C6.11235 12.5567 6.14767 12.6234 6.19594 12.6803C6.2442 12.7371 6.30425 12.7828 6.37193 12.8141C6.43961 12.8455 6.5133 12.8617 6.58788 12.8617H8.45741C8.57712 12.8604 8.6926 12.8173 8.78395 12.7399Z" fill="white"/>
|
||||
<path d="M13.686 12.7378C13.7786 12.6617 13.8418 12.5555 13.8644 12.4378L14.6428 7.9134C14.6603 7.83959 14.6613 7.76282 14.6456 7.68859C14.6299 7.61437 14.598 7.54454 14.5522 7.48409C14.5064 7.42364 14.4477 7.37409 14.3805 7.33897C14.3133 7.30385 14.2391 7.28404 14.1633 7.28096H12.2938C12.1741 7.28233 12.0586 7.32543 11.9672 7.40282C11.8759 7.48021 11.8144 7.58704 11.7934 7.7049L10.9872 12.2571C10.9739 12.3303 10.9769 12.4055 10.996 12.4774C11.015 12.5493 11.0497 12.6161 11.0974 12.6731C11.1451 12.7302 11.2048 12.776 11.2723 12.8074C11.3397 12.8388 11.4132 12.855 11.4876 12.8548H13.3571C13.477 12.8553 13.5934 12.814 13.686 12.7378Z" fill="white"/>
|
||||
<path opacity="0.5" d="M14.891 5.59802C14.9822 5.52202 15.0443 5.41679 15.0667 5.30021L15.3239 3.93802C15.3383 3.86434 15.3362 3.78837 15.3176 3.71561C15.2991 3.64284 15.2647 3.5751 15.2168 3.51725C15.1689 3.45941 15.1088 3.41291 15.0408 3.38112C14.9728 3.34932 14.8986 3.33301 14.8235 3.33338H12.9609C12.8428 3.33281 12.7282 3.37345 12.6369 3.4483C12.5455 3.52315 12.4832 3.62752 12.4605 3.74343L12.1964 5.11256C12.1833 5.18609 12.1863 5.2616 12.2054 5.33384C12.2244 5.40607 12.2589 5.4733 12.3065 5.53084C12.3541 5.58839 12.4137 5.63486 12.4811 5.66705C12.5485 5.69923 12.6221 5.71635 12.6968 5.71721H14.5663C14.6851 5.71614 14.7998 5.67403 14.891 5.59802Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.7 KiB |
|
|
@ -0,0 +1,4 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.185 14.7097C8.41034 15.273 9.21034 15.2667 9.42667 14.6997L10.8753 10.9044L14.701 9.42671C15.266 9.20838 15.2707 8.41072 14.7083 8.18572L5.28034 4.41438C4.736 4.19672 4.196 4.73705 4.41367 5.28105L8.185 14.7097Z" fill="white"/>
|
||||
<path opacity="0.5" d="M8.33167 4.559C8.275 2.58367 6.65567 1 4.66667 1C2.64167 1 1 2.64167 1 4.66667C1 6.65467 2.58167 8.273 4.55567 8.33167L3.98233 6.898C3.59753 6.77974 3.24987 6.56394 2.97314 6.27157C2.69642 5.9792 2.50004 5.6202 2.4031 5.22948C2.30617 4.83876 2.31196 4.42961 2.41993 4.0418C2.52791 3.65398 2.73438 3.3007 3.01928 3.01629C3.30418 2.73187 3.65781 2.52601 4.04581 2.4187C4.43381 2.3114 4.84298 2.3063 5.23353 2.40391C5.62408 2.50152 5.98274 2.69851 6.27463 2.97574C6.56653 3.25296 6.78174 3.60099 6.89933 3.986L8.33167 4.559Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 904 B |
|
|
@ -5,8 +5,8 @@
|
|||
</el-text>
|
||||
</div>
|
||||
<div>
|
||||
<el-tooltip effect="dark" content="重新生成" placement="top">
|
||||
<el-button text @click="regeneration">
|
||||
<el-tooltip effect="dark" content="换个答案" placement="top">
|
||||
<el-button :disabled="chat_loading" text @click="regeneration">
|
||||
<AppIcon iconName="VideoPlay"></AppIcon>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
|
|
@ -78,6 +78,9 @@ const props = defineProps({
|
|||
type: String,
|
||||
default: ''
|
||||
},
|
||||
chat_loading: {
|
||||
type: Boolean
|
||||
},
|
||||
log: Boolean
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -131,6 +131,7 @@
|
|||
:data="item"
|
||||
:applicationId="appId"
|
||||
:chatId="chartOpenId"
|
||||
:chat_loading="loading"
|
||||
@regeneration="regenerationChart(item)"
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -541,7 +542,9 @@ function chatMessage(chat?: any, problem?: string, re_chat?: boolean) {
|
|||
|
||||
function regenerationChart(item: chatType) {
|
||||
inputValue.value = item.problem_text
|
||||
chatMessage(null, '', true)
|
||||
if (!loading.value) {
|
||||
chatMessage(null, '', true)
|
||||
}
|
||||
}
|
||||
|
||||
function getSourceDetail(row: any) {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<el-card shadow="never" class="card-add">
|
||||
<div class="flex-center">
|
||||
<AppIcon iconName="Plus" class="add-icon p-8" />
|
||||
<AppIcon iconName="Plus" class="add-icon p-8 border-r-4 layout-bg" />
|
||||
<span>{{ title }}</span>
|
||||
</div>
|
||||
</el-card>
|
||||
|
|
@ -30,9 +30,7 @@ defineProps({
|
|||
|
||||
.add-icon {
|
||||
font-size: 14px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--app-border-color-dark);
|
||||
background: var(--app-layout-bg-color);
|
||||
margin-right: 12px;
|
||||
}
|
||||
&:hover {
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
</el-card>
|
||||
<el-card shadow="never" class="card-add box-card" @click="add_card">
|
||||
<div class="flex-center">
|
||||
<AppIcon iconName="Plus" class="add-icon p-8" />
|
||||
<AppIcon iconName="Plus" class="add-icon layout-bg p-8 border-r-4" />
|
||||
<span>{{ add_msg }}</span>
|
||||
</div>
|
||||
</el-card>
|
||||
|
|
@ -133,9 +133,7 @@ defineExpose({
|
|||
|
||||
.add-icon {
|
||||
font-size: 14px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--app-border-color-dark);
|
||||
background: var(--app-layout-bg-color);
|
||||
margin-right: 12px;
|
||||
}
|
||||
&:hover {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="content-container">
|
||||
<div class="content-container border-r-4">
|
||||
<div class="content-container__header flex align-center w-full" v-if="slots.header || header">
|
||||
<slot name="backButton">
|
||||
<back-button :to="backTo" v-if="showBack"></back-button>
|
||||
|
|
@ -41,7 +41,6 @@ const showBack = computed(() => {
|
|||
}
|
||||
.content-container__main {
|
||||
background-color: var(--app-view-bg-color);
|
||||
border-radius: 4px;
|
||||
box-sizing: border-box;
|
||||
min-width: 700px;
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -1,115 +0,0 @@
|
|||
import LogicFlow from '@logicflow/core'
|
||||
import { shapeList } from './data'
|
||||
type ShapeItem = {
|
||||
type?: string
|
||||
text?: string
|
||||
icon?: string
|
||||
label?: string
|
||||
className?: string
|
||||
disabled?: boolean
|
||||
properties?: Record<string, any>
|
||||
callback?: (lf: LogicFlow, container: HTMLElement) => void
|
||||
}
|
||||
|
||||
class AppMenu {
|
||||
lf: LogicFlow
|
||||
shapeList: ShapeItem[]
|
||||
panelEl?: HTMLDivElement
|
||||
static pluginName = 'AppMenu'
|
||||
domContainer?: HTMLElement
|
||||
constructor({ lf }: { lf: LogicFlow }) {
|
||||
this.lf = lf
|
||||
this.lf.setPatternItems = (shapeList: Array<ShapeItem>) => {
|
||||
this.setPatternItems(shapeList)
|
||||
}
|
||||
this.shapeList = shapeList
|
||||
this.panelEl = undefined
|
||||
this.domContainer = undefined
|
||||
}
|
||||
render(lf: LogicFlow, domContainer: HTMLElement) {
|
||||
this.destroy()
|
||||
if (!this.shapeList || this.shapeList.length === 0) {
|
||||
// 首次render后失败后,后续调用setPatternItems支持渲染
|
||||
this.domContainer = domContainer
|
||||
return
|
||||
}
|
||||
this.panelEl = document.createElement('div')
|
||||
this.panelEl.className = 'lf-dndpanel'
|
||||
this.shapeList.forEach((shapeItem) => {
|
||||
this.panelEl?.appendChild(this.createDndItem(shapeItem))
|
||||
})
|
||||
domContainer.appendChild(this.panelEl)
|
||||
this.domContainer = domContainer
|
||||
}
|
||||
destroy() {
|
||||
if (this.domContainer && this.panelEl && this.domContainer.contains(this.panelEl)) {
|
||||
this.domContainer.removeChild(this.panelEl)
|
||||
}
|
||||
}
|
||||
setPatternItems(shapeList: Array<ShapeItem>) {
|
||||
this.shapeList = shapeList
|
||||
// 支持渲染后重新设置拖拽面板
|
||||
if (this.domContainer) {
|
||||
this.render(this.lf, this.domContainer)
|
||||
}
|
||||
}
|
||||
private createDndItem(shapeItem: ShapeItem): HTMLElement {
|
||||
const el = document.createElement('div')
|
||||
el.className = shapeItem.className ? `lf-dnd-item ${shapeItem.className}` : 'lf-dnd-item'
|
||||
const shape = document.createElement('div')
|
||||
shape.className = 'lf-dnd-shape'
|
||||
|
||||
if (shapeItem.icon) {
|
||||
shape.style.backgroundImage = `url(${shapeItem.icon})`
|
||||
}
|
||||
el.appendChild(shape)
|
||||
if (shapeItem.label) {
|
||||
const text = document.createElement('div')
|
||||
text.innerText = shapeItem.label
|
||||
text.className = 'lf-dnd-text'
|
||||
el.appendChild(text)
|
||||
}
|
||||
if (shapeItem.disabled) {
|
||||
el.classList.add('disabled')
|
||||
// 保留callback的执行,可用于界面提示当前shapeItem的禁用状态
|
||||
el.onmousedown = () => {
|
||||
if (shapeItem.callback && this.domContainer) {
|
||||
shapeItem.callback(this.lf, this.domContainer)
|
||||
}
|
||||
}
|
||||
return el
|
||||
}
|
||||
el.onmousedown = () => {
|
||||
if (shapeItem.type) {
|
||||
this.lf.dnd.startDrag({
|
||||
type: shapeItem.type,
|
||||
properties: shapeItem.properties
|
||||
})
|
||||
}
|
||||
if (shapeItem.callback && this.domContainer) {
|
||||
shapeItem.callback(this.lf, this.domContainer)
|
||||
}
|
||||
}
|
||||
el.ondblclick = (e) => {
|
||||
this.lf.graphModel.eventCenter.emit('dnd:panel-dbclick', {
|
||||
e,
|
||||
data: shapeItem
|
||||
})
|
||||
}
|
||||
el.onclick = (e) => {
|
||||
this.lf.graphModel.eventCenter.emit('dnd:panel-click', {
|
||||
e,
|
||||
data: shapeItem
|
||||
})
|
||||
}
|
||||
el.oncontextmenu = (e) => {
|
||||
this.lf.graphModel.eventCenter.emit('dnd:panel-contextmenu', {
|
||||
e,
|
||||
data: shapeItem
|
||||
})
|
||||
}
|
||||
return el
|
||||
}
|
||||
}
|
||||
|
||||
export { AppMenu }
|
||||
|
|
@ -1,193 +0,0 @@
|
|||
<template>
|
||||
<!-- <button @click="validate">点击校验</button>
|
||||
<button @click="getGraphData">点击获取流程数据</button> -->
|
||||
<div className="helloworld-app sql" style="height: 100%; width: 100%" id="container"></div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import LogicFlow from '@logicflow/core'
|
||||
import { ref, onMounted } from 'vue'
|
||||
import AiChatNode from './nodes/ai-chat-node/index'
|
||||
import AppEdge from './common/edge/index'
|
||||
import { AppMenu } from './common/menu/index'
|
||||
import '@logicflow/extension/lib/style/index.css'
|
||||
import '@logicflow/core/dist/style/index.css'
|
||||
|
||||
defineOptions({ name: 'WorkFlow' })
|
||||
|
||||
type ShapeItem = {
|
||||
type?: string
|
||||
text?: string
|
||||
icon?: string
|
||||
label?: string
|
||||
className?: string
|
||||
disabled?: boolean
|
||||
properties?: Record<string, any>
|
||||
callback?: (lf: LogicFlow, container: HTMLElement) => void
|
||||
}
|
||||
// LogicFlow.use(AppMenu)
|
||||
|
||||
const graphData = {
|
||||
nodes: [
|
||||
{
|
||||
id: '92a94b25-453d-4a00-aa26-9fed9b487e08',
|
||||
type: 'ai-chat-node',
|
||||
x: -10,
|
||||
y: 239,
|
||||
properties: {
|
||||
height: 200,
|
||||
stepName: 'AI对话',
|
||||
input: [{ key: '输入' }],
|
||||
output: [{ key: '输出' }],
|
||||
node_data: { model: 'shanghai', name: '222' }
|
||||
}
|
||||
},
|
||||
{
|
||||
id: '34902d3d-a3ff-497f-b8e1-0c34a44d7dd4',
|
||||
type: 'ai-chat-node',
|
||||
x: 143,
|
||||
y: 523,
|
||||
properties: {
|
||||
height: 200,
|
||||
stepName: 'AI对话',
|
||||
input: [{ key: '输入' }],
|
||||
output: [{ key: '输出' }],
|
||||
node_data: { model: 'shanghai', name: '222222' }
|
||||
}
|
||||
}
|
||||
],
|
||||
edges: [
|
||||
{
|
||||
id: 'bc7297fa-2409-4c85-9a4d-3d74c9c1e30f',
|
||||
type: 'app-edge',
|
||||
sourceNodeId: '92a94b25-453d-4a00-aa26-9fed9b487e08',
|
||||
targetNodeId: '34902d3d-a3ff-497f-b8e1-0c34a44d7dd4',
|
||||
startPoint: { x: 230, y: 333.000005 },
|
||||
endPoint: { x: -97, y: 596.111105 },
|
||||
properties: {},
|
||||
pointsList: [
|
||||
{ x: 230, y: 333.000005 },
|
||||
{ x: 340, y: 333.000005 },
|
||||
{ x: -207, y: 596.111105 },
|
||||
{ x: -97, y: 596.111105 }
|
||||
],
|
||||
sourceAnchorId: '92a94b25-453d-4a00-aa26-9fed9b487e08_输出_right',
|
||||
targetAnchorId: '34902d3d-a3ff-497f-b8e1-0c34a44d7dd4_输入_left'
|
||||
},
|
||||
{
|
||||
id: '9f5740ce-b55e-42d4-90a2-a06f34d6f5ef',
|
||||
type: 'app-edge',
|
||||
sourceNodeId: '92a94b25-453d-4a00-aa26-9fed9b487e08',
|
||||
targetNodeId: '34902d3d-a3ff-497f-b8e1-0c34a44d7dd4',
|
||||
startPoint: { x: 230, y: 333.000005 },
|
||||
endPoint: { x: -97, y: 596.111105 },
|
||||
properties: {},
|
||||
pointsList: [
|
||||
{ x: 230, y: 333.000005 },
|
||||
{ x: 340, y: 333.000005 },
|
||||
{ x: -207, y: 596.111105 },
|
||||
{ x: -97, y: 596.111105 }
|
||||
],
|
||||
sourceAnchorId: '92a94b25-453d-4a00-aa26-9fed9b487e08_输出_right',
|
||||
targetAnchorId: '34902d3d-a3ff-497f-b8e1-0c34a44d7dd4_输入_left'
|
||||
}
|
||||
]
|
||||
}
|
||||
const lf = ref()
|
||||
|
||||
onMounted(() => {
|
||||
const container: any = document.querySelector('#container')
|
||||
if (container) {
|
||||
lf.value = new LogicFlow({
|
||||
background: {
|
||||
backgroundColor: '#f5f6f7'
|
||||
},
|
||||
grid: {
|
||||
size: 10,
|
||||
type: 'dot',
|
||||
config: {
|
||||
color: '#DEE0E3',
|
||||
thickness: 1
|
||||
}
|
||||
},
|
||||
keyboard: {
|
||||
enabled: true,
|
||||
shortcuts: [
|
||||
{
|
||||
keys: ['backspace'],
|
||||
callback: () => {
|
||||
const elements = lf.value.getSelectElements(true)
|
||||
if (
|
||||
(elements.edges && elements.edges.length > 0) ||
|
||||
(elements.nodes && elements.nodes.length > 0)
|
||||
) {
|
||||
const r = window.confirm('确定要删除吗?')
|
||||
if (r) {
|
||||
lf.value.clearSelectElements()
|
||||
elements.edges.forEach((edge: any) => {
|
||||
lf.value.deleteEdge(edge.id)
|
||||
})
|
||||
elements.nodes.forEach((node: any) => {
|
||||
lf.value.deleteNode(node.id)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
isSilentMode: false,
|
||||
container: container
|
||||
})
|
||||
lf.value.setTheme({
|
||||
bezier: {
|
||||
stroke: '#afafaf',
|
||||
strokeWidth: 1
|
||||
}
|
||||
})
|
||||
|
||||
lf.value.register(AiChatNode)
|
||||
lf.value.register(AppEdge)
|
||||
lf.value.setDefaultEdgeType('app-edge')
|
||||
|
||||
lf.value.render(graphData)
|
||||
|
||||
lf.value.translateCenter()
|
||||
}
|
||||
})
|
||||
const validate = () => {
|
||||
lf.value.graphModel.nodes.forEach((element: any) => {
|
||||
element.validate()
|
||||
})
|
||||
}
|
||||
const getGraphData = () => {
|
||||
console.log(JSON.stringify(lf.value.getGraphData()))
|
||||
}
|
||||
|
||||
const onmousedown = (shapeItem: ShapeItem) => {
|
||||
if (shapeItem.type) {
|
||||
lf.value.dnd.startDrag({
|
||||
type: shapeItem.type,
|
||||
properties: shapeItem.properties,
|
||||
icon: shapeItem.icon,
|
||||
})
|
||||
}
|
||||
if (shapeItem.callback) {
|
||||
shapeItem.callback(lf.value)
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
onmousedown
|
||||
})
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.lf-dnd-text {
|
||||
width: 200px;
|
||||
}
|
||||
.lf-dnd-shape {
|
||||
height: 50px;
|
||||
}
|
||||
.lf-node-selected {
|
||||
border: 1px solid #000;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
const icons: any = import.meta.glob('./icons/**.vue', { eager: true })
|
||||
function iconComponent(name) {
|
||||
const url = `./icons/${name}.vue`
|
||||
return icons[url]?.default || null
|
||||
}
|
||||
/**
|
||||
* 说明
|
||||
* type 与 nodes 文件对应
|
||||
*/
|
||||
const shapeList = [
|
||||
{
|
||||
type: 'ai-chat-node',
|
||||
text: '与 AI 大模型进行对话',
|
||||
label: 'AI 对话',
|
||||
icon: 'ai-chat-node-icon',
|
||||
properties: {
|
||||
height: '',
|
||||
stepName: 'AI 对话',
|
||||
input: [
|
||||
{
|
||||
key: '输入'
|
||||
}
|
||||
],
|
||||
output: [
|
||||
{
|
||||
key: '输出'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'search-dataset-node',
|
||||
text: '关联知识库,查找与问题相关的分段',
|
||||
label: '知识检索',
|
||||
icon: 'search-dataset-node-icon',
|
||||
properties: {
|
||||
height: '',
|
||||
stepName: '知识检索',
|
||||
input: [
|
||||
{
|
||||
key: '输入'
|
||||
}
|
||||
],
|
||||
output: [
|
||||
{
|
||||
key: '输出'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
export { shapeList, iconComponent }
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
<template>
|
||||
<NodeContainer :nodeModel="nodeModel">
|
||||
<el-form
|
||||
:model="chat_data"
|
||||
label-position="top"
|
||||
require-asterisk-position="right"
|
||||
class="mb-24"
|
||||
label-width="auto"
|
||||
ref="aiChatNodeFormRef"
|
||||
>
|
||||
<el-form-item label="知识库">
|
||||
<el-select v-model="chat_data.model" placeholder="请选择模型">
|
||||
<el-option label="Zone one" value="shanghai" />
|
||||
<el-option label="Zone two" value="beijing" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="提示词">
|
||||
<el-input v-model="chat_data.name" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</NodeContainer>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import NodeContainer from '@/components/workflow/common/node-container/index.vue'
|
||||
import type { FormInstance } from 'element-plus'
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
|
||||
const chat_data = computed({
|
||||
get: () => {
|
||||
if (props.nodeModel.properties.node_data) {
|
||||
return props.nodeModel.properties.node_data
|
||||
}
|
||||
return {}
|
||||
},
|
||||
set: (value) => {
|
||||
props.nodeModel.properties.node_data = value
|
||||
}
|
||||
})
|
||||
const props = defineProps<{ nodeModel: any }>()
|
||||
|
||||
const aiChatNodeFormRef = ref<FormInstance>()
|
||||
|
||||
const validate = () => {
|
||||
aiChatNodeFormRef.value?.validate()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
props.nodeModel.validate = validate
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<el-dialog v-model="aboutDialogVisible" class="about-dialog">
|
||||
<el-dialog v-model="aboutDialogVisible" class="about-dialog border-r-4">
|
||||
<template #header="{ titleId, titleClass }">
|
||||
<div class="flex-center">
|
||||
<div class="logo mr-4"></div>
|
||||
|
|
@ -57,7 +57,6 @@ defineExpose({ open })
|
|||
<style lang="scss" scope>
|
||||
.about-dialog {
|
||||
padding: 0 0 24px 0;
|
||||
border-radius: 4px;
|
||||
width: 600px;
|
||||
font-weight: 400;
|
||||
.el-dialog__header {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { watch, onBeforeMount, onMounted, onBeforeUnmount } from 'vue'
|
||||
import { nextTick, onBeforeMount, onMounted, onBeforeUnmount } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import useStore from '@/stores'
|
||||
import { DeviceType } from '@/enums/common'
|
||||
|
|
@ -9,7 +9,7 @@ const WIDTH = 600
|
|||
export default () => {
|
||||
const { common } = useStore()
|
||||
const _isMobile = () => {
|
||||
const rect = document.body.getBoundingClientRect()
|
||||
const rect = document.body?.getBoundingClientRect()
|
||||
return rect.width - 1 < WIDTH
|
||||
}
|
||||
|
||||
|
|
@ -25,9 +25,11 @@ export default () => {
|
|||
})
|
||||
|
||||
onMounted(() => {
|
||||
if (_isMobile()) {
|
||||
common.toggleDevice(DeviceType.Mobile)
|
||||
}
|
||||
nextTick(() => {
|
||||
if (_isMobile()) {
|
||||
common.toggleDevice(DeviceType.Mobile)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ export default {
|
|||
LimitDialog: {
|
||||
dialogTitle: 'Access Restrictions',
|
||||
showSourceLabel: 'Show Source',
|
||||
clientQueryLimitLabel: 'Client Query Limit',
|
||||
clientQueryLimitLabel: 'Each Client Query Limit',
|
||||
timesDays: 'Times/Day',
|
||||
whitelistLabel: 'Whitelist',
|
||||
whitelistPlaceholder: 'Please enter allowed third-party source addresses, one per line, such as:\nhttp://127.0.0.1:5678\nhttps://dataease.io',
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ export default {
|
|||
LimitDialog:{
|
||||
dialogTitle: '访问限制',
|
||||
showSourceLabel: '显示知识来源',
|
||||
clientQueryLimitLabel: '客户端提问限制',
|
||||
clientQueryLimitLabel: '每个客户端提问限制',
|
||||
timesDays: '次/天',
|
||||
whitelistLabel: '白名单',
|
||||
whitelistPlaceholder: '请输入允许嵌入第三方的源地址,一行一个,如:\nhttp://127.0.0.1:5678\nhttps://dataease.io',
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import directives from '@/directives'
|
|||
import App from './App.vue'
|
||||
import router from '@/router'
|
||||
import Components from '@/components'
|
||||
import i18n from './locales';
|
||||
import i18n from './locales'
|
||||
const app = createApp(App)
|
||||
app.use(store)
|
||||
app.use(directives)
|
||||
|
|
@ -24,6 +24,7 @@ app.use(ElementPlus, {
|
|||
app.use(theme)
|
||||
|
||||
app.use(router)
|
||||
app.use(i18n);
|
||||
app.use(i18n)
|
||||
app.use(Components)
|
||||
app.mount('#app')
|
||||
export { app }
|
||||
|
|
|
|||
|
|
@ -65,14 +65,8 @@ const useApplicationStore = defineStore({
|
|||
applicationApi
|
||||
.postAppAuthentication(token, loading)
|
||||
.then((res) => {
|
||||
const accessTokenObjStr = localStorage.getItem('accessTokenObj')
|
||||
if (accessTokenObjStr) {
|
||||
const accessTokenObj = JSON.parse(accessTokenObjStr)
|
||||
accessTokenObj[token] = res.data
|
||||
localStorage.setItem('accessTokenObj', JSON.stringify(accessTokenObj))
|
||||
} else {
|
||||
localStorage.setItem('accessTokenObj', JSON.stringify({ [token]: res.data }))
|
||||
}
|
||||
localStorage.setItem('accessToken', res.data)
|
||||
sessionStorage.setItem('accessToken', res.data)
|
||||
resolve(res)
|
||||
})
|
||||
.catch((error) => {
|
||||
|
|
|
|||
|
|
@ -16,8 +16,7 @@ const useUserStore = defineStore({
|
|||
userType: 1,
|
||||
userInfo: null,
|
||||
token: '',
|
||||
version: '',
|
||||
accessToken: ''
|
||||
version: ''
|
||||
}),
|
||||
actions: {
|
||||
getToken(): String | null {
|
||||
|
|
@ -27,13 +26,9 @@ const useUserStore = defineStore({
|
|||
return this.userType === 1 ? localStorage.getItem('token') : this.getAccessToken()
|
||||
},
|
||||
getAccessToken() {
|
||||
const accessTokenObjStr = localStorage.getItem('accessTokenObj')
|
||||
if (accessTokenObjStr && this.accessToken) {
|
||||
const accessTokenObj = JSON.parse(accessTokenObjStr)
|
||||
const result = accessTokenObj[this.accessToken]
|
||||
if (result) {
|
||||
return result
|
||||
}
|
||||
const accessToken = sessionStorage.getItem('accessToken')
|
||||
if (accessToken) {
|
||||
return accessToken
|
||||
}
|
||||
return localStorage.getItem('accessToken')
|
||||
},
|
||||
|
|
@ -55,9 +50,6 @@ const useUserStore = defineStore({
|
|||
changeUserType(num: number) {
|
||||
this.userType = num
|
||||
},
|
||||
setAccessToken(accessToken: string) {
|
||||
this.accessToken = accessToken
|
||||
},
|
||||
|
||||
async asyncGetVersion() {
|
||||
return UserApi.getVersion().then((ok) => {
|
||||
|
|
|
|||
|
|
@ -273,6 +273,9 @@ h5 {
|
|||
.border-b-light {
|
||||
border-bottom: 1px solid var(--el-border-color-lighter);
|
||||
}
|
||||
.border-r-4 {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.cursor {
|
||||
cursor: pointer;
|
||||
|
|
@ -460,6 +463,14 @@ h5 {
|
|||
color: var(--app-text-color-secondary);
|
||||
}
|
||||
|
||||
.layout-bg {
|
||||
background: var(--app-layout-bg-color);
|
||||
}
|
||||
|
||||
.white-bg {
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.app-warning-icon {
|
||||
font-size: 16px;
|
||||
color: var(--app-text-color-secondary);
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
--el-box-shadow-light: 0px 2px 4px 0px rgba(31, 35, 41, 0.12);
|
||||
--el-border-color: #dee0e3;
|
||||
--el-text-color-regular: #1f2329;
|
||||
--el-color-info: #8f959e;
|
||||
--el-color-info: #8f959e !important;
|
||||
}
|
||||
|
||||
.el-button {
|
||||
|
|
|
|||
|
|
@ -5,4 +5,4 @@
|
|||
@import 'nprogress/nprogress.css';
|
||||
@import 'highlight.js/styles/default.css';
|
||||
@import 'md-editor-v3/lib/style.css';
|
||||
@import './md-editor.scss';
|
||||
@import './md-editor.scss';
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
<template>
|
||||
<el-dialog title="API Key" v-model="dialogVisible" width="800">
|
||||
<el-button type="primary" class="mb-16" @click="createApiKey"> {{$t('views.applicationOverview.appInfo.APIKeyDialog.creatApiKey')}} </el-button>
|
||||
<el-button type="primary" class="mb-16" @click="createApiKey">
|
||||
{{ $t('views.applicationOverview.appInfo.APIKeyDialog.creatApiKey') }}
|
||||
</el-button>
|
||||
<el-table :data="apiKey" class="mb-16" :loading="loading">
|
||||
<el-table-column prop="secret_key" label="API Key">
|
||||
<template #default="{ row }">
|
||||
|
|
@ -12,28 +14,47 @@
|
|||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('views.applicationOverview.appInfo.APIKeyDialog.status')" width="60">
|
||||
<el-table-column
|
||||
:label="$t('views.applicationOverview.appInfo.APIKeyDialog.status')"
|
||||
width="60"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<div @click.stop>
|
||||
<el-switch size="small" v-model="row.is_active" @change="changeState($event, row)" />
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="name" :label="$t('views.applicationOverview.appInfo.APIKeyDialog.creationDate')" width="170">
|
||||
<el-table-column
|
||||
prop="name"
|
||||
:label="$t('views.applicationOverview.appInfo.APIKeyDialog.creationDate')"
|
||||
width="170"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
{{ datetimeFormat(row.create_time) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('views.applicationOverview.appInfo.APIKeyDialog.operations')" align="left" width="80">
|
||||
<el-table-column
|
||||
:label="$t('views.applicationOverview.appInfo.APIKeyDialog.operations')"
|
||||
align="left"
|
||||
width="80"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<span class="mr-4">
|
||||
<el-tooltip effect="dark" :content="$t('views.applicationOverview.appInfo.APIKeyDialog.settings')" placement="top">
|
||||
<el-tooltip
|
||||
effect="dark"
|
||||
:content="$t('views.applicationOverview.appInfo.APIKeyDialog.settings')"
|
||||
placement="top"
|
||||
>
|
||||
<el-button type="primary" text @click.stop="settingApiKey(row)">
|
||||
<el-icon><Setting /></el-icon>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
<el-tooltip effect="dark" :content="$t('views.applicationOverview.appInfo.APIKeyDialog.delete')" placement="top">
|
||||
<el-tooltip
|
||||
effect="dark"
|
||||
:content="$t('views.applicationOverview.appInfo.APIKeyDialog.delete')"
|
||||
placement="top"
|
||||
>
|
||||
<el-button type="primary" text @click="deleteApiKey(row)">
|
||||
<el-icon><Delete /></el-icon>
|
||||
</el-button>
|
||||
|
|
@ -82,7 +103,7 @@ function deleteApiKey(row: any) {
|
|||
t('views.applicationOverview.appInfo.APIKeyDialog.msgConfirm2'),
|
||||
{
|
||||
confirmButtonText: t('views.applicationOverview.appInfo.APIKeyDialog.confirmDelete'),
|
||||
cancelButtonText:t('views.applicationOverview.appInfo.APIKeyDialog.cancel'),
|
||||
cancelButtonText: t('views.applicationOverview.appInfo.APIKeyDialog.cancel'),
|
||||
confirmButtonClass: 'danger'
|
||||
}
|
||||
)
|
||||
|
|
@ -99,7 +120,9 @@ function changeState(bool: Boolean, row: any) {
|
|||
const obj = {
|
||||
is_active: bool
|
||||
}
|
||||
const str = bool ? t('views.applicationOverview.appInfo.APIKeyDialog.enabledSuccess') : t('views.applicationOverview.appInfo.APIKeyDialog.disabledSuccess')
|
||||
const str = bool
|
||||
? t('views.applicationOverview.appInfo.APIKeyDialog.enabledSuccess')
|
||||
: t('views.applicationOverview.appInfo.APIKeyDialog.disabledSuccess')
|
||||
overviewApi.putAPIKey(id as string, row.id, obj, loading).then((res) => {
|
||||
MsgSuccess(str)
|
||||
getApiKeyList()
|
||||
|
|
@ -129,15 +152,4 @@ function refresh() {
|
|||
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
<style lang="scss" scope>
|
||||
.embed-dialog {
|
||||
.code {
|
||||
color: var(--app-text-color) !important;
|
||||
background: var(--app-layout-bg-color);
|
||||
font-weight: 400;
|
||||
font-size: 13px;
|
||||
white-space: pre;
|
||||
height: 180px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style lang="scss" scope></style>
|
||||
|
|
|
|||
|
|
@ -1,13 +1,22 @@
|
|||
<template>
|
||||
<el-dialog :title="$t('views.applicationOverview.appInfo.EmbedDialog.embedDialogTitle')" v-model="dialogVisible" width="900" class="embed-dialog">
|
||||
<el-dialog
|
||||
:title="$t('views.applicationOverview.appInfo.EmbedDialog.embedDialogTitle')"
|
||||
v-model="dialogVisible"
|
||||
width="900"
|
||||
class="embed-dialog"
|
||||
>
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="12">
|
||||
<div class="border">
|
||||
<p class="title p-16 bold">{{$t('views.applicationOverview.appInfo.EmbedDialog.embedDialogTitle')}}</p>
|
||||
<p class="title p-16 bold">
|
||||
{{ $t('views.applicationOverview.appInfo.EmbedDialog.fullscreenModeTitle') }}
|
||||
</p>
|
||||
<img src="@/assets/window1.png" alt="" class="ml-8" />
|
||||
<div class="code border-t p-16">
|
||||
<div class="code layout-bg border-t p-16">
|
||||
<div class="flex-between">
|
||||
<span class="bold">{{$t('views.applicationOverview.appInfo.EmbedDialog.fullscreenModeTitle')}}</span>
|
||||
<span class="bold">{{
|
||||
$t('views.applicationOverview.appInfo.EmbedDialog.copyInstructions')
|
||||
}}</span>
|
||||
<el-button text @click="copyClick(source1)">
|
||||
<AppIcon iconName="app-copy"></AppIcon>
|
||||
</el-button>
|
||||
|
|
@ -20,11 +29,15 @@
|
|||
</el-col>
|
||||
<el-col :span="12">
|
||||
<div class="border">
|
||||
<p class="title p-16 bold">{{$t('views.applicationOverview.appInfo.EmbedDialog.floatingModeTitle')}}</p>
|
||||
<p class="title p-16 bold">
|
||||
{{ $t('views.applicationOverview.appInfo.EmbedDialog.floatingModeTitle') }}
|
||||
</p>
|
||||
<img src="@/assets/window2.png" alt="" class="ml-8" />
|
||||
<div class="code border-t p-16">
|
||||
<div class="flex-between">
|
||||
<span class="bold">{{$t('views.applicationOverview.appInfo.EmbedDialog.copyInstructions')}}</span>
|
||||
<span class="bold">{{
|
||||
$t('views.applicationOverview.appInfo.EmbedDialog.copyInstructions')
|
||||
}}</span>
|
||||
<el-button text @click="copyClick(source2)">
|
||||
<AppIcon iconName="app-copy"></AppIcon>
|
||||
</el-button>
|
||||
|
|
@ -91,7 +104,7 @@ defineExpose({ open })
|
|||
|
||||
.code {
|
||||
color: var(--app-text-color) !important;
|
||||
background: var(--app-layout-bg-color);
|
||||
|
||||
font-weight: 400;
|
||||
font-size: 13px;
|
||||
white-space: pre;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,18 @@
|
|||
<template>
|
||||
<el-dialog :title="$t('views.applicationOverview.appInfo.LimitDialog.dialogTitle')" v-model="dialogVisible">
|
||||
<el-dialog
|
||||
:title="$t('views.applicationOverview.appInfo.LimitDialog.dialogTitle')"
|
||||
v-model="dialogVisible"
|
||||
>
|
||||
<el-form label-position="top" ref="limitFormRef" :model="form">
|
||||
<el-form-item :label="$t('views.applicationOverview.appInfo.LimitDialog.showSourceLabel')" @click.prevent>
|
||||
<el-form-item
|
||||
:label="$t('views.applicationOverview.appInfo.LimitDialog.showSourceLabel')"
|
||||
@click.prevent
|
||||
>
|
||||
<el-switch size="small" v-model="form.show_source"></el-switch>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('views.applicationOverview.appInfo.LimitDialog.clientQueryLimitLabel')">
|
||||
<el-form-item
|
||||
:label="$t('views.applicationOverview.appInfo.LimitDialog.clientQueryLimitLabel')"
|
||||
>
|
||||
<el-input-number
|
||||
v-model="form.access_num"
|
||||
:min="0"
|
||||
|
|
@ -12,9 +20,14 @@
|
|||
controls-position="right"
|
||||
step-strictly
|
||||
/>
|
||||
<span class="ml-4">{{$t('views.applicationOverview.appInfo.LimitDialog.timesDays')}}</span>
|
||||
<span class="ml-4">{{
|
||||
$t('views.applicationOverview.appInfo.LimitDialog.timesDays')
|
||||
}}</span>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('views.applicationOverview.appInfo.LimitDialog.whitelistLabel')" @click.prevent>
|
||||
<el-form-item
|
||||
:label="$t('views.applicationOverview.appInfo.LimitDialog.whitelistLabel')"
|
||||
@click.prevent
|
||||
>
|
||||
<el-switch size="small" v-model="form.white_active"></el-switch>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
|
|
@ -28,9 +41,11 @@
|
|||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click.prevent="dialogVisible = false">{{$t('views.applicationOverview.appInfo.LimitDialog.cancelButtonText')}} </el-button>
|
||||
<el-button @click.prevent="dialogVisible = false"
|
||||
>{{ $t('views.applicationOverview.appInfo.LimitDialog.cancelButtonText') }}
|
||||
</el-button>
|
||||
<el-button type="primary" @click="submit(limitFormRef)" :loading="loading">
|
||||
{{$t('views.applicationOverview.appInfo.LimitDialog.saveButtonText')}}
|
||||
{{ $t('views.applicationOverview.appInfo.LimitDialog.saveButtonText') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
|
|
@ -103,15 +118,4 @@ const submit = async (formEl: FormInstance | undefined) => {
|
|||
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
<style lang="scss" scope>
|
||||
.embed-dialog {
|
||||
.code {
|
||||
color: var(--app-text-color) !important;
|
||||
background: var(--app-layout-bg-color);
|
||||
font-weight: 400;
|
||||
font-size: 13px;
|
||||
white-space: pre;
|
||||
height: 180px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style lang="scss" scope></style>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@
|
|||
<h4>创建应用</h4>
|
||||
</div>
|
||||
<div>
|
||||
<button @click="validate">点击校验</button>
|
||||
<button @click="getGraphData">点击获取流程数据</button>
|
||||
<el-button icon="Plus" @click="showPopover = !showPopover" v-click-outside="clickoutside">
|
||||
添加组件
|
||||
</el-button>
|
||||
|
|
@ -15,13 +17,13 @@
|
|||
</div>
|
||||
<!-- 下拉框 -->
|
||||
<el-collapse-transition>
|
||||
<div v-show="showPopover" class="workflow-dropdown-menu border">
|
||||
<div v-show="showPopover" class="workflow-dropdown-menu border border-r-4">
|
||||
<h5 class="title">基础组件</h5>
|
||||
<template v-for="(item, index) in shapeList" :key="index">
|
||||
<template v-for="(item, index) in menuNodes" :key="index">
|
||||
<div class="workflow-dropdown-item cursor flex p-8-12" @mousedown="onmousedown(item)">
|
||||
<component :is="iconComponent(item.icon)" class="mr-8 mt-4" />
|
||||
<component :is="iconComponent(item.icon)" class="mr-8 mt-4" :size="32" />
|
||||
<div class="pre-line">
|
||||
<div>{{ item.label }}</div>
|
||||
<div class="lighter">{{ item.label }}</div>
|
||||
<el-text type="info" size="small">{{ item.text }}</el-text>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -35,18 +37,9 @@
|
|||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onBeforeUnmount, computed } from 'vue'
|
||||
import Workflow from '@/components/workflow/index.vue'
|
||||
import { shapeList, iconComponent } from '@/components/workflow/menu-data'
|
||||
type ShapeItem = {
|
||||
type?: string
|
||||
text?: string
|
||||
icon?: string
|
||||
label?: string
|
||||
className?: string
|
||||
disabled?: boolean
|
||||
properties?: Record<string, any>
|
||||
callback?: (lf: LogicFlow, container: HTMLElement) => void
|
||||
}
|
||||
import Workflow from '@/workflow/index.vue'
|
||||
import { menuNodes } from '@/workflow/common/data.ts'
|
||||
import { iconComponent } from '@/workflow/icons/utils.ts'
|
||||
|
||||
const workflowRef = ref()
|
||||
|
||||
|
|
@ -56,10 +49,17 @@ function clickoutside() {
|
|||
showPopover.value = false
|
||||
}
|
||||
|
||||
function onmousedown(item: ShapeItem) {
|
||||
function onmousedown(item: any) {
|
||||
workflowRef.value?.onmousedown(item)
|
||||
}
|
||||
|
||||
function validate() {
|
||||
workflowRef.value?.validate()
|
||||
}
|
||||
function getGraphData() {
|
||||
workflowRef.value?.getGraphData()
|
||||
}
|
||||
|
||||
onMounted(() => {})
|
||||
|
||||
onBeforeUnmount(() => {})
|
||||
|
|
@ -70,7 +70,6 @@ onBeforeUnmount(() => {})
|
|||
background: #ffffff;
|
||||
}
|
||||
.workflow-main {
|
||||
width: 100vw;
|
||||
height: calc(100vh - var(--app-header-height) - 70px);
|
||||
}
|
||||
.workflow-dropdown-menu {
|
||||
|
|
@ -87,10 +86,10 @@ onBeforeUnmount(() => {})
|
|||
width: 240px;
|
||||
box-shadow: 0px 4px 8px 0px var(--app-text-color-light-1);
|
||||
background: #ffffff;
|
||||
border-radius: 4px;
|
||||
padding-bottom: 8px;
|
||||
|
||||
.title {
|
||||
padding: 8px 12px 4px;
|
||||
padding: 12px 12px 4px;
|
||||
}
|
||||
.workflow-dropdown-item {
|
||||
&:hover {
|
||||
|
|
|
|||
|
|
@ -206,7 +206,7 @@
|
|||
v-for="(item, index) in applicationForm.dataset_id_list"
|
||||
:key="index"
|
||||
>
|
||||
<el-card class="relate-dataset-card" shadow="never">
|
||||
<el-card class="relate-dataset-card border-r-4" shadow="never">
|
||||
<div class="flex-between">
|
||||
<div class="flex align-center">
|
||||
<AppAvatar
|
||||
|
|
@ -218,7 +218,7 @@
|
|||
<img src="@/assets/icon_web.svg" style="width: 58%" alt="" />
|
||||
</AppAvatar>
|
||||
|
||||
<AppAvatar v-else class="mr-12" shape="square" :size="32">
|
||||
<AppAvatar v-else class="mr-8" shape="square" :size="32">
|
||||
<img src="@/assets/icon_document.svg" style="width: 58%" alt="" />
|
||||
</AppAvatar>
|
||||
<div class="ellipsis">
|
||||
|
|
@ -525,7 +525,6 @@ onMounted(() => {
|
|||
.create-application {
|
||||
.relate-dataset-card {
|
||||
color: var(--app-text-color);
|
||||
border-radius: 4px;
|
||||
}
|
||||
.dialog-bg {
|
||||
border-radius: 8px;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,18 @@
|
|||
<template>
|
||||
<el-dialog :title="$t('views.application.applicationForm.dialogues.addDataset')" v-model="dialogVisible" width="600">
|
||||
<el-dialog
|
||||
:title="$t('views.application.applicationForm.dialogues.addDataset')"
|
||||
v-model="dialogVisible"
|
||||
width="600"
|
||||
append-to-body
|
||||
>
|
||||
<template #header="{ titleId, titleClass }">
|
||||
<div class="my-header flex">
|
||||
<h4 :id="titleId" :class="titleClass">{{$t('views.application.applicationForm.dialogues.addDataset')}}</h4>
|
||||
<h4 :id="titleId" :class="titleClass">
|
||||
{{ $t('views.application.applicationForm.dialogues.addDataset') }}
|
||||
</h4>
|
||||
<el-button link class="ml-16" @click="refresh">
|
||||
<el-icon class="mr-4"><Refresh /></el-icon>{{$t('views.application.applicationForm.dialogues.refresh')}}
|
||||
<el-icon class="mr-4"><Refresh /></el-icon
|
||||
>{{ $t('views.application.applicationForm.dialogues.refresh') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -19,8 +27,12 @@
|
|||
</el-row>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click.prevent="dialogVisible = false"> {{$t('views.application.applicationForm.buttons.cancel')}} </el-button>
|
||||
<el-button type="primary" @click="submitHandle"> {{$t('views.application.applicationForm.buttons.confirm')}} </el-button>
|
||||
<el-button @click.prevent="dialogVisible = false">
|
||||
{{ $t('views.application.applicationForm.buttons.cancel') }}
|
||||
</el-button>
|
||||
<el-button type="primary" @click="submitHandle">
|
||||
{{ $t('views.application.applicationForm.buttons.confirm') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
|
|
|||
|
|
@ -5,12 +5,15 @@
|
|||
class="param-dialog"
|
||||
v-model="dialogVisible"
|
||||
style="width: 550px"
|
||||
append-to-body
|
||||
>
|
||||
<div class="dialog-max-height">
|
||||
<el-scrollbar always>
|
||||
<div class="p-16">
|
||||
<el-form label-position="top" ref="paramFormRef" :model="form">
|
||||
<el-form-item :label="$t('views.application.applicationForm.dialogues.selectSearchMode')">
|
||||
<el-form-item
|
||||
:label="$t('views.application.applicationForm.dialogues.selectSearchMode')"
|
||||
>
|
||||
<el-radio-group v-model="form.search_mode" class="card__radio" @change="changeHandle">
|
||||
<el-card
|
||||
shadow="never"
|
||||
|
|
@ -18,8 +21,12 @@
|
|||
:class="form.search_mode === 'embedding' ? 'active' : ''"
|
||||
>
|
||||
<el-radio value="embedding" size="large">
|
||||
<p class="mb-4">{{$t('views.application.applicationForm.dialogues.vectorSearch')}}</p>
|
||||
<el-text type="info">{{$t('views.application.applicationForm.dialogues.vectorSearchTooltip')}}</el-text>
|
||||
<p class="mb-4">
|
||||
{{ $t('views.application.applicationForm.dialogues.vectorSearch') }}
|
||||
</p>
|
||||
<el-text type="info">{{
|
||||
$t('views.application.applicationForm.dialogues.vectorSearchTooltip')
|
||||
}}</el-text>
|
||||
</el-radio>
|
||||
</el-card>
|
||||
<el-card
|
||||
|
|
@ -28,16 +35,22 @@
|
|||
:class="form.search_mode === 'keywords' ? 'active' : ''"
|
||||
>
|
||||
<el-radio value="keywords" size="large">
|
||||
<p class="mb-4">{{$t('views.application.applicationForm.dialogues.fullTextSearch')}}</p>
|
||||
<el-text type="info">{{$t('views.application.applicationForm.dialogues.fullTextSearchTooltip')}}</el-text>
|
||||
<p class="mb-4">
|
||||
{{ $t('views.application.applicationForm.dialogues.fullTextSearch') }}
|
||||
</p>
|
||||
<el-text type="info">{{
|
||||
$t('views.application.applicationForm.dialogues.fullTextSearchTooltip')
|
||||
}}</el-text>
|
||||
</el-radio>
|
||||
</el-card>
|
||||
<el-card shadow="never" :class="form.search_mode === 'blend' ? 'active' : ''">
|
||||
<el-radio value="blend" size="large">
|
||||
<p class="mb-4">{{$t('views.application.applicationForm.dialogues.hybridSearch')}}</p>
|
||||
<el-text type="info"
|
||||
>{{$t('views.application.applicationForm.dialogues.hybridSearchTooltip')}}</el-text
|
||||
>
|
||||
<p class="mb-4">
|
||||
{{ $t('views.application.applicationForm.dialogues.hybridSearch') }}
|
||||
</p>
|
||||
<el-text type="info">{{
|
||||
$t('views.application.applicationForm.dialogues.hybridSearchTooltip')
|
||||
}}</el-text>
|
||||
</el-radio>
|
||||
</el-card>
|
||||
</el-radio-group>
|
||||
|
|
@ -47,7 +60,9 @@
|
|||
<el-form-item>
|
||||
<template #label>
|
||||
<div class="flex align-center">
|
||||
<span class="mr-4">{{$t('views.application.applicationForm.dialogues.similarityThreshold')}}</span>
|
||||
<span class="mr-4">{{
|
||||
$t('views.application.applicationForm.dialogues.similarityThreshold')
|
||||
}}</span>
|
||||
<el-tooltip effect="dark" content="相似度越高相关性越强。" placement="right">
|
||||
<AppIcon iconName="app-warning" class="app-warning-icon"></AppIcon>
|
||||
</el-tooltip>
|
||||
|
|
@ -65,7 +80,9 @@
|
|||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item :label="$t('views.application.applicationForm.dialogues.topReferences')">
|
||||
<el-form-item
|
||||
:label="$t('views.application.applicationForm.dialogues.topReferences')"
|
||||
>
|
||||
<el-input-number
|
||||
v-model="form.top_n"
|
||||
:min="1"
|
||||
|
|
@ -87,7 +104,10 @@
|
|||
class="custom-slider"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('views.application.applicationForm.dialogues.noReferencesAction')">
|
||||
<el-form-item
|
||||
v-if="!isWorkflow"
|
||||
:label="$t('views.application.applicationForm.dialogues.noReferencesAction')"
|
||||
>
|
||||
<el-form
|
||||
label-position="top"
|
||||
ref="noReferencesformRef"
|
||||
|
|
@ -102,7 +122,9 @@
|
|||
>
|
||||
<div>
|
||||
<el-radio value="ai_questioning">
|
||||
<p>{{$t('views.application.applicationForm.dialogues.continueQuestioning')}}</p>
|
||||
<p>
|
||||
{{ $t('views.application.applicationForm.dialogues.continueQuestioning') }}
|
||||
</p>
|
||||
<el-form-item
|
||||
v-if="form.no_references_setting.status === 'ai_questioning'"
|
||||
:label="$t('views.application.applicationForm.form.prompt.label')"
|
||||
|
|
@ -120,7 +142,7 @@
|
|||
</div>
|
||||
<div class="mt-8">
|
||||
<el-radio value="designated_answer">
|
||||
<p>{{$t('views.application.applicationForm.dialogues.provideAnswer')}}</p>
|
||||
<p>{{ $t('views.application.applicationForm.dialogues.provideAnswer') }}</p>
|
||||
<el-form-item
|
||||
v-if="form.no_references_setting.status === 'designated_answer'"
|
||||
prop="designated_answer"
|
||||
|
|
@ -144,9 +166,11 @@
|
|||
</div>
|
||||
<template #footer>
|
||||
<span class="dialog-footer p-16">
|
||||
<el-button @click.prevent="dialogVisible = false">{{$t('views.application.applicationForm.buttons.cancel')}}</el-button>
|
||||
<el-button @click.prevent="dialogVisible = false">{{
|
||||
$t('views.application.applicationForm.buttons.cancel')
|
||||
}}</el-button>
|
||||
<el-button type="primary" @click="submit(noReferencesformRef)" :loading="loading">
|
||||
{{$t('views.application.applicationForm.buttons.save')}}
|
||||
{{ $t('views.application.applicationForm.buttons.save') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
|
|
@ -164,8 +188,8 @@ const noReferencesformRef = ref()
|
|||
|
||||
const defaultValue = {
|
||||
ai_questioning: '{question}',
|
||||
// @ts-ignore
|
||||
designated_answer:t('views.application.applicationForm.dialogues.designated_answer')
|
||||
// @ts-ignore
|
||||
designated_answer: t('views.application.applicationForm.dialogues.designated_answer')
|
||||
}
|
||||
|
||||
const form = ref<any>({
|
||||
|
|
@ -185,13 +209,27 @@ const noReferencesform = ref<any>({
|
|||
})
|
||||
|
||||
const noReferencesRules = reactive<FormRules<any>>({
|
||||
ai_questioning: [{ required: true, message: t('views.application.applicationForm.dialogues.promptPlaceholder'), trigger: 'blur' }],
|
||||
designated_answer: [{ required: true, message: t('views.application.applicationForm.dialogues.concentPlaceholder'), trigger: 'blur' }]
|
||||
ai_questioning: [
|
||||
{
|
||||
required: true,
|
||||
message: t('views.application.applicationForm.dialogues.promptPlaceholder'),
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
designated_answer: [
|
||||
{
|
||||
required: true,
|
||||
message: t('views.application.applicationForm.dialogues.concentPlaceholder'),
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
const dialogVisible = ref<boolean>(false)
|
||||
const loading = ref(false)
|
||||
|
||||
const isWorkflow = ref(false)
|
||||
|
||||
watch(dialogVisible, (bool) => {
|
||||
if (!bool) {
|
||||
form.value = {
|
||||
|
|
@ -212,7 +250,8 @@ watch(dialogVisible, (bool) => {
|
|||
}
|
||||
})
|
||||
|
||||
const open = (data: any) => {
|
||||
const open = (data: any, type?: string) => {
|
||||
isWorkflow.value = type === 'workflow'
|
||||
form.value = { ...form.value, ...cloneDeep(data) }
|
||||
noReferencesform.value[form.value.no_references_setting.status] =
|
||||
form.value.no_references_setting.value
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="chat" v-loading="loading">
|
||||
<div class="chat layout-bg" v-loading="loading">
|
||||
<div class="chat__header">
|
||||
<div class="chat-width">
|
||||
<h2 class="ml-24">{{ applicationDetail?.name }}</h2>
|
||||
|
|
@ -54,13 +54,11 @@ function getProfile() {
|
|||
|
||||
onMounted(() => {
|
||||
user.changeUserType(2)
|
||||
user.setAccessToken(accessToken)
|
||||
getAccessToken(accessToken)
|
||||
})
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.chat {
|
||||
background-color: var(--app-layout-bg-color);
|
||||
overflow: hidden;
|
||||
&__header {
|
||||
background: var(--app-header-bg-color);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="chat-embed" v-loading="loading">
|
||||
<div class="chat-embed layout-bg" v-loading="loading">
|
||||
<div class="chat-embed__header">
|
||||
<div class="chat-width">
|
||||
<h4 class="ml-24">{{ applicationDetail?.name }}</h4>
|
||||
|
|
@ -15,7 +15,9 @@
|
|||
:chatId="currentChatId"
|
||||
@refresh="refresh"
|
||||
@scroll="handleScroll"
|
||||
></AiChat>
|
||||
class="AiChat-embed"
|
||||
>
|
||||
</AiChat>
|
||||
</div>
|
||||
|
||||
<el-button type="primary" link class="new-chat-button" @click="newChat">
|
||||
|
|
@ -194,13 +196,11 @@ function refresh(id: string) {
|
|||
|
||||
onMounted(() => {
|
||||
user.changeUserType(2)
|
||||
user.setAccessToken(accessToken)
|
||||
getAccessToken(accessToken)
|
||||
})
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.chat-embed {
|
||||
background-color: var(--app-layout-bg-color);
|
||||
overflow: hidden;
|
||||
&__header {
|
||||
background: var(--app-header-bg-color);
|
||||
|
|
@ -221,7 +221,7 @@ onMounted(() => {
|
|||
}
|
||||
.new-chat-button {
|
||||
position: absolute;
|
||||
bottom: 84px;
|
||||
bottom: 80px;
|
||||
left: 18px;
|
||||
z-index: 11;
|
||||
}
|
||||
|
|
@ -278,5 +278,13 @@ onMounted(() => {
|
|||
max-width: var(--app-chat-width, 860px);
|
||||
margin: 0 auto;
|
||||
}
|
||||
.AiChat-embed {
|
||||
.ai-chat__operate {
|
||||
padding-top: 38px;
|
||||
}
|
||||
.ai-chat__content {
|
||||
padding-bottom: 104px
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="chat-pc" :class="classObj" v-loading="loading">
|
||||
<div class="chat-pc layout-bg" :class="classObj" v-loading="loading">
|
||||
<div class="chat-pc__header">
|
||||
<h4 class="ml-24">{{ applicationDetail?.name }}</h4>
|
||||
</div>
|
||||
|
|
@ -59,9 +59,7 @@
|
|||
{{ paginationConfig.total }} 条提问
|
||||
</span>
|
||||
<el-dropdown class="ml-8">
|
||||
<el-tooltip effect="dark" content="导出聊天记录" placement="top">
|
||||
<AppIcon iconName="app-export" class="cursor"></AppIcon>
|
||||
</el-tooltip>
|
||||
<AppIcon iconName="app-export" class="cursor" title="导出聊天记录"></AppIcon>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item @click="exportMarkdown">导出 Markdown</el-dropdown-item>
|
||||
|
|
@ -286,13 +284,11 @@ async function exportHTML(): Promise<void> {
|
|||
|
||||
onMounted(() => {
|
||||
user.changeUserType(2)
|
||||
user.setAccessToken(accessToken)
|
||||
getAccessToken(accessToken)
|
||||
})
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.chat-pc {
|
||||
background-color: var(--app-layout-bg-color);
|
||||
overflow: hidden;
|
||||
|
||||
&__header {
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@
|
|||
shadow="hover"
|
||||
:title="item.title || '-'"
|
||||
:description="item.content"
|
||||
class="document-card cursor"
|
||||
class="document-card layout-bg layout-bg cursor "
|
||||
:class="item.is_active ? '' : 'disabled'"
|
||||
:showIcon="false"
|
||||
@click="editParagraph(item)"
|
||||
|
|
@ -144,7 +144,7 @@
|
|||
<el-input-number
|
||||
v-model="cloneForm.top_number"
|
||||
:min="1"
|
||||
:max="10"
|
||||
:max="100"
|
||||
controls-position="right"
|
||||
class="w-full"
|
||||
/>
|
||||
|
|
@ -362,7 +362,6 @@ onMounted(() => {})
|
|||
}
|
||||
.document-card {
|
||||
height: 210px;
|
||||
background: var(--app-layout-bg-color);
|
||||
border: 1px solid var(--app-layout-bg-color);
|
||||
&:hover {
|
||||
background: #ffffff;
|
||||
|
|
|
|||
|
|
@ -9,7 +9,13 @@
|
|||
@submit.prevent
|
||||
>
|
||||
<el-form-item label="关联问题">
|
||||
<el-input v-model="form.problem_text" placeholder="关联问题"> </el-input>
|
||||
<el-input
|
||||
v-model="form.problem_text"
|
||||
placeholder="关联问题"
|
||||
maxlength="256"
|
||||
show-word-limit
|
||||
>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="内容" prop="content">
|
||||
<el-input
|
||||
|
|
@ -23,7 +29,12 @@
|
|||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="标题">
|
||||
<el-input v-model="form.title" placeholder="请给当前内容设置一个标题,以便管理查看">
|
||||
<el-input
|
||||
show-word-limit
|
||||
v-model="form.title"
|
||||
placeholder="请给当前内容设置一个标题,以便管理查看"
|
||||
maxlength="256"
|
||||
>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="选择知识库" prop="dataset_id">
|
||||
|
|
|
|||
|
|
@ -8,7 +8,14 @@
|
|||
@submit.prevent
|
||||
>
|
||||
<el-form-item label="分段标题">
|
||||
<el-input v-if="isEdit" v-model="form.title" placeholder="请输入分段标题"> </el-input>
|
||||
<el-input
|
||||
v-if="isEdit"
|
||||
v-model="form.title"
|
||||
placeholder="请输入分段标题"
|
||||
maxlength="256"
|
||||
show-word-limit
|
||||
>
|
||||
</el-input>
|
||||
<span class="lighter" v-else>{{ form.title || '-' }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="分段内容" prop="content">
|
||||
|
|
@ -24,7 +31,7 @@
|
|||
:footers="footers"
|
||||
>
|
||||
<template #defFooters>
|
||||
<span style="margin-left: -6px;">/ 4096</span>
|
||||
<span style="margin-left: -6px">/ 4096</span>
|
||||
</template>
|
||||
</MarkdownEditor>
|
||||
<MdPreview
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
<template>
|
||||
<el-cascader :options="options" @visible-change="visibleChange" v-bind="$attrs" separator=" > ">
|
||||
<template #default="{ node, data }">
|
||||
<span>{{ data.label }}</span>
|
||||
</template>
|
||||
</el-cascader>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
const props = defineProps<{
|
||||
nodeModel: any
|
||||
}>()
|
||||
|
||||
const options = ref([])
|
||||
|
||||
function visibleChange(bool: boolean) {
|
||||
if (bool) {
|
||||
options.value = []
|
||||
getIncomingNode(props.nodeModel.id)
|
||||
}
|
||||
}
|
||||
|
||||
function getIncomingNode(id: string) {
|
||||
const list = props.nodeModel.graphModel.getNodeIncomingNode(id)
|
||||
if (list.length > 0) {
|
||||
list.forEach((item) => {
|
||||
if (!options.value.some((obj: any) => obj.id === item.id)) {
|
||||
options.value.unshift({
|
||||
value: item.id,
|
||||
label: item.properties.stepName,
|
||||
children: item.properties?.fields || []
|
||||
})
|
||||
}
|
||||
})
|
||||
list.forEach((item) => {
|
||||
getIncomingNode(item.id)
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped></style>
|
||||
|
|
@ -8,7 +8,7 @@
|
|||
</div>
|
||||
<div><slot></slot></div>
|
||||
</div>
|
||||
<div class="input-container" v-resize="resetInputContainer">
|
||||
<!-- <div class="input-container" v-resize="resetInputContainer">
|
||||
<div v-for="(item, index) in nodeModel.properties.input" :key="index" class="step-field">
|
||||
<span>{{ item.key }}</span>
|
||||
</div>
|
||||
|
|
@ -21,13 +21,14 @@
|
|||
>
|
||||
<span>{{ item.key }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { iconComponent } from '../../menu-data'
|
||||
import { iconComponent } from '../icons/utils'
|
||||
|
||||
const height = ref<{
|
||||
stepContainerHeight: number
|
||||
inputContainerHeight: number
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-button-group>
|
||||
<el-button size="small" @click="zoomIn">放大</el-button>
|
||||
<el-button size="small" @click="zoomOut">缩小</el-button>
|
||||
<el-button size="small" @click="fitView">适应</el-button>
|
||||
</el-button-group>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const props = defineProps({
|
||||
lf: Object || String || null
|
||||
})
|
||||
|
||||
function zoomIn() {
|
||||
props.lf?.zoom(true)
|
||||
}
|
||||
function zoomOut() {
|
||||
props.lf?.zoom(false)
|
||||
}
|
||||
function fitView() {
|
||||
props.lf?.resetZoom()
|
||||
props.lf?.resetTranslate()
|
||||
// props.lf?.fitView()
|
||||
}
|
||||
</script>
|
||||
<style scoped></style>
|
||||
|
|
@ -1,8 +1,10 @@
|
|||
import Components from '@/components'
|
||||
import ElementPlus from 'element-plus'
|
||||
import { HtmlNode, HtmlNodeModel } from '@logicflow/core'
|
||||
import * as ElementPlusIcons from '@element-plus/icons-vue'
|
||||
import { HtmlNode, HtmlNodeModel, BaseEdge } from '@logicflow/core'
|
||||
import { createApp, h } from 'vue'
|
||||
import directives from '@/directives'
|
||||
import i18n from '@/locales'
|
||||
|
||||
class AppNode extends HtmlNode {
|
||||
isMounted
|
||||
|
|
@ -24,6 +26,10 @@ class AppNode extends HtmlNode {
|
|||
this.app.use(ElementPlus)
|
||||
this.app.use(Components)
|
||||
this.app.use(directives)
|
||||
this.app.use(i18n)
|
||||
for (const [key, component] of Object.entries(ElementPlusIcons)) {
|
||||
this.app.component(key, component)
|
||||
}
|
||||
}
|
||||
|
||||
setHtml(rootEl: HTMLElement) {
|
||||
|
|
@ -31,7 +37,7 @@ class AppNode extends HtmlNode {
|
|||
this.isMounted = true
|
||||
const node = document.createElement('div')
|
||||
rootEl.appendChild(node)
|
||||
this.app.mount(node)
|
||||
this.app?.mount(node)
|
||||
} else {
|
||||
if (this.r && this.r.component) {
|
||||
this.r.component.props.properties = this.props.model.getProperties()
|
||||
|
|
@ -45,7 +51,7 @@ class AppNodeModel extends HtmlNodeModel {
|
|||
* 给model自定义添加字段方法
|
||||
*/
|
||||
addField(item: any) {
|
||||
this.properties.fields.unshift(item)
|
||||
this.properties.output.unshift(item)
|
||||
this.setAttributes()
|
||||
// 为了保持节点顶部位置不变,在节点变化后,对节点进行一个位移,位移距离为添加高度的一半。
|
||||
this.move(0, 24 / 2)
|
||||
|
|
@ -80,6 +86,7 @@ class AppNodeModel extends HtmlNodeModel {
|
|||
}
|
||||
return style
|
||||
}
|
||||
|
||||
setHeight(height: number, inputContainerHeight: number, outputContainerHeight: number) {
|
||||
this.height = height + inputContainerHeight + outputContainerHeight + 100
|
||||
this.baseHeight = height
|
||||
|
|
@ -96,7 +103,7 @@ class AppNodeModel extends HtmlNodeModel {
|
|||
})
|
||||
}
|
||||
setAttributes() {
|
||||
this.width = 500
|
||||
this.width = this.properties?.width || 340
|
||||
|
||||
const circleOnlyAsTarget = {
|
||||
message: '只允许从右边的锚点连出',
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
/**
|
||||
* 说明
|
||||
* type 与 nodes 文件对应
|
||||
*/
|
||||
export const baseNodes = [
|
||||
{
|
||||
id: 'base-node',
|
||||
type: 'base-node',
|
||||
x: 200,
|
||||
y: 270,
|
||||
properties: {
|
||||
height: 200,
|
||||
stepName: '基本信息',
|
||||
node_data: {
|
||||
name: '',
|
||||
desc: '',
|
||||
prologue: ''
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'start-node',
|
||||
type: 'start-node',
|
||||
x: 180,
|
||||
y: 720,
|
||||
properties: {
|
||||
height: 200,
|
||||
stepName: '开始',
|
||||
output: [{ key: '' }],
|
||||
fields: [
|
||||
{
|
||||
label: '用户问题',
|
||||
value: 'question'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
export const menuNodes = [
|
||||
{
|
||||
type: 'ai-chat-node',
|
||||
text: '与 AI 大模型进行对话',
|
||||
label: 'AI 对话',
|
||||
icon: 'ai-chat-node-icon',
|
||||
properties: {
|
||||
height: '',
|
||||
stepName: 'AI 对话',
|
||||
input: [
|
||||
{
|
||||
key: ''
|
||||
}
|
||||
],
|
||||
output: [
|
||||
{
|
||||
key: ''
|
||||
}
|
||||
],
|
||||
fields: [
|
||||
{
|
||||
label: 'AI 回答内容',
|
||||
value: 'content'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'search-dataset-node',
|
||||
text: '关联知识库,查找与问题相关的分段',
|
||||
label: '知识库检索',
|
||||
icon: 'search-dataset-node-icon',
|
||||
properties: {
|
||||
height: '',
|
||||
stepName: '知识库检索',
|
||||
input: [
|
||||
{
|
||||
key: '输入'
|
||||
}
|
||||
],
|
||||
output: [
|
||||
{
|
||||
key: '输出'
|
||||
}
|
||||
],
|
||||
fields: [
|
||||
{
|
||||
label: '检索结果',
|
||||
value: 'data'
|
||||
},
|
||||
{
|
||||
label: '满足直接回答的分段内容',
|
||||
value: 'paragraph'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'condition-node',
|
||||
text: '根据不同条件执行不同的节点',
|
||||
label: '判断器',
|
||||
icon: 'condition-node-icon',
|
||||
properties: {
|
||||
width: 600,
|
||||
stepName: '判断器',
|
||||
input: [
|
||||
{
|
||||
key: '输入'
|
||||
}
|
||||
],
|
||||
output: [
|
||||
{
|
||||
key: '输出'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
@ -3,12 +3,16 @@ import { BezierEdge, BezierEdgeModel } from '@logicflow/core'
|
|||
class CustomEdge2 extends BezierEdge {}
|
||||
|
||||
class CustomEdgeModel2 extends BezierEdgeModel {
|
||||
getArrowStyle() {
|
||||
return { offet: 0 }
|
||||
}
|
||||
|
||||
getEdgeStyle() {
|
||||
const style = super.getEdgeStyle()
|
||||
|
||||
// svg属性
|
||||
style.strokeWidth = 1
|
||||
style.stroke = '#ababac'
|
||||
style.strokeWidth = 2
|
||||
style.stroke = '#BBBFC4'
|
||||
return style
|
||||
}
|
||||
/**
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<template>
|
||||
<AppAvatar shape="square" style="background: #FF8800;">
|
||||
<img src="@/assets/icon_hi.svg" style="width: 75%" alt="" />
|
||||
</AppAvatar>
|
||||
</template>
|
||||
<script setup lang="ts"></script>
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<template>
|
||||
<AppAvatar shape="square" style="background: #14C0FF;">
|
||||
<img src="@/assets/icon_condition.svg" style="width: 75%" alt="" />
|
||||
</AppAvatar>
|
||||
</template>
|
||||
<script setup lang="ts"></script>
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<template>
|
||||
<AppAvatar shape="square" style="background: #D136D1;">
|
||||
<img src="@/assets/icon_start.svg" style="width: 75%" alt="" />
|
||||
</AppAvatar>
|
||||
</template>
|
||||
<script setup lang="ts"></script>
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
const icons: any = import.meta.glob('./**.vue', { eager: true })
|
||||
export function iconComponent(name: string) {
|
||||
const url = `./${name}.vue`
|
||||
return icons[url]?.default || null
|
||||
}
|
||||
|
|
@ -0,0 +1,332 @@
|
|||
<template>
|
||||
<div className="workflow-app" id="container"></div>
|
||||
<!-- 辅助工具栏 -->
|
||||
<Control class="workflow-control" v-if="lf" :lf="lf"></Control>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import LogicFlow from '@logicflow/core'
|
||||
import { ref, onMounted } from 'vue'
|
||||
import AppEdge from './common/edge.ts'
|
||||
import Control from './common/NodeControl.vue'
|
||||
import { baseNodes } from '@/workflow/common/data.ts'
|
||||
import '@logicflow/extension/lib/style/index.css'
|
||||
import '@logicflow/core/dist/style/index.css'
|
||||
const nodes: any = import.meta.glob('./nodes/**/index.ts', { eager: true })
|
||||
|
||||
defineOptions({ name: 'WorkFlow' })
|
||||
|
||||
type ShapeItem = {
|
||||
type?: string
|
||||
text?: string
|
||||
icon?: string
|
||||
label?: string
|
||||
className?: string
|
||||
disabled?: boolean
|
||||
properties?: Record<string, any>
|
||||
callback?: (lf: LogicFlow, container?: HTMLElement) => void
|
||||
}
|
||||
|
||||
const graphData = {
|
||||
nodes: [
|
||||
...baseNodes,
|
||||
{
|
||||
id: '34902d3d-a3ff-497f-b8e1-0c34a44d7dd5',
|
||||
type: 'search-dataset-node',
|
||||
x: 600,
|
||||
y: 250,
|
||||
properties: {
|
||||
height: 200,
|
||||
stepName: '知识库检索',
|
||||
input: [{ key: '输入' }],
|
||||
output: [{ key: '输出' }],
|
||||
fields: [
|
||||
{
|
||||
label: '检索结果',
|
||||
value: 'data'
|
||||
},
|
||||
{
|
||||
label: '满足直接回答的分段内容',
|
||||
value: 'paragraph'
|
||||
}
|
||||
],
|
||||
node_data: {
|
||||
dataset_id_list: [],
|
||||
dataset_setting: {
|
||||
top_n: 3,
|
||||
similarity: 0.6,
|
||||
max_paragraph_char_number: 5000,
|
||||
search_mode: 'embedding'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
id: '34902d3d-a3ff-497f-b8e1-0c34a44d7dd6',
|
||||
type: 'condition-node',
|
||||
x: 810,
|
||||
y: 764,
|
||||
properties: {
|
||||
height: 200,
|
||||
width: 600,
|
||||
stepName: '判断器',
|
||||
input: [{ key: '输入' }],
|
||||
output: [{ key: '9208' }, { key: '1143' }, { key: '输出' }],
|
||||
node_data: {
|
||||
branch: [
|
||||
{
|
||||
conditions: [{ field: [], compare: '', value: '' }],
|
||||
id: '2391',
|
||||
condition: 'and'
|
||||
},
|
||||
{
|
||||
conditions: [{ field: [], compare: '', value: '' }],
|
||||
id: '1143',
|
||||
condition: 'and'
|
||||
},
|
||||
{
|
||||
conditions: [{ field: [], compare: '', value: '' }],
|
||||
id: '9208',
|
||||
condition: 'and'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
id: '03597cb0-ed4c-4bcb-b25b-3b358f72b266',
|
||||
type: 'ai-chat-node',
|
||||
x: 1330,
|
||||
y: 690,
|
||||
properties: {
|
||||
height: '',
|
||||
stepName: 'AI 对话',
|
||||
input: [{ key: '输入' }],
|
||||
output: [{ key: '输出' }],
|
||||
node_data: { model: '', name: '' }
|
||||
}
|
||||
},
|
||||
{
|
||||
id: '6649ee86-348c-4d68-9cad-71f0612beb05',
|
||||
type: 'ai-chat-node',
|
||||
x: 1320,
|
||||
y: 990,
|
||||
properties: {
|
||||
height: '',
|
||||
stepName: 'AI 对话',
|
||||
input: [{ key: '输入' }],
|
||||
output: [{ key: '输出' }],
|
||||
node_data: { model: '', name: '' }
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'ede4a9c9-e2fa-40ac-9215-2e1ad04f09c5',
|
||||
type: 'ai-chat-node',
|
||||
x: 1360,
|
||||
y: 1300,
|
||||
properties: {
|
||||
height: '',
|
||||
stepName: 'AI 对话',
|
||||
input: [{ key: '输入' }],
|
||||
output: [{ key: '输出' }],
|
||||
node_data: { model: '', name: '' }
|
||||
}
|
||||
}
|
||||
],
|
||||
edges: [
|
||||
{
|
||||
id: '8dde4baf-0965-4999-9d37-f867ab16d638',
|
||||
type: 'app-edge',
|
||||
sourceNodeId: 'start-node',
|
||||
targetNodeId: '34902d3d-a3ff-497f-b8e1-0c34a44d7dd5',
|
||||
startPoint: { x: 340, y: 788 },
|
||||
endPoint: { x: 440, y: 469 },
|
||||
properties: {},
|
||||
pointsList: [
|
||||
{ x: 340, y: 788 },
|
||||
{ x: 450, y: 788 },
|
||||
{ x: 330, y: 469 },
|
||||
{ x: 440, y: 469 }
|
||||
],
|
||||
sourceAnchorId: 'start-node_输出_right',
|
||||
targetAnchorId: '34902d3d-a3ff-497f-b8e1-0c34a44d7dd5_输入_left'
|
||||
},
|
||||
{
|
||||
id: 'b60de7b4-d8d2-4e7d-bba6-3738b9e523b9',
|
||||
type: 'app-edge',
|
||||
sourceNodeId: '34902d3d-a3ff-497f-b8e1-0c34a44d7dd5',
|
||||
targetNodeId: '34902d3d-a3ff-497f-b8e1-0c34a44d7dd6',
|
||||
startPoint: { x: 760, y: 464 },
|
||||
endPoint: { x: 520, y: 973.75 },
|
||||
properties: {},
|
||||
pointsList: [
|
||||
{ x: 760, y: 464 },
|
||||
{ x: 870, y: 464 },
|
||||
{ x: 410, y: 973.75 },
|
||||
{ x: 520, y: 973.75 }
|
||||
],
|
||||
sourceAnchorId: '34902d3d-a3ff-497f-b8e1-0c34a44d7dd5_输出_right',
|
||||
targetAnchorId: '34902d3d-a3ff-497f-b8e1-0c34a44d7dd6_输入_left'
|
||||
},
|
||||
{
|
||||
id: '8de0da85-b5d6-459a-8be9-4d00082baf1c',
|
||||
type: 'app-edge',
|
||||
sourceNodeId: '34902d3d-a3ff-497f-b8e1-0c34a44d7dd6',
|
||||
targetNodeId: '03597cb0-ed4c-4bcb-b25b-3b358f72b266',
|
||||
startPoint: { x: 1100, y: 968.75 },
|
||||
endPoint: { x: 1170, y: 803 },
|
||||
properties: {},
|
||||
pointsList: [
|
||||
{ x: 1100, y: 968.75 },
|
||||
{ x: 1210, y: 968.75 },
|
||||
{ x: 1060, y: 803 },
|
||||
{ x: 1170, y: 803 }
|
||||
],
|
||||
sourceAnchorId: '34902d3d-a3ff-497f-b8e1-0c34a44d7dd6_9208_right',
|
||||
targetAnchorId: '03597cb0-ed4c-4bcb-b25b-3b358f72b266_输入_left'
|
||||
},
|
||||
{
|
||||
id: '3e66821a-ce0a-4ef9-a6cf-ea4095158261',
|
||||
type: 'app-edge',
|
||||
sourceNodeId: '34902d3d-a3ff-497f-b8e1-0c34a44d7dd6',
|
||||
targetNodeId: '6649ee86-348c-4d68-9cad-71f0612beb05',
|
||||
startPoint: { x: 1100, y: 992.75 },
|
||||
endPoint: { x: 1160, y: 1103 },
|
||||
properties: {},
|
||||
pointsList: [
|
||||
{ x: 1100, y: 992.75 },
|
||||
{ x: 1210, y: 992.75 },
|
||||
{ x: 1050, y: 1103 },
|
||||
{ x: 1160, y: 1103 }
|
||||
],
|
||||
sourceAnchorId: '34902d3d-a3ff-497f-b8e1-0c34a44d7dd6_1143_right',
|
||||
targetAnchorId: '6649ee86-348c-4d68-9cad-71f0612beb05_输入_left'
|
||||
},
|
||||
{
|
||||
id: 'cc52ab90-58e7-4f54-9660-d9fb16f776ea',
|
||||
type: 'app-edge',
|
||||
sourceNodeId: '34902d3d-a3ff-497f-b8e1-0c34a44d7dd6',
|
||||
targetNodeId: 'ede4a9c9-e2fa-40ac-9215-2e1ad04f09c5',
|
||||
startPoint: { x: 1100, y: 1016.75 },
|
||||
endPoint: { x: 1200, y: 1413 },
|
||||
properties: {},
|
||||
pointsList: [
|
||||
{ x: 1100, y: 1016.75 },
|
||||
{ x: 1210, y: 1016.75 },
|
||||
{ x: 1090, y: 1413 },
|
||||
{ x: 1200, y: 1413 }
|
||||
],
|
||||
sourceAnchorId: '34902d3d-a3ff-497f-b8e1-0c34a44d7dd6_输出_right',
|
||||
targetAnchorId: 'ede4a9c9-e2fa-40ac-9215-2e1ad04f09c5_输入_left'
|
||||
}
|
||||
]
|
||||
}
|
||||
const lf = ref()
|
||||
|
||||
onMounted(() => {
|
||||
const container: any = document.querySelector('#container')
|
||||
if (container) {
|
||||
lf.value = new LogicFlow({
|
||||
textEdit: false,
|
||||
background: {
|
||||
backgroundColor: '#f5f6f7'
|
||||
},
|
||||
grid: {
|
||||
size: 10,
|
||||
type: 'dot',
|
||||
config: {
|
||||
color: '#DEE0E3',
|
||||
thickness: 1
|
||||
}
|
||||
},
|
||||
// keyboard: {
|
||||
// enabled: true,
|
||||
// shortcuts: [
|
||||
// {
|
||||
// keys: ['backspace'],
|
||||
// callback: () => {
|
||||
// const elements = lf.value.getSelectElements(true)
|
||||
// if (
|
||||
// (elements.edges && elements.edges.length > 0) ||
|
||||
// (elements.nodes && elements.nodes.length > 0)
|
||||
// ) {
|
||||
// const r = window.confirm('确定要删除吗?')
|
||||
// if (r) {
|
||||
// lf.value.clearSelectElements()
|
||||
// elements.edges.forEach((edge: any) => {
|
||||
// lf.value.deleteEdge(edge.id)
|
||||
// })
|
||||
// elements.nodes.forEach((node: any) => {
|
||||
// lf.value.deleteNode(node.id)
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// ]
|
||||
// },
|
||||
isSilentMode: false,
|
||||
container: container
|
||||
})
|
||||
lf.value.setTheme({
|
||||
bezier: {
|
||||
stroke: '#afafaf',
|
||||
strokeWidth: 1
|
||||
}
|
||||
})
|
||||
|
||||
lf.value.batchRegister([...Object.keys(nodes).map((key) => nodes[key].default), AppEdge])
|
||||
lf.value.setDefaultEdgeType('app-edge')
|
||||
|
||||
lf.value.render(graphData)
|
||||
}
|
||||
})
|
||||
const validate = () => {
|
||||
lf.value.graphModel.nodes.forEach((element: any) => {
|
||||
element?.validate?.()
|
||||
})
|
||||
}
|
||||
const getGraphData = () => {
|
||||
console.log(JSON.stringify(lf.value.getGraphData()))
|
||||
}
|
||||
|
||||
const onmousedown = (shapeItem: ShapeItem) => {
|
||||
if (shapeItem.type) {
|
||||
lf.value.dnd.startDrag({
|
||||
type: shapeItem.type,
|
||||
properties: shapeItem.properties,
|
||||
icon: shapeItem.icon
|
||||
})
|
||||
}
|
||||
if (shapeItem.callback) {
|
||||
shapeItem.callback(lf.value)
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
onmousedown,
|
||||
validate,
|
||||
getGraphData
|
||||
})
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.workflow-app {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
.workflow-control {
|
||||
position: absolute;
|
||||
bottom: 24px;
|
||||
left: 24px;
|
||||
z-index: 2;
|
||||
}
|
||||
// .lf-dnd-text {
|
||||
// width: 200px;
|
||||
// }
|
||||
// .lf-dnd-shape {
|
||||
// height: 50px;
|
||||
// }
|
||||
// .lf-node-selected {
|
||||
// border: 1px solid #000;
|
||||
// }
|
||||
</style>
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import ChatNodeVue from './index.vue'
|
||||
import { AppNode, AppNodeModel } from '@/components/workflow/common/app-node/index'
|
||||
import { AppNode, AppNodeModel } from '@/workflow/common/app-node.ts'
|
||||
class ChatNode extends AppNode {
|
||||
constructor(props: any) {
|
||||
super(props, ChatNodeVue)
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<NodeContainer :nodeModel="nodeModel">
|
||||
<h5 class="title-decoration-1 mb-8">节点设置</h5>
|
||||
<el-card shadow="never" class="card-never">
|
||||
<el-card shadow="never" class="card-never" style="--el-card-padding: 12px">
|
||||
<el-form
|
||||
@submit.prevent
|
||||
:model="chat_data"
|
||||
|
|
@ -34,14 +34,15 @@
|
|||
}"
|
||||
prop="name"
|
||||
>
|
||||
<el-input v-model="chat_data.name" @focus="handleFocus" />
|
||||
<el-input v-model="chat_data.name" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</NodeContainer>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import NodeContainer from '@/components/workflow/common/node-container/index.vue'
|
||||
import { set } from 'lodash'
|
||||
import NodeContainer from '@/workflow/common/NodeContainer.vue'
|
||||
import type { FormInstance } from 'element-plus'
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
|
||||
|
|
@ -59,9 +60,7 @@ const chat_data = computed({
|
|||
}
|
||||
})
|
||||
const props = defineProps<{ nodeModel: any }>()
|
||||
const handleFocus = () => {
|
||||
props.nodeModel.isSelected = false
|
||||
}
|
||||
|
||||
const aiChatNodeFormRef = ref<FormInstance>()
|
||||
|
||||
const validate = () => {
|
||||
|
|
@ -69,7 +68,7 @@ const validate = () => {
|
|||
}
|
||||
|
||||
onMounted(() => {
|
||||
props.nodeModel.validate = validate
|
||||
set(props.nodeModel, 'validate', validate)
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
import BaseNodeVue from './index.vue'
|
||||
import { AppNode, AppNodeModel } from '@/workflow/common/app-node.ts'
|
||||
class BaseNode extends AppNode {
|
||||
constructor(props: any) {
|
||||
super(props, BaseNodeVue)
|
||||
}
|
||||
}
|
||||
export default {
|
||||
type: 'base-node',
|
||||
model: AppNodeModel,
|
||||
view: BaseNode
|
||||
}
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
<template>
|
||||
<NodeContainer :nodeModel="nodeModel">
|
||||
<el-form
|
||||
@submit.prevent
|
||||
:model="chat_data"
|
||||
label-position="top"
|
||||
require-asterisk-position="right"
|
||||
class="mb-24"
|
||||
label-width="auto"
|
||||
ref="baseNodeFormRef"
|
||||
>
|
||||
<el-form-item
|
||||
label="应用名称"
|
||||
prop="name"
|
||||
:rules="{
|
||||
message: '应用名称不能为空',
|
||||
trigger: 'blur',
|
||||
required: true
|
||||
}"
|
||||
>
|
||||
<el-input
|
||||
v-model="chat_data.name"
|
||||
maxlength="64"
|
||||
placeholder="请输入应用名称"
|
||||
show-word-limit
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="应用描述">
|
||||
<el-input
|
||||
v-model="chat_data.desc"
|
||||
placeholder="请输入应用描述"
|
||||
:rows="3"
|
||||
type="textarea"
|
||||
maxlength="256"
|
||||
show-word-limit
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="开场白">
|
||||
<MdEditor
|
||||
style="height: 150px"
|
||||
v-model="chat_data.prologue"
|
||||
:preview="false"
|
||||
:toolbars="[]"
|
||||
:footers="[]"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</NodeContainer>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { set } from 'lodash'
|
||||
import NodeContainer from '@/workflow/common/NodeContainer.vue'
|
||||
import type { FormInstance } from 'element-plus'
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { MdEditor } from 'md-editor-v3'
|
||||
const props = defineProps<{ nodeModel: any }>()
|
||||
|
||||
const chat_data = computed({
|
||||
get: () => {
|
||||
if (props.nodeModel.properties.node_data) {
|
||||
return props.nodeModel.properties.node_data
|
||||
} else {
|
||||
props.nodeModel.properties.node_data = {
|
||||
name: '',
|
||||
desc: '',
|
||||
prologue:
|
||||
'您好,我是 MaxKB 小助手,您可以向我提出 MaxKB 使用问题。\n- MaxKB 主要功能有什么?\n- MaxKB 支持哪些大语言模型?\n- MaxKB 支持哪些文档类型?'
|
||||
}
|
||||
}
|
||||
return props.nodeModel.properties.node_data
|
||||
},
|
||||
set: (value) => {
|
||||
set(props.nodeModel.properties, 'node_data', value)
|
||||
}
|
||||
})
|
||||
|
||||
const baseNodeFormRef = ref<FormInstance>()
|
||||
|
||||
const validate = () => {
|
||||
baseNodeFormRef.value?.validate()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
set(props.nodeModel, 'validate', validate)
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
import ConditioNodeVue from './index.vue'
|
||||
import { AppNode, AppNodeModel } from '@/workflow/common/app-node.ts'
|
||||
class ConditioNode extends AppNode {
|
||||
constructor(props: any) {
|
||||
super(props, ConditioNodeVue)
|
||||
}
|
||||
}
|
||||
export default {
|
||||
type: 'condition-node',
|
||||
model: AppNodeModel,
|
||||
view: ConditioNode
|
||||
}
|
||||
|
|
@ -0,0 +1,177 @@
|
|||
<template>
|
||||
<NodeContainer :nodeModel="nodeModel" class="start-node">
|
||||
<el-form
|
||||
:model="form_data"
|
||||
label-position="top"
|
||||
require-asterisk-position="right"
|
||||
label-width="auto"
|
||||
ref="ConditionNodeFormRef"
|
||||
@keydown.stop
|
||||
@submit.prevent
|
||||
@click.stop
|
||||
@mousedown.stop
|
||||
>
|
||||
<template v-for="(item, index) in form_data.branch" :key="index">
|
||||
<el-card shadow="never" class="card-never mb-8" style="--el-card-padding: 12px">
|
||||
<p class="lighter mb-8">{{ judgeLabel(index) }}</p>
|
||||
<template v-for="(condition, cIndex) in item.conditions" :key="cIndex">
|
||||
<el-row :gutter="8">
|
||||
<el-col :span="11">
|
||||
<el-form-item
|
||||
:prop="'branch.' + index + '.conditions' + cIndex + '.field'"
|
||||
:rules="{
|
||||
type: Array,
|
||||
required: true,
|
||||
message: '请选择变量',
|
||||
trigger: 'change'
|
||||
}"
|
||||
>
|
||||
<NodeCascader
|
||||
:nodeModel="nodeModel"
|
||||
class="w-full"
|
||||
placeholder="请选择变量"
|
||||
v-model="condition.field"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item
|
||||
:prop="'branch.' + index + '.conditions' + cIndex + '.compare'"
|
||||
:rules="{
|
||||
required: true,
|
||||
message: '请选择条件',
|
||||
trigger: 'change'
|
||||
}"
|
||||
>
|
||||
<el-select v-model="condition.compare" placeholder="请选择条件" clearable>
|
||||
<el-option label="Zone one" value="shanghai" />
|
||||
<el-option label="Zone two" value="beijing" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item
|
||||
:prop="'branch.' + index + '.conditions' + cIndex + '.value'"
|
||||
:rules="{
|
||||
required: true,
|
||||
message: '请输入值',
|
||||
trigger: 'blur'
|
||||
}"
|
||||
>
|
||||
<el-input v-model="condition.value" placeholder="请输入值" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="1">
|
||||
<el-button link type="info" class="mt-4" @click="deleteCondition(index, cIndex)">
|
||||
<el-icon><Delete /></el-icon>
|
||||
</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
<el-button link type="primary" @click="addCondition(index)">
|
||||
<el-icon class="mr-4"><Plus /></el-icon> 添加条件
|
||||
</el-button>
|
||||
</el-card>
|
||||
</template>
|
||||
<el-button link type="primary" @click="addBranch">
|
||||
<el-icon class="mr-4"><Plus /></el-icon> 添加分支
|
||||
</el-button>
|
||||
</el-form>
|
||||
</NodeContainer>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { cloneDeep, set } from 'lodash'
|
||||
import NodeContainer from '@/workflow/common/NodeContainer.vue'
|
||||
import NodeCascader from '@/workflow/common/NodeCascader.vue'
|
||||
import type { FormInstance } from 'element-plus'
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { randomId } from '@/utils/utils'
|
||||
const props = defineProps<{ nodeModel: any }>()
|
||||
const form = {
|
||||
branch: [
|
||||
{
|
||||
conditions: [
|
||||
{
|
||||
field: [],
|
||||
compare: '',
|
||||
value: ''
|
||||
}
|
||||
],
|
||||
id: randomId(),
|
||||
condition: 'and'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const form_data = computed({
|
||||
get: () => {
|
||||
if (props.nodeModel.properties.node_data) {
|
||||
return props.nodeModel.properties.node_data
|
||||
} else {
|
||||
set(props.nodeModel.properties, 'node_data', form)
|
||||
}
|
||||
return props.nodeModel.properties.node_data
|
||||
},
|
||||
set: (value) => {
|
||||
set(props.nodeModel.properties, 'node_data', value)
|
||||
}
|
||||
})
|
||||
|
||||
const ConditionNodeFormRef = ref<FormInstance>()
|
||||
|
||||
const validate = () => {
|
||||
ConditionNodeFormRef.value?.validate()
|
||||
}
|
||||
|
||||
const judgeLabel = (index: number) => {
|
||||
if (index === 0) {
|
||||
return 'IF'
|
||||
} else if (index === form_data.value.branch.length - 1) {
|
||||
return 'ELSE'
|
||||
} else {
|
||||
return 'ELSE IF ' + index
|
||||
}
|
||||
}
|
||||
|
||||
function addBranch() {
|
||||
const list = cloneDeep(props.nodeModel.properties.node_data.branch)
|
||||
const obj = {
|
||||
conditions: [
|
||||
{
|
||||
field: [],
|
||||
compare: '',
|
||||
value: ''
|
||||
}
|
||||
],
|
||||
id: randomId(),
|
||||
condition: 'and'
|
||||
}
|
||||
list.push(obj)
|
||||
props.nodeModel.addField({ key: obj.id })
|
||||
set(props.nodeModel.properties.node_data, 'branch', list)
|
||||
}
|
||||
|
||||
function addCondition(index: number) {
|
||||
const list = cloneDeep(props.nodeModel.properties.node_data.branch)
|
||||
list[index]['conditions'].push({
|
||||
field: { node_id: 'xxx', fields: '' },
|
||||
compare: '',
|
||||
value: ''
|
||||
})
|
||||
set(props.nodeModel.properties.node_data, 'branch', list)
|
||||
}
|
||||
|
||||
function deleteCondition(index: number, cIndex: number) {
|
||||
const list = cloneDeep(props.nodeModel.properties.node_data.branch)
|
||||
list[index]['conditions'].splice(cIndex, 1)
|
||||
if (list[index]['conditions'].length === 0) {
|
||||
list.splice(index, 1)
|
||||
}
|
||||
set(props.nodeModel.properties.node_data, 'branch', list)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
set(props.nodeModel, 'validate', validate)
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import SearchDatasetVue from './index.vue'
|
||||
import { AppNode, AppNodeModel } from '@/components/workflow/common/app-node/index'
|
||||
import { AppNode, AppNodeModel } from '@/workflow/common/app-node.ts'
|
||||
class SearchDatasetNode extends AppNode {
|
||||
constructor(props: any) {
|
||||
super(props, SearchDatasetVue)
|
||||
|
|
@ -0,0 +1,199 @@
|
|||
<template>
|
||||
<NodeContainer :nodeModel="nodeModel">
|
||||
<h5 class="title-decoration-1 mb-8">节点设置</h5>
|
||||
<el-card shadow="never" class="card-never">
|
||||
<el-form
|
||||
:model="form_data"
|
||||
label-position="top"
|
||||
require-asterisk-position="right"
|
||||
label-width="auto"
|
||||
ref="DatasetNodeFormRef"
|
||||
>
|
||||
<el-form-item label="选择知识库">
|
||||
<template #label>
|
||||
<div class="flex-between">
|
||||
<span>选择知识库</span>
|
||||
<el-button type="primary" link @click="openDatasetDialog">
|
||||
<el-icon><Plus /></el-icon>
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<div class="w-full">
|
||||
<el-text type="info" v-if="form_data.dataset_id_list?.length === 0">
|
||||
关联的知识库展示在这里
|
||||
</el-text>
|
||||
<template v-for="(item, index) in form_data.dataset_id_list" :key="index" v-else>
|
||||
<div class="flex-between border border-r-4 white-bg mb-4" style="padding: 5px 8px">
|
||||
<div class="flex align-center" style="line-height: 20px">
|
||||
<AppAvatar
|
||||
v-if="relatedObject(datasetList, item, 'id')?.type === '1'"
|
||||
class="mr-8 avatar-purple"
|
||||
shape="square"
|
||||
:size="20"
|
||||
>
|
||||
<img src="@/assets/icon_web.svg" style="width: 58%" alt="" />
|
||||
</AppAvatar>
|
||||
|
||||
<AppAvatar v-else class="mr-8" shape="square" :size="20">
|
||||
<img src="@/assets/icon_document.svg" style="width: 58%" alt="" />
|
||||
</AppAvatar>
|
||||
<div class="ellipsis">
|
||||
{{ relatedObject(datasetList, item, 'id')?.name }}
|
||||
</div>
|
||||
</div>
|
||||
<el-button text @click="removeDataset(item)">
|
||||
<el-icon><Close /></el-icon>
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="检索参数">
|
||||
<template #label>
|
||||
<div class="flex-between">
|
||||
<span>检索参数</span>
|
||||
<el-button type="primary" link @click="openParamSettingDialog">
|
||||
<el-icon><Setting /></el-icon>
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<div class="w-full">
|
||||
<el-row>
|
||||
<el-col :span="12" class="color-secondary lighter">检索模式</el-col>
|
||||
<el-col :span="12" class="lighter">
|
||||
{{ form_data.dataset_setting.search_mode }}</el-col
|
||||
>
|
||||
<el-col :span="12" class="color-secondary lighter"> 相似度高于</el-col>
|
||||
<el-col :span="12" class="lighter">
|
||||
{{ form_data.dataset_setting.similarity }}</el-col
|
||||
>
|
||||
<el-col :span="12" class="color-secondary lighter"> 引用分段 Top</el-col>
|
||||
<el-col :span="12" class="lighter"> {{ form_data.dataset_setting.top_n }}</el-col>
|
||||
<el-col :span="12" class="color-secondary lighter"> 最大引用字符数</el-col>
|
||||
<el-col :span="12" class="lighter">
|
||||
{{ form_data.dataset_setting.max_paragraph_char_number }}</el-col
|
||||
>
|
||||
</el-row>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="检索问题输入">
|
||||
<NodeCascader
|
||||
:nodeModel="nodeModel"
|
||||
class="w-full"
|
||||
placeholder="请选择检索问题输入"
|
||||
v-model="form_data.fields"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
<h5 class="title-decoration-1 mb-8 mt-8">参数输出</h5>
|
||||
<div class="border-r-4 p-8-12 mb-8 layout-bg lighter">检索结果 {data}</div>
|
||||
<div class="border-r-4 p-8-12 mb-8 layout-bg lighter">满足直接回答的分段内容 {paragraph}</div>
|
||||
<ParamSettingDialog ref="ParamSettingDialogRef" @refresh="refreshParam" />
|
||||
<AddDatasetDialog
|
||||
ref="AddDatasetDialogRef"
|
||||
@addData="addDataset"
|
||||
:data="datasetList"
|
||||
@refresh="refresh"
|
||||
:loading="datasetLoading"
|
||||
/>
|
||||
</NodeContainer>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { set } from 'lodash'
|
||||
import { app } from '@/main'
|
||||
import NodeContainer from '@/workflow/common/NodeContainer.vue'
|
||||
import NodeCascader from '@/workflow/common/NodeCascader.vue'
|
||||
import AddDatasetDialog from '@/views/application/components/AddDatasetDialog.vue'
|
||||
import ParamSettingDialog from '@/views/application/components/ParamSettingDialog.vue'
|
||||
|
||||
import type { FormInstance } from 'element-plus'
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { relatedObject } from '@/utils/utils'
|
||||
import useStore from '@/stores'
|
||||
const { dataset, application, user } = useStore()
|
||||
const {
|
||||
params: { id }
|
||||
} = app.config.globalProperties.$route as any
|
||||
|
||||
const props = defineProps<{ nodeModel: any }>()
|
||||
|
||||
const form = {
|
||||
dataset_id_list: [],
|
||||
dataset_setting: {
|
||||
top_n: 3,
|
||||
similarity: 0.6,
|
||||
max_paragraph_char_number: 5000,
|
||||
search_mode: 'embedding'
|
||||
},
|
||||
fields: []
|
||||
}
|
||||
|
||||
const form_data = computed({
|
||||
get: () => {
|
||||
if (props.nodeModel.properties.node_data) {
|
||||
return props.nodeModel.properties.node_data
|
||||
} else {
|
||||
set(props.nodeModel.properties, 'node_data', form.value)
|
||||
}
|
||||
return props.nodeModel.properties.node_data
|
||||
},
|
||||
set: (value) => {
|
||||
set(props.nodeModel.properties, 'node_data', value)
|
||||
}
|
||||
})
|
||||
|
||||
const DatasetNodeFormRef = ref<FormInstance>()
|
||||
const ParamSettingDialogRef = ref<InstanceType<typeof ParamSettingDialog>>()
|
||||
const AddDatasetDialogRef = ref<InstanceType<typeof AddDatasetDialog>>()
|
||||
const datasetList = ref<any>([])
|
||||
const datasetLoading = ref(false)
|
||||
|
||||
function refreshParam(data: any) {
|
||||
set(props.nodeModel.properties.node_data, 'dataset_setting', data)
|
||||
}
|
||||
|
||||
const openParamSettingDialog = () => {
|
||||
ParamSettingDialogRef.value?.open(form_data.value.dataset_setting, 'workflow')
|
||||
}
|
||||
|
||||
function removeDataset(id: any) {
|
||||
const list = props.nodeModel.properties.node_data.dataset_id_list.filter((v) => v !== id)
|
||||
set(props.nodeModel.properties.node_data, 'dataset_id_list', list)
|
||||
}
|
||||
|
||||
function addDataset(val: Array<string>) {
|
||||
set(props.nodeModel.properties.node_data, 'dataset_id_list', val)
|
||||
}
|
||||
|
||||
function openDatasetDialog() {
|
||||
if (AddDatasetDialogRef.value) {
|
||||
AddDatasetDialogRef.value.open(form_data.value.dataset_id_list)
|
||||
}
|
||||
}
|
||||
|
||||
function getDataset() {
|
||||
if (id) {
|
||||
application.asyncGetApplicationDataset(id, datasetLoading).then((res: any) => {
|
||||
datasetList.value = res.data
|
||||
})
|
||||
} else {
|
||||
dataset.asyncGetAllDataset(datasetLoading).then((res: any) => {
|
||||
datasetList.value = res.data?.filter((v: any) => v.user_id === user.userInfo?.id)
|
||||
})
|
||||
}
|
||||
}
|
||||
function refresh() {
|
||||
getDataset()
|
||||
}
|
||||
|
||||
const validate = () => {
|
||||
DatasetNodeFormRef.value?.validate()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getDataset()
|
||||
set(props.nodeModel, 'validate', validate)
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
import ChatNodeVue from './index.vue'
|
||||
import { AppNode, AppNodeModel } from '@/workflow/common/app-node.ts'
|
||||
class ChatNode extends AppNode {
|
||||
constructor(props: any) {
|
||||
super(props, ChatNodeVue)
|
||||
}
|
||||
}
|
||||
export default {
|
||||
type: 'start-node',
|
||||
model: AppNodeModel,
|
||||
view: ChatNode
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
<template>
|
||||
<NodeContainer :nodeModel="nodeModel" class="start-node">
|
||||
<h5 class="title-decoration-1 mb-8">全局变量</h5>
|
||||
<div class="border-r-4 p-8-12 mb-8 layout-bg lighter">当前时间 {time}</div>
|
||||
<h5 class="title-decoration-1 mb-8">参数输出</h5>
|
||||
<div class="border-r-4 p-8-12 mb-8 layout-bg lighter">用户问题 {question}</div>
|
||||
</NodeContainer>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { set } from 'lodash'
|
||||
import NodeContainer from '@/workflow/common/NodeContainer.vue'
|
||||
import type { FormInstance } from 'element-plus'
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
|
||||
const props = defineProps<{ nodeModel: any }>()
|
||||
|
||||
// onMounted(() => {
|
||||
// set(props.nodeModel, 'validate', validate)
|
||||
// })
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
Loading…
Reference in New Issue