mirror of
https://github.com/1Panel-dev/MaxKB.git
synced 2025-12-26 18:22:46 +00:00
Compare commits
43 Commits
main
...
v1.10.9-lt
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5f50059443 | ||
|
|
d5148ddadf | ||
|
|
b838a14bd8 | ||
|
|
5ba725ba18 | ||
|
|
d1cd01f555 | ||
|
|
6f4df54917 | ||
|
|
9d790f1eda | ||
|
|
dd5622d2bb | ||
|
|
7eaf860869 | ||
|
|
bd0f44efd1 | ||
|
|
23fcb0e94e | ||
|
|
3083d48dff | ||
|
|
ef549c7c89 | ||
|
|
bc6a5a8869 | ||
|
|
14d011d61d | ||
|
|
753bf5f777 | ||
|
|
8e3e46a96d | ||
|
|
33762f26bf | ||
|
|
2adb872cdf | ||
|
|
a0b6aaa568 | ||
|
|
01075166b8 | ||
|
|
ae30052dae | ||
|
|
55a7d73f98 | ||
|
|
0531a6ecc8 | ||
|
|
1ee0eac455 | ||
|
|
0ba9b97752 | ||
|
|
02d6239a71 | ||
|
|
8d3b3f8121 | ||
|
|
4013606a93 | ||
|
|
40be71d765 | ||
|
|
8ecf5b52ed | ||
|
|
abe51dc30c | ||
|
|
b7ba9fdf67 | ||
|
|
bca56af788 | ||
|
|
622a8e525c | ||
|
|
f568c6800f | ||
|
|
93d1958fef | ||
|
|
90ee3c4d21 | ||
|
|
30ddab322f | ||
|
|
ee83139b96 | ||
|
|
1268b2043a | ||
|
|
f01a65f507 | ||
|
|
efa196c58b |
|
|
@ -24,7 +24,7 @@ MaxKB = Max Knowledge Brain, it is an open-source platform for building enterpri
|
|||
Execute the script below to start a MaxKB container using Docker:
|
||||
|
||||
```bash
|
||||
docker run -d --name=maxkb --restart=always -p 8080:8080 -v ~/.maxkb:/var/lib/postgresql/data -v ~/.python-packages:/opt/maxkb/app/sandbox/python-packages 1panel/maxkb
|
||||
docker run -d --name=maxkb --restart=always -p 8080:8080 -v ~/.maxkb:/var/lib/postgresql/data -v ~/.python-packages:/opt/maxkb/app/sandbox/python-packages 1panel/maxkb:v1
|
||||
```
|
||||
|
||||
Access MaxKB web interface at `http://your_server_ip:8080` with default admin credentials:
|
||||
|
|
@ -32,7 +32,7 @@ Access MaxKB web interface at `http://your_server_ip:8080` with default admin cr
|
|||
- username: admin
|
||||
- password: MaxKB@123..
|
||||
|
||||
中国用户如遇到 Docker 镜像 Pull 失败问题,请参照该 [离线安装文档](https://maxkb.cn/docs/installation/offline_installtion/) 进行安装。
|
||||
中国用户如遇到 Docker 镜像 Pull 失败问题,请参照该 [离线安装文档](https://maxkb.cn/docs/v1/installation/offline_installtion/) 进行安装。
|
||||
|
||||
## Screenshots
|
||||
|
||||
|
|
|
|||
12
README_CN.md
12
README_CN.md
|
|
@ -14,12 +14,12 @@
|
|||
</p>
|
||||
<hr/>
|
||||
|
||||
MaxKB = Max Knowledge Brain,是一款强大易用的企业级智能体平台,支持 RAG 检索增强生成、工作流编排、MCP 工具调用能力。MaxKB 支持对接各种主流大语言模型,广泛应用于智能客服、企业内部知识库问答、员工助手、学术研究与教育等场景。
|
||||
MaxKB = Max Knowledge Brain,是一个强大易用的企业级智能体平台,致力于解决企业 AI 落地面临的技术门槛高、部署成本高、迭代周期长等问题,助力企业在人工智能时代赢得先机。秉承“开箱即用,伴随成长”的设计理念,MaxKB 支持企业快速接入主流大模型,高效构建专属知识库,并提供从基础问答(RAG)、复杂流程自动化(工作流)到智能体(Agent)的渐进式升级路径,全面赋能智能客服、智能办公助手等多种应用场景。
|
||||
|
||||
- **RAG 检索增强生成**:高效搭建本地 AI 知识库,支持直接上传文档 / 自动爬取在线文档,支持文本自动拆分、向量化,有效减少大模型幻觉,提升问答效果;
|
||||
- **灵活编排**:内置强大的工作流引擎、函数库和 MCP 工具调用能力,支持编排 AI 工作过程,满足复杂业务场景下的需求;
|
||||
- **无缝嵌入**:支持零编码快速嵌入到第三方业务系统,让已有系统快速拥有智能问答能力,提高用户满意度;
|
||||
- **模型中立**:支持对接各种大模型,包括本地私有大模型(DeepSeek R1 / Llama 3 / Qwen 2 等)、国内公共大模型(通义千问 / 腾讯混元 / 字节豆包 / 百度千帆 / 智谱 AI / Kimi 等)和国外公共大模型(OpenAI / Claude / Gemini 等)。
|
||||
- **模型中立**:支持对接各种大模型,包括本地私有大模型(DeepSeek R1 / Qwen 3 等)、国内公共大模型(通义千问 / 腾讯混元 / 字节豆包 / 百度千帆 / 智谱 AI / Kimi 等)和国外公共大模型(OpenAI / Claude / Gemini 等)。
|
||||
|
||||
MaxKB 三分钟视频介绍:https://www.bilibili.com/video/BV18JypYeEkj/
|
||||
|
||||
|
|
@ -27,10 +27,10 @@ MaxKB 三分钟视频介绍:https://www.bilibili.com/video/BV18JypYeEkj/
|
|||
|
||||
```
|
||||
# Linux 机器
|
||||
docker run -d --name=maxkb --restart=always -p 8080:8080 -v ~/.maxkb:/var/lib/postgresql/data -v ~/.python-packages:/opt/maxkb/app/sandbox/python-packages registry.fit2cloud.com/maxkb/maxkb
|
||||
docker run -d --name=maxkb --restart=always -p 8080:8080 -v ~/.maxkb:/var/lib/postgresql/data -v ~/.python-packages:/opt/maxkb/app/sandbox/python-packages registry.fit2cloud.com/maxkb/maxkb:v1
|
||||
|
||||
# Windows 机器
|
||||
docker run -d --name=maxkb --restart=always -p 8080:8080 -v C:/maxkb:/var/lib/postgresql/data -v C:/python-packages:/opt/maxkb/app/sandbox/python-packages registry.fit2cloud.com/maxkb/maxkb
|
||||
docker run -d --name=maxkb --restart=always -p 8080:8080 -v C:/maxkb:/var/lib/postgresql/data -v C:/python-packages:/opt/maxkb/app/sandbox/python-packages registry.fit2cloud.com/maxkb/maxkb:v1
|
||||
|
||||
# 用户名: admin
|
||||
# 密码: MaxKB@123..
|
||||
|
|
@ -38,8 +38,8 @@ docker run -d --name=maxkb --restart=always -p 8080:8080 -v C:/maxkb:/var/lib/po
|
|||
|
||||
- 你也可以通过 [1Panel 应用商店](https://apps.fit2cloud.com/1panel) 快速部署 MaxKB;
|
||||
- 如果是内网环境,推荐使用 [离线安装包](https://community.fit2cloud.com/#/products/maxkb/downloads) 进行安装部署;
|
||||
- MaxKB 产品版本分为社区版和专业版,详情请参见:[MaxKB 产品版本对比](https://maxkb.cn/pricing.html);
|
||||
- 如果您需要向团队介绍 MaxKB,可以使用这个 [官方 PPT 材料](https://maxkb.cn/download/introduce-maxkb_202503.pdf)。
|
||||
- MaxKB 不同产品产品版本的对比请参见:[MaxKB 产品版本对比](https://maxkb.cn/price);
|
||||
- 如果您需要向团队介绍 MaxKB,可以使用这个 [官方 PPT 材料](https://fit2cloud.com/maxkb/download/introduce-maxkb_202507.pdf)。
|
||||
|
||||
如你有更多问题,可以查看使用手册,或者通过论坛与我们交流。
|
||||
|
||||
|
|
|
|||
|
|
@ -188,6 +188,7 @@ class BaseChatNode(IChatNode):
|
|||
self.context['answer'] = details.get('answer')
|
||||
self.context['question'] = details.get('question')
|
||||
self.context['reasoning_content'] = details.get('reasoning_content')
|
||||
self.context['model_setting'] = details.get('model_setting')
|
||||
if self.node_params.get('is_result', False):
|
||||
self.answer_text = details.get('answer')
|
||||
|
||||
|
|
@ -274,6 +275,7 @@ class BaseChatNode(IChatNode):
|
|||
"index": index,
|
||||
'run_time': self.context.get('run_time'),
|
||||
'system': self.context.get('system'),
|
||||
'model_setting': self.context.get('model_setting'),
|
||||
'history_message': [{'content': message.content, 'role': message.type} for message in
|
||||
(self.context.get('history_message') if self.context.get(
|
||||
'history_message') is not None else [])],
|
||||
|
|
|
|||
|
|
@ -45,6 +45,8 @@ def get_field_value(debug_field_list, name, is_required):
|
|||
|
||||
|
||||
def valid_reference_value(_type, value, name):
|
||||
if value is None:
|
||||
return
|
||||
if _type == 'int':
|
||||
instance_type = int | float
|
||||
elif _type == 'float':
|
||||
|
|
@ -70,10 +72,17 @@ def convert_value(name: str, value, _type, is_required, source, node):
|
|||
if not is_required and source == 'reference' and (value is None or len(value) == 0):
|
||||
return None
|
||||
if source == 'reference':
|
||||
if value and isinstance(value, list) and len(value) == 0:
|
||||
if not is_required:
|
||||
return None
|
||||
else:
|
||||
raise Exception(f"字段:{name}类型:{_type}值:{value}必填参数")
|
||||
value = node.workflow_manage.get_reference_field(
|
||||
value[0],
|
||||
value[1:])
|
||||
valid_reference_value(_type, value, name)
|
||||
if value is None:
|
||||
return None
|
||||
if _type == 'int':
|
||||
return int(value)
|
||||
if _type == 'float':
|
||||
|
|
|
|||
|
|
@ -32,6 +32,8 @@ def write_context(step_variable: Dict, global_variable: Dict, node, workflow):
|
|||
|
||||
|
||||
def valid_reference_value(_type, value, name):
|
||||
if value is None:
|
||||
return
|
||||
if _type == 'int':
|
||||
instance_type = int | float
|
||||
elif _type == 'float':
|
||||
|
|
@ -52,10 +54,17 @@ def convert_value(name: str, value, _type, is_required, source, node):
|
|||
if not is_required and (value is None or (isinstance(value, str) and len(value) == 0)):
|
||||
return None
|
||||
if source == 'reference':
|
||||
if value and isinstance(value, list) and len(value) == 0:
|
||||
if not is_required:
|
||||
return None
|
||||
else:
|
||||
raise Exception(f"字段:{name}类型:{_type}值:{value}必填参数")
|
||||
value = node.workflow_manage.get_reference_field(
|
||||
value[0],
|
||||
value[1:])
|
||||
valid_reference_value(_type, value, name)
|
||||
if value is None:
|
||||
return None
|
||||
if _type == 'int':
|
||||
return int(value)
|
||||
if _type == 'float':
|
||||
|
|
|
|||
|
|
@ -298,8 +298,8 @@ class WorkflowManage:
|
|||
if global_fields is not None:
|
||||
for global_field in global_fields:
|
||||
global_field_list.append({**global_field, 'node_id': node_id, 'node_name': node_name})
|
||||
field_list.sort(key=lambda f: len(f.get('node_name')), reverse=True)
|
||||
global_field_list.sort(key=lambda f: len(f.get('node_name')), reverse=True)
|
||||
field_list.sort(key=lambda f: len(f.get('node_name') + f.get('value')), reverse=True)
|
||||
global_field_list.sort(key=lambda f: len(f.get('node_name') + f.get('value')), reverse=True)
|
||||
self.field_list = field_list
|
||||
self.global_field_list = global_field_list
|
||||
|
||||
|
|
@ -755,7 +755,10 @@ class WorkflowManage:
|
|||
if node_id == 'global':
|
||||
return INode.get_field(self.context, fields)
|
||||
else:
|
||||
return self.get_node_by_id(node_id).get_reference_field(fields)
|
||||
node = self.get_node_by_id(node_id)
|
||||
if node:
|
||||
return node.get_reference_field(fields)
|
||||
return None
|
||||
|
||||
def get_workflow_content(self):
|
||||
context = {
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import re
|
|||
import uuid
|
||||
from functools import reduce
|
||||
from typing import Dict, List
|
||||
|
||||
from django.contrib.postgres.fields import ArrayField
|
||||
from django.core import cache, validators
|
||||
from django.core import signing
|
||||
|
|
@ -24,8 +25,8 @@ from django.db.models import QuerySet
|
|||
from django.db.models.expressions import RawSQL
|
||||
from django.http import HttpResponse
|
||||
from django.template import Template, Context
|
||||
from django.utils.translation import gettext_lazy as _, get_language, to_locale
|
||||
from langchain_mcp_adapters.client import MultiServerMCPClient
|
||||
from mcp.client.sse import sse_client
|
||||
from rest_framework import serializers, status
|
||||
from rest_framework.utils.formatting import lazy_format
|
||||
|
||||
|
|
@ -38,7 +39,7 @@ from common.config.embedding_config import VectorStore
|
|||
from common.constants.authentication_type import AuthenticationType
|
||||
from common.db.search import get_dynamics_model, native_search, native_page_search
|
||||
from common.db.sql_execute import select_list
|
||||
from common.exception.app_exception import AppApiException, NotFound404, AppUnauthorizedFailed, ChatException
|
||||
from common.exception.app_exception import AppApiException, NotFound404, AppUnauthorizedFailed
|
||||
from common.field.common import UploadedImageField, UploadedFileField
|
||||
from common.models.db_model_manage import DBModelManage
|
||||
from common.response import result
|
||||
|
|
@ -57,7 +58,6 @@ from setting.models_provider.tools import get_model_instance_by_model_user_id
|
|||
from setting.serializers.provider_serializers import ModelSerializer
|
||||
from smartdoc.conf import PROJECT_DIR
|
||||
from users.models import User
|
||||
from django.utils.translation import gettext_lazy as _, get_language, to_locale
|
||||
|
||||
chat_cache = cache.caches['chat_cache']
|
||||
|
||||
|
|
@ -1328,6 +1328,9 @@ class ApplicationSerializer(serializers.Serializer):
|
|||
if '"stdio"' in self.data.get('mcp_servers'):
|
||||
raise AppApiException(500, _('stdio is not supported'))
|
||||
servers = json.loads(self.data.get('mcp_servers'))
|
||||
for server, config in servers.items():
|
||||
if config.get('transport') not in ['sse', 'streamable_http']:
|
||||
raise AppApiException(500, _('Only support transport=sse or transport=streamable_http'))
|
||||
|
||||
async def get_mcp_tools(servers):
|
||||
async with MultiServerMCPClient(servers) as client:
|
||||
|
|
|
|||
|
|
@ -395,13 +395,14 @@ class ChatMessageSerializer(serializers.Serializer):
|
|||
work_flow_manage = WorkflowManage(Flow.new_instance(chat_info.work_flow_version.work_flow),
|
||||
{'history_chat_record': history_chat_record, 'question': message,
|
||||
'chat_id': chat_info.chat_id, 'chat_record_id': str(
|
||||
uuid.uuid1()) if chat_record is None else chat_record.id,
|
||||
uuid.uuid1()) if chat_record is None else str(chat_record.id),
|
||||
'stream': stream,
|
||||
're_chat': re_chat,
|
||||
'client_id': client_id,
|
||||
'client_type': client_type,
|
||||
'user_id': user_id}, WorkFlowPostHandler(chat_info, client_id, client_type),
|
||||
base_to_response, form_data, image_list, document_list, audio_list, other_list,
|
||||
base_to_response, form_data, image_list, document_list, audio_list,
|
||||
other_list,
|
||||
self.data.get('runtime_node_id'),
|
||||
self.data.get('node_data'), chat_record, self.data.get('child_node'))
|
||||
r = work_flow_manage.run()
|
||||
|
|
|
|||
|
|
@ -222,7 +222,8 @@ class ChatSerializers(serializers.Serializer):
|
|||
reference_paragraph,
|
||||
"\n".join([
|
||||
f"{improve_paragraph_list[index].get('title')}\n{improve_paragraph_list[index].get('content')}"
|
||||
for index in range(len(improve_paragraph_list))]),
|
||||
for index in range(len(improve_paragraph_list))
|
||||
]) if improve_paragraph_list is not None else "",
|
||||
row.get('asker').get('user_name'),
|
||||
row.get('message_tokens') + row.get('answer_tokens'), row.get('run_time'),
|
||||
str(row.get('create_time').astimezone(pytz.timezone(TIME_ZONE)).strftime('%Y-%m-%d %H:%M:%S')
|
||||
|
|
|
|||
|
|
@ -302,7 +302,19 @@ class ApplicationApi(ApiMixin):
|
|||
'no_references_prompt': openapi.Schema(type=openapi.TYPE_STRING,
|
||||
title=_("No citation segmentation prompt"),
|
||||
default="{question}",
|
||||
description=_("No citation segmentation prompt"))
|
||||
description=_("No citation segmentation prompt")),
|
||||
'reasoning_content_enable': openapi.Schema(type=openapi.TYPE_BOOLEAN,
|
||||
title=_("Reasoning enable"),
|
||||
default=False,
|
||||
description=_("Reasoning enable")),
|
||||
'reasoning_content_end': openapi.Schema(type=openapi.TYPE_STRING,
|
||||
title=_("Reasoning end tag"),
|
||||
default="</think>",
|
||||
description=_("Reasoning end tag")),
|
||||
"reasoning_content_start": openapi.Schema(type=openapi.TYPE_STRING,
|
||||
title=_("Reasoning start tag"),
|
||||
default="<think>",
|
||||
description=_("Reasoning start tag"))
|
||||
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -326,11 +326,6 @@ class ChatApi(ApiMixin):
|
|||
type=openapi.TYPE_STRING,
|
||||
required=True,
|
||||
description=_('Application ID')),
|
||||
openapi.Parameter(name='history_day',
|
||||
in_=openapi.IN_QUERY,
|
||||
type=openapi.TYPE_NUMBER,
|
||||
required=True,
|
||||
description=_('Historical days')),
|
||||
openapi.Parameter(name='abstract', in_=openapi.IN_QUERY, type=openapi.TYPE_STRING, required=False,
|
||||
description=_("abstract")),
|
||||
openapi.Parameter(name='min_star', in_=openapi.IN_QUERY, type=openapi.TYPE_INTEGER, required=False,
|
||||
|
|
|
|||
|
|
@ -7,16 +7,6 @@
|
|||
@desc:
|
||||
"""
|
||||
|
||||
from django.core import cache
|
||||
from django.http import HttpResponse
|
||||
from django.utils.translation import gettext_lazy as _, gettext
|
||||
from drf_yasg.utils import swagger_auto_schema
|
||||
from langchain_core.prompts import PromptTemplate
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.parsers import MultiPartParser
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from application.serializers.application_serializers import ApplicationSerializer
|
||||
from application.serializers.application_statistics_serializers import ApplicationStatisticsSerializer
|
||||
from application.swagger_api.application_api import ApplicationApi
|
||||
|
|
@ -31,6 +21,14 @@ from common.response import result
|
|||
from common.swagger_api.common_api import CommonApi
|
||||
from common.util.common import query_params_to_single_dict
|
||||
from dataset.serializers.dataset_serializers import DataSetSerializers
|
||||
from django.core import cache
|
||||
from django.http import HttpResponse
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from drf_yasg.utils import swagger_auto_schema
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.parsers import MultiPartParser
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.views import APIView
|
||||
|
||||
chat_cache = cache.caches['chat_cache']
|
||||
|
||||
|
|
@ -494,7 +492,7 @@ class Application(APIView):
|
|||
class HitTest(APIView):
|
||||
authentication_classes = [TokenAuth]
|
||||
|
||||
@action(methods="GET", detail=False)
|
||||
@action(methods="PUT", detail=False)
|
||||
@swagger_auto_schema(operation_summary=_("Hit Test List"), operation_id=_("Hit Test List"),
|
||||
manual_parameters=CommonApi.HitTestApi.get_request_params_api(),
|
||||
responses=result.get_api_array_response(CommonApi.HitTestApi.get_response_body_api()),
|
||||
|
|
@ -505,15 +503,15 @@ class Application(APIView):
|
|||
[lambda r, keywords: Permission(group=Group.APPLICATION, operate=Operate.USE,
|
||||
dynamic_tag=keywords.get('application_id'))],
|
||||
compare=CompareConstants.AND))
|
||||
def get(self, request: Request, application_id: str):
|
||||
return result.success(
|
||||
ApplicationSerializer.HitTest(data={'id': application_id, 'user_id': request.user.id,
|
||||
"query_text": request.query_params.get("query_text"),
|
||||
"top_number": request.query_params.get("top_number"),
|
||||
'similarity': request.query_params.get('similarity'),
|
||||
'search_mode': request.query_params.get(
|
||||
'search_mode')}).hit_test(
|
||||
))
|
||||
def put(self, request: Request, application_id: str):
|
||||
return result.success(ApplicationSerializer.HitTest(data={
|
||||
'id': application_id,
|
||||
'user_id': request.user.id,
|
||||
"query_text": request.data.get("query_text"),
|
||||
"top_number": request.data.get("top_number"),
|
||||
'similarity': request.data.get('similarity'),
|
||||
'search_mode': request.data.get('search_mode')}
|
||||
).hit_test())
|
||||
|
||||
class Publish(APIView):
|
||||
authentication_classes = [TokenAuth]
|
||||
|
|
|
|||
|
|
@ -6,18 +6,18 @@
|
|||
@date:2024/3/14 03:02
|
||||
@desc: 用户认证
|
||||
"""
|
||||
from django.core import cache
|
||||
from django.db.models import QuerySet
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from common.auth.handle.auth_base_handle import AuthBaseHandle
|
||||
from common.constants.authentication_type import AuthenticationType
|
||||
from common.constants.permission_constants import RoleConstants, get_permission_list_by_role, Auth
|
||||
from common.exception.app_exception import AppAuthenticationFailed
|
||||
from smartdoc.settings import JWT_AUTH
|
||||
from smartdoc.const import CONFIG
|
||||
from users.models import User
|
||||
from django.core import cache
|
||||
|
||||
from users.models.user import get_user_dynamics_permission
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
token_cache = cache.caches['token_cache']
|
||||
|
||||
|
||||
|
|
@ -35,7 +35,7 @@ class UserToken(AuthBaseHandle):
|
|||
auth_details = get_token_details()
|
||||
user = QuerySet(User).get(id=auth_details['id'])
|
||||
# 续期
|
||||
token_cache.touch(token, timeout=JWT_AUTH['JWT_EXPIRATION_DELTA'].total_seconds())
|
||||
token_cache.touch(token, timeout=CONFIG.get_session_timeout())
|
||||
rule = RoleConstants[user.role]
|
||||
permission_list = get_permission_list_by_role(RoleConstants[user.role])
|
||||
# 获取用户的应用和知识库的权限
|
||||
|
|
|
|||
|
|
@ -526,7 +526,7 @@ class DataSetSerializers(serializers.ModelSerializer):
|
|||
def get_request_body_api():
|
||||
return openapi.Schema(
|
||||
type=openapi.TYPE_OBJECT,
|
||||
required=['name', 'desc'],
|
||||
required=['name', 'desc', 'embedding_mode_id'],
|
||||
properties={
|
||||
'name': openapi.Schema(type=openapi.TYPE_STRING, title=_('dataset name'),
|
||||
description=_('dataset name')),
|
||||
|
|
|
|||
|
|
@ -141,7 +141,8 @@ class DocumentEditInstanceSerializer(ApiMixin, serializers.Serializer):
|
|||
if 'meta' in self.data and self.data.get('meta') is not None:
|
||||
dataset_meta_valid_map = self.get_meta_valid_map()
|
||||
valid_class = dataset_meta_valid_map.get(document.type)
|
||||
valid_class(data=self.data.get('meta')).is_valid(raise_exception=True)
|
||||
if valid_class is not None:
|
||||
valid_class(data=self.data.get('meta')).is_valid(raise_exception=True)
|
||||
|
||||
|
||||
class DocumentWebInstanceSerializer(ApiMixin, serializers.Serializer):
|
||||
|
|
@ -808,27 +809,40 @@ class DocumentSerializers(ApiMixin, serializers.Serializer):
|
|||
def get_response_body_api():
|
||||
return openapi.Schema(
|
||||
type=openapi.TYPE_OBJECT,
|
||||
required=['id', 'name', 'char_length', 'user_id', 'paragraph_count', 'is_active'
|
||||
'update_time', 'create_time'],
|
||||
required=['create_time', 'update_time', 'id', 'name', 'char_length', 'status', 'is_active',
|
||||
'type', 'meta', 'dataset_id', 'hit_handling_method', 'directly_return_similarity',
|
||||
'status_meta', 'paragraph_count'],
|
||||
properties={
|
||||
'create_time': openapi.Schema(type=openapi.TYPE_STRING, title=_('create time'),
|
||||
description=_('create time'),
|
||||
default="1970-01-01 00:00:00"),
|
||||
'update_time': openapi.Schema(type=openapi.TYPE_STRING, title=_('update time'),
|
||||
description=_('update time'),
|
||||
default="1970-01-01 00:00:00"),
|
||||
'id': openapi.Schema(type=openapi.TYPE_STRING, title="id",
|
||||
description="id", default="xx"),
|
||||
'name': openapi.Schema(type=openapi.TYPE_STRING, title=_('name'),
|
||||
description=_('name'), default="xx"),
|
||||
'char_length': openapi.Schema(type=openapi.TYPE_INTEGER, title=_('char length'),
|
||||
description=_('char length'), default=10),
|
||||
'user_id': openapi.Schema(type=openapi.TYPE_STRING, title=_('user id'), description=_('user id')),
|
||||
'paragraph_count': openapi.Schema(type=openapi.TYPE_INTEGER, title="_('document count')",
|
||||
description="_('document count')", default=1),
|
||||
'status':openapi.Schema(type=openapi.TYPE_STRING, title=_('status'),
|
||||
description=_('status'), default="xx"),
|
||||
'is_active': openapi.Schema(type=openapi.TYPE_BOOLEAN, title=_('Is active'),
|
||||
description=_('Is active'), default=True),
|
||||
'update_time': openapi.Schema(type=openapi.TYPE_STRING, title=_('update time'),
|
||||
description=_('update time'),
|
||||
default="1970-01-01 00:00:00"),
|
||||
'create_time': openapi.Schema(type=openapi.TYPE_STRING, title=_('create time'),
|
||||
description=_('create time'),
|
||||
default="1970-01-01 00:00:00"
|
||||
)
|
||||
'type': openapi.Schema(type=openapi.TYPE_STRING, title=_('type'),
|
||||
description=_('type'), default="xx"),
|
||||
'meta': openapi.Schema(type=openapi.TYPE_OBJECT, title=_('meta'),
|
||||
description=_('meta'), default="{}"),
|
||||
'dataset_id': openapi.Schema(type=openapi.TYPE_STRING, title=_('dataset_id'),
|
||||
description=_('dataset_id'), default="xx"),
|
||||
'hit_handling_method': openapi.Schema(type=openapi.TYPE_STRING, title=_('hit_handling_method'),
|
||||
description=_('hit_handling_method'), default="xx"),
|
||||
'directly_return_similarity': openapi.Schema(type=openapi.TYPE_NUMBER, title=_('directly_return_similarity'),
|
||||
description=_('directly_return_similarity'), default="xx"),
|
||||
'status_meta': openapi.Schema(type=openapi.TYPE_OBJECT, title=_('status_meta'),
|
||||
description=_('status_meta'), default="{}"),
|
||||
'paragraph_count': openapi.Schema(type=openapi.TYPE_INTEGER, title="_('document count')",
|
||||
description="_('document count')", default=1),
|
||||
}
|
||||
)
|
||||
|
||||
|
|
@ -855,7 +869,7 @@ class DocumentSerializers(ApiMixin, serializers.Serializer):
|
|||
|
||||
class Create(ApiMixin, serializers.Serializer):
|
||||
dataset_id = serializers.UUIDField(required=True, error_messages=ErrMessage.char(
|
||||
_('document id')))
|
||||
_('dataset id')))
|
||||
|
||||
def is_valid(self, *, raise_exception=False):
|
||||
super().is_valid(raise_exception=True)
|
||||
|
|
@ -983,7 +997,7 @@ class DocumentSerializers(ApiMixin, serializers.Serializer):
|
|||
in_=openapi.IN_PATH,
|
||||
type=openapi.TYPE_STRING,
|
||||
required=True,
|
||||
description=_('document id'))
|
||||
description=_('dataset id'))
|
||||
]
|
||||
|
||||
class Split(ApiMixin, serializers.Serializer):
|
||||
|
|
|
|||
|
|
@ -7,13 +7,13 @@
|
|||
@desc:
|
||||
"""
|
||||
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from drf_yasg.utils import swagger_auto_schema
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.parsers import MultiPartParser
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.views import Request
|
||||
|
||||
import dataset.models
|
||||
from common.auth import TokenAuth, has_permissions
|
||||
from common.constants.permission_constants import PermissionConstants, CompareConstants, Permission, Group, Operate, \
|
||||
ViewPermission, RoleConstants
|
||||
|
|
@ -25,7 +25,6 @@ from dataset.serializers.common_serializers import GenerateRelatedSerializer
|
|||
from dataset.serializers.dataset_serializers import DataSetSerializers
|
||||
from dataset.views.common import get_dataset_operation_object
|
||||
from setting.serializers.provider_serializers import ModelSerializer
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
class Dataset(APIView):
|
||||
|
|
@ -141,21 +140,22 @@ class Dataset(APIView):
|
|||
class HitTest(APIView):
|
||||
authentication_classes = [TokenAuth]
|
||||
|
||||
@action(methods="GET", detail=False)
|
||||
@action(methods="PUT", detail=False)
|
||||
@swagger_auto_schema(operation_summary=_('Hit test list'), operation_id=_('Hit test list'),
|
||||
manual_parameters=CommonApi.HitTestApi.get_request_params_api(),
|
||||
responses=result.get_api_array_response(CommonApi.HitTestApi.get_response_body_api()),
|
||||
tags=[_('Knowledge Base')])
|
||||
@has_permissions(lambda r, keywords: Permission(group=Group.DATASET, operate=Operate.USE,
|
||||
dynamic_tag=keywords.get('dataset_id')))
|
||||
def get(self, request: Request, dataset_id: str):
|
||||
return result.success(
|
||||
DataSetSerializers.HitTest(data={'id': dataset_id, 'user_id': request.user.id,
|
||||
"query_text": request.query_params.get("query_text"),
|
||||
"top_number": request.query_params.get("top_number"),
|
||||
'similarity': request.query_params.get('similarity'),
|
||||
'search_mode': request.query_params.get('search_mode')}).hit_test(
|
||||
))
|
||||
def put(self, request: Request, dataset_id: str):
|
||||
return result.success(DataSetSerializers.HitTest(data={
|
||||
'id': dataset_id,
|
||||
'user_id': request.user.id,
|
||||
"query_text": request.data.get("query_text"),
|
||||
"top_number": request.data.get("top_number"),
|
||||
'similarity': request.data.get('similarity'),
|
||||
'search_mode': request.data.get('search_mode')}
|
||||
).hit_test())
|
||||
|
||||
class Embedding(APIView):
|
||||
authentication_classes = [TokenAuth]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,127 @@
|
|||
# Generated by Django 4.2.15 on 2025-03-13 07:21
|
||||
|
||||
from django.db import migrations
|
||||
from django.db.models import Q
|
||||
|
||||
mysql_template = """
|
||||
def query_mysql(host,port, user, password, database, sql):
|
||||
import pymysql
|
||||
import json
|
||||
from pymysql.cursors import DictCursor
|
||||
from datetime import datetime, date
|
||||
|
||||
def default_serializer(obj):
|
||||
from decimal import Decimal
|
||||
if isinstance(obj, (datetime, date)):
|
||||
return obj.isoformat() # 将 datetime/date 转换为 ISO 格式字符串
|
||||
elif isinstance(obj, Decimal):
|
||||
return float(obj) # 将 Decimal 转换为 float
|
||||
raise TypeError(f"Type {type(obj)} not serializable")
|
||||
|
||||
try:
|
||||
# 创建连接
|
||||
db = pymysql.connect(
|
||||
host=host,
|
||||
port=int(port),
|
||||
user=user,
|
||||
password=password,
|
||||
database=database,
|
||||
cursorclass=DictCursor # 使用字典游标
|
||||
)
|
||||
|
||||
# 使用 cursor() 方法创建一个游标对象 cursor
|
||||
cursor = db.cursor()
|
||||
|
||||
# 使用 execute() 方法执行 SQL 查询
|
||||
cursor.execute(sql)
|
||||
|
||||
# 使用 fetchall() 方法获取所有数据
|
||||
data = cursor.fetchall()
|
||||
|
||||
# 处理 bytes 类型的数据
|
||||
for row in data:
|
||||
for key, value in row.items():
|
||||
if isinstance(value, bytes):
|
||||
row[key] = value.decode("utf-8") # 转换为字符串
|
||||
|
||||
# 将数据序列化为 JSON
|
||||
json_data = json.dumps(data, default=default_serializer, ensure_ascii=False)
|
||||
return json_data
|
||||
|
||||
# 关闭数据库连接
|
||||
db.close()
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error while connecting to MySQL: {e}")
|
||||
raise e
|
||||
"""
|
||||
|
||||
pgsql_template = """
|
||||
def queryPgSQL(database, user, password, host, port, query):
|
||||
import psycopg2
|
||||
import json
|
||||
from datetime import datetime
|
||||
|
||||
# 自定义 JSON 序列化函数
|
||||
def default_serializer(obj):
|
||||
from decimal import Decimal
|
||||
if isinstance(obj, datetime):
|
||||
return obj.isoformat() # 将 datetime 转换为 ISO 格式字符串
|
||||
elif isinstance(obj, Decimal):
|
||||
return float(obj) # 将 Decimal 转换为 float
|
||||
raise TypeError(f"Type {type(obj)} not serializable")
|
||||
|
||||
# 数据库连接信息
|
||||
conn_params = {
|
||||
"dbname": database,
|
||||
"user": user,
|
||||
"password": password,
|
||||
"host": host,
|
||||
"port": port
|
||||
}
|
||||
try:
|
||||
# 建立连接
|
||||
conn = psycopg2.connect(**conn_params)
|
||||
print("连接成功!")
|
||||
# 创建游标对象
|
||||
cursor = conn.cursor()
|
||||
# 执行查询语句
|
||||
cursor.execute(query)
|
||||
# 获取查询结果
|
||||
rows = cursor.fetchall()
|
||||
# 处理 bytes 类型的数据
|
||||
columns = [desc[0] for desc in cursor.description]
|
||||
result = [dict(zip(columns, row)) for row in rows]
|
||||
# 转换为 JSON 格式
|
||||
json_result = json.dumps(result, default=default_serializer, ensure_ascii=False)
|
||||
return json_result
|
||||
except Exception as e:
|
||||
print(f"发生错误:{e}")
|
||||
raise e
|
||||
finally:
|
||||
# 关闭游标和连接
|
||||
if cursor:
|
||||
cursor.close()
|
||||
if conn:
|
||||
conn.close()
|
||||
"""
|
||||
|
||||
|
||||
def fix_type(apps, schema_editor):
|
||||
FunctionLib = apps.get_model('function_lib', 'FunctionLib')
|
||||
FunctionLib.objects.filter(
|
||||
Q(id='22c21b76-0308-11f0-9694-5618c4394482') | Q(template_id='22c21b76-0308-11f0-9694-5618c4394482')
|
||||
).update(code=mysql_template)
|
||||
FunctionLib.objects.filter(
|
||||
Q(id='bd1e8b88-0302-11f0-87bb-5618c4394482') | Q(template_id='bd1e8b88-0302-11f0-87bb-5618c4394482')
|
||||
).update(code=pgsql_template)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('function_lib', '0003_functionlib_function_type_functionlib_icon_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(fix_type)
|
||||
]
|
||||
|
|
@ -33,11 +33,13 @@ from smartdoc.const import CONFIG
|
|||
|
||||
function_executor = FunctionExecutor(CONFIG.get('SANDBOX'))
|
||||
|
||||
|
||||
class FlibInstance:
|
||||
def __init__(self, function_lib: dict, version: str):
|
||||
self.function_lib = function_lib
|
||||
self.version = version
|
||||
|
||||
|
||||
def encryption(message: str):
|
||||
"""
|
||||
加密敏感字段数据 加密方式是 如果密码是 1234567890 那么给前端则是 123******890
|
||||
|
|
@ -68,7 +70,8 @@ def encryption(message: str):
|
|||
class FunctionLibModelSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = FunctionLib
|
||||
fields = ['id', 'name', 'icon', 'desc', 'code', 'input_field_list','init_field_list', 'init_params', 'permission_type', 'is_active', 'user_id', 'template_id',
|
||||
fields = ['id', 'name', 'icon', 'desc', 'code', 'input_field_list', 'init_field_list', 'init_params',
|
||||
'permission_type', 'is_active', 'user_id', 'template_id',
|
||||
'create_time', 'update_time']
|
||||
|
||||
|
||||
|
|
@ -148,7 +151,6 @@ class FunctionLibSerializer(serializers.Serializer):
|
|||
select_user_id = serializers.CharField(required=False, allow_null=True, allow_blank=True)
|
||||
function_type = serializers.CharField(required=False, allow_null=True, allow_blank=True)
|
||||
|
||||
|
||||
def get_query_set(self):
|
||||
query_set = QuerySet(FunctionLib).filter(
|
||||
(Q(user_id=self.data.get('user_id')) | Q(permission_type='PUBLIC')))
|
||||
|
|
@ -269,7 +271,7 @@ class FunctionLibSerializer(serializers.Serializer):
|
|||
|
||||
def is_valid(self, *, raise_exception=False):
|
||||
super().is_valid(raise_exception=True)
|
||||
if not QuerySet(FunctionLib).filter(id=self.data.get('id')).exists():
|
||||
if not QuerySet(FunctionLib).filter(user_id=self.data.get('user_id'), id=self.data.get('id')).exists():
|
||||
raise AppApiException(500, _('Function does not exist'))
|
||||
|
||||
def delete(self, with_valid=True):
|
||||
|
|
@ -285,7 +287,8 @@ class FunctionLibSerializer(serializers.Serializer):
|
|||
if with_valid:
|
||||
self.is_valid(raise_exception=True)
|
||||
EditFunctionLib(data=instance).is_valid(raise_exception=True)
|
||||
edit_field_list = ['name', 'desc', 'code', 'icon', 'input_field_list', 'init_field_list', 'init_params', 'permission_type', 'is_active']
|
||||
edit_field_list = ['name', 'desc', 'code', 'icon', 'input_field_list', 'init_field_list', 'init_params',
|
||||
'permission_type', 'is_active']
|
||||
edit_dict = {field: instance.get(field) for field in edit_field_list if (
|
||||
field in instance and instance.get(field) is not None)}
|
||||
|
||||
|
|
@ -317,7 +320,8 @@ class FunctionLibSerializer(serializers.Serializer):
|
|||
if function_lib.init_params:
|
||||
function_lib.init_params = json.loads(rsa_long_decrypt(function_lib.init_params))
|
||||
if function_lib.init_field_list:
|
||||
password_fields = [i["field"] for i in function_lib.init_field_list if i.get("input_type") == "PasswordInput"]
|
||||
password_fields = [i["field"] for i in function_lib.init_field_list if
|
||||
i.get("input_type") == "PasswordInput"]
|
||||
if function_lib.init_params:
|
||||
for k in function_lib.init_params:
|
||||
if k in password_fields and function_lib.init_params[k]:
|
||||
|
|
|
|||
|
|
@ -7238,7 +7238,7 @@ msgstr ""
|
|||
msgid ""
|
||||
"The confirmation password must be 6-20 characters long and must be a "
|
||||
"combination of letters, numbers, and special characters."
|
||||
msgstr ""
|
||||
msgstr "The confirmation password must be 6-20 characters long and must be a combination of letters, numbers, and special characters.(Special character support:_、!、@、#、$、(、) ……)"
|
||||
|
||||
#: community/apps/users/serializers/user_serializers.py:380
|
||||
#, python-brace-format
|
||||
|
|
@ -7499,4 +7499,13 @@ msgid "Captcha code error or expiration"
|
|||
msgstr ""
|
||||
|
||||
msgid "captcha"
|
||||
msgstr ""
|
||||
|
||||
msgid "Reasoning enable"
|
||||
msgstr ""
|
||||
|
||||
msgid "Reasoning start tag"
|
||||
msgstr ""
|
||||
|
||||
msgid "Reasoning end tag"
|
||||
msgstr ""
|
||||
|
|
@ -7395,7 +7395,7 @@ msgstr "语言只支持:"
|
|||
msgid ""
|
||||
"The confirmation password must be 6-20 characters long and must be a "
|
||||
"combination of letters, numbers, and special characters."
|
||||
msgstr "确认密码长度6-20个字符,必须字母、数字、特殊字符组合"
|
||||
msgstr "确认密码长度6-20个字符,必须字母、数字、特殊字符组合(特殊字符支持:_、!、@、#、$、(、) ……)"
|
||||
|
||||
#: community/apps/users/serializers/user_serializers.py:380
|
||||
#, python-brace-format
|
||||
|
|
@ -7662,4 +7662,13 @@ msgid "Captcha code error or expiration"
|
|||
msgstr "验证码错误或过期"
|
||||
|
||||
msgid "captcha"
|
||||
msgstr "验证码"
|
||||
msgstr "验证码"
|
||||
|
||||
msgid "Reasoning enable"
|
||||
msgstr "开启思考过程"
|
||||
|
||||
msgid "Reasoning start tag"
|
||||
msgstr "思考过程开始标签"
|
||||
|
||||
msgid "Reasoning end tag"
|
||||
msgstr "思考过程结束标签"
|
||||
|
|
@ -7405,7 +7405,7 @@ msgstr "語言只支持:"
|
|||
msgid ""
|
||||
"The confirmation password must be 6-20 characters long and must be a "
|
||||
"combination of letters, numbers, and special characters."
|
||||
msgstr "確認密碼長度6-20個字符,必須字母、數字、特殊字符組合"
|
||||
msgstr "確認密碼長度6-20個字符,必須字母、數字、特殊字符組合(特殊字元支持:_、!、@、#、$、(、) ……)"
|
||||
|
||||
#: community/apps/users/serializers/user_serializers.py:380
|
||||
#, python-brace-format
|
||||
|
|
@ -7672,4 +7672,13 @@ msgid "Captcha code error or expiration"
|
|||
msgstr "驗證碼錯誤或過期"
|
||||
|
||||
msgid "captcha"
|
||||
msgstr "驗證碼"
|
||||
msgstr "驗證碼"
|
||||
|
||||
msgid "Reasoning enable"
|
||||
msgstr "開啟思考過程"
|
||||
|
||||
msgid "Reasoning start tag"
|
||||
msgstr "思考過程開始標籤"
|
||||
|
||||
msgid "Reasoning end tag"
|
||||
msgstr "思考過程結束標籤"
|
||||
|
|
@ -99,7 +99,7 @@ class BaseChatOpenAI(ChatOpenAI):
|
|||
except Exception as e:
|
||||
tokenizer = TokenizerManage.get_tokenizer()
|
||||
return sum([len(tokenizer.encode(get_buffer_string([m]))) for m in messages])
|
||||
return self.usage_metadata.get('input_tokens', 0)
|
||||
return self.usage_metadata.get('input_tokens', self.usage_metadata.get('prompt_tokens', 0))
|
||||
|
||||
def get_num_tokens(self, text: str) -> int:
|
||||
if self.usage_metadata is None or self.usage_metadata == {}:
|
||||
|
|
@ -108,7 +108,8 @@ class BaseChatOpenAI(ChatOpenAI):
|
|||
except Exception as e:
|
||||
tokenizer = TokenizerManage.get_tokenizer()
|
||||
return len(tokenizer.encode(text))
|
||||
return self.get_last_generation_info().get('output_tokens', 0)
|
||||
return self.get_last_generation_info().get('output_tokens',
|
||||
self.get_last_generation_info().get('completion_tokens', 0))
|
||||
|
||||
def _stream(self, *args: Any, **kwargs: Any) -> Iterator[ChatGenerationChunk]:
|
||||
kwargs['stream_usage'] = True
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
2. 程序需要, 用户不需要更改的写到settings中
|
||||
3. 程序需要, 用户需要更改的写到本config中
|
||||
"""
|
||||
import datetime
|
||||
import errno
|
||||
import logging
|
||||
import os
|
||||
|
|
@ -112,13 +113,18 @@ class Config(dict):
|
|||
"USER": self.get('DB_USER'),
|
||||
"PASSWORD": self.get('DB_PASSWORD'),
|
||||
"ENGINE": self.get('DB_ENGINE'),
|
||||
"CONN_MAX_AGE": 0,
|
||||
"POOL_OPTIONS": {
|
||||
"POOL_SIZE": 20,
|
||||
"MAX_OVERFLOW": int(self.get('DB_MAX_OVERFLOW')),
|
||||
'RECYCLE': 30 * 60
|
||||
"RECYCLE": 1800,
|
||||
"TIMEOUT": 30
|
||||
}
|
||||
}
|
||||
|
||||
def get_session_timeout(self):
|
||||
return datetime.timedelta(seconds=int(self.get('SESSION_TIMEOUT', 60 * 60 * 2)))
|
||||
|
||||
def get_language_code(self):
|
||||
return self.get('LANGUAGE_CODE', 'zh-CN')
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ from common.constants.permission_constants import PermissionConstants, CompareCo
|
|||
from common.log.log import log
|
||||
from common.response import result
|
||||
from common.util.common import encryption
|
||||
from smartdoc.settings import JWT_AUTH
|
||||
from smartdoc.const import CONFIG
|
||||
from users.serializers.user_serializers import RegisterSerializer, LoginSerializer, CheckCodeSerializer, \
|
||||
RePasswordSerializer, \
|
||||
SendEmailSerializer, UserProfile, UserSerializer, UserManageSerializer, UserInstanceSerializer, SystemSerializer, \
|
||||
|
|
@ -199,7 +199,7 @@ class Login(APIView):
|
|||
# 校验请求参数
|
||||
user = login_request.is_valid(raise_exception=True)
|
||||
token = login_request.get_user_token()
|
||||
token_cache.set(token, user, timeout=JWT_AUTH['JWT_EXPIRATION_DELTA'])
|
||||
token_cache.set(token, user, timeout=CONFIG.get_session_timeout())
|
||||
return result.success(token)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@
|
|||
"mermaid": "^10.9.0",
|
||||
"mitt": "^3.0.0",
|
||||
"moment": "^2.30.1",
|
||||
"nanoid": "^5.1.5",
|
||||
"npm": "^10.2.4",
|
||||
"nprogress": "^0.2.0",
|
||||
"pinia": "^2.1.6",
|
||||
|
|
@ -53,8 +54,7 @@
|
|||
"vue-draggable-plus": "^0.6.0",
|
||||
"vue-i18n": "^9.13.1",
|
||||
"vue-router": "^4.2.4",
|
||||
"vue3-menus": "^1.1.2",
|
||||
"vuedraggable": "^4.1.0"
|
||||
"vue3-menus": "^1.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rushstack/eslint-patch": "^1.3.2",
|
||||
|
|
|
|||
|
|
@ -227,7 +227,7 @@ const getApplicationHitTest: (
|
|||
data: any,
|
||||
loading?: Ref<boolean>
|
||||
) => Promise<Result<Array<any>>> = (application_id, data, loading) => {
|
||||
return get(`${prefix}/${application_id}/hit_test`, data, loading)
|
||||
return put(`${prefix}/${application_id}/hit_test`, data, undefined, loading)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -186,7 +186,7 @@ const getDatasetHitTest: (
|
|||
data: any,
|
||||
loading?: Ref<boolean>
|
||||
) => Promise<Result<Array<any>>> = (dataset_id, data, loading) => {
|
||||
return get(`${prefix}/${dataset_id}/hit_test`, data, loading)
|
||||
return put(`${prefix}/${dataset_id}/hit_test`, data, undefined, loading)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -182,6 +182,7 @@
|
|||
@keydown.enter="sendChatHandle($event)"
|
||||
@paste="handlePaste"
|
||||
@drop="handleDrop"
|
||||
@dragover.prevent="handleDragOver"
|
||||
/>
|
||||
|
||||
<div class="operate flex align-center">
|
||||
|
|
@ -287,7 +288,7 @@
|
|||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted, nextTick, watch } from 'vue'
|
||||
import { ref, computed, onMounted, nextTick, watch, reactive } from 'vue'
|
||||
import Recorder from 'recorder-core'
|
||||
import TouchChat from './TouchChat.vue'
|
||||
import applicationApi from '@/api/application'
|
||||
|
|
@ -392,17 +393,7 @@ const checkMaxFilesLimit = () => {
|
|||
uploadOtherList.value.length
|
||||
)
|
||||
}
|
||||
const file_name_eq = (str: string, str1: string) => {
|
||||
return (
|
||||
str.replaceAll(' ', '') === str1.replaceAll(' ', '') ||
|
||||
decodeHtmlEntities(str) === decodeHtmlEntities(str1)
|
||||
)
|
||||
}
|
||||
function decodeHtmlEntities(str: string) {
|
||||
const tempDiv = document.createElement('div')
|
||||
tempDiv.innerHTML = str
|
||||
return tempDiv.textContent || tempDiv.innerText || ''
|
||||
}
|
||||
|
||||
const uploadFile = async (file: any, fileList: any) => {
|
||||
const { maxFiles, fileLimit } = props.applicationDetails.file_upload_setting
|
||||
// 单次上传文件数量限制
|
||||
|
|
@ -427,6 +418,7 @@ const uploadFile = async (file: any, fileList: any) => {
|
|||
const formData = new FormData()
|
||||
formData.append('file', file.raw, file.name)
|
||||
//
|
||||
file = reactive(file)
|
||||
const extension = file.name.split('.').pop().toUpperCase() // 获取文件后缀名并转为小写
|
||||
if (imageExtensions.includes(extension)) {
|
||||
uploadImageList.value.push(file)
|
||||
|
|
@ -460,41 +452,9 @@ const uploadFile = async (file: any, fileList: any) => {
|
|||
)
|
||||
.then((response) => {
|
||||
fileList.splice(0, fileList.length)
|
||||
uploadImageList.value.forEach((file: any) => {
|
||||
const f = response.data.filter((f: any) => file_name_eq(f.name, file.name))
|
||||
if (f.length > 0) {
|
||||
file.url = f[0].url
|
||||
file.file_id = f[0].file_id
|
||||
}
|
||||
})
|
||||
uploadDocumentList.value.forEach((file: any) => {
|
||||
const f = response.data.filter((f: any) => file_name_eq(f.name, file.name))
|
||||
if (f.length > 0) {
|
||||
file.url = f[0].url
|
||||
file.file_id = f[0].file_id
|
||||
}
|
||||
})
|
||||
uploadAudioList.value.forEach((file: any) => {
|
||||
const f = response.data.filter((f: any) => file_name_eq(f.name, file.name))
|
||||
if (f.length > 0) {
|
||||
file.url = f[0].url
|
||||
file.file_id = f[0].file_id
|
||||
}
|
||||
})
|
||||
uploadVideoList.value.forEach((file: any) => {
|
||||
const f = response.data.filter((f: any) => file_name_eq(f.name, file.name))
|
||||
if (f.length > 0) {
|
||||
file.url = f[0].url
|
||||
file.file_id = f[0].file_id
|
||||
}
|
||||
})
|
||||
uploadOtherList.value.forEach((file: any) => {
|
||||
const f = response.data.filter((f: any) => file_name_eq(f.name, file.name))
|
||||
if (f.length > 0) {
|
||||
file.url = f[0].url
|
||||
file.file_id = f[0].file_id
|
||||
}
|
||||
})
|
||||
file.url = response.data[0].url
|
||||
file.file_id = response.data[0].file_id
|
||||
|
||||
if (!inputValue.value && uploadImageList.value.length > 0) {
|
||||
inputValue.value = t('chat.uploadFile.imageMessage')
|
||||
}
|
||||
|
|
@ -529,6 +489,7 @@ const handlePaste = (event: ClipboardEvent) => {
|
|||
// 阻止默认粘贴行为
|
||||
event.preventDefault()
|
||||
}
|
||||
|
||||
// 新增拖拽处理
|
||||
const handleDrop = (event: DragEvent) => {
|
||||
if (!props.applicationDetails.file_upload_enable) return
|
||||
|
|
@ -548,6 +509,12 @@ const handleDrop = (event: DragEvent) => {
|
|||
uploadFile(elFile, [elFile])
|
||||
})
|
||||
}
|
||||
|
||||
const handleDragOver = (event: DragEvent) => {
|
||||
if (event.dataTransfer) {
|
||||
event.dataTransfer.dropEffect = 'copy' // Firefox需要这一行来允许放置操作
|
||||
}
|
||||
}
|
||||
// 语音录制任务id
|
||||
const intervalId = ref<any | null>(null)
|
||||
// 语音录制开始秒数
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ const prologue = computed(() => {
|
|||
]
|
||||
let _temp = temp
|
||||
for (const index in tag_list) {
|
||||
_temp = _temp.replaceAll(tag_list[index], '')
|
||||
_temp = _temp.replace(new RegExp(tag_list[index], 'g'), '')
|
||||
}
|
||||
const quick_question_list = _temp.match(/-\s.+/g)
|
||||
let result = temp
|
||||
|
|
|
|||
|
|
@ -163,13 +163,13 @@ const initialApiFormData = ref({})
|
|||
const isUserInput = computed(
|
||||
() =>
|
||||
props.applicationDetails.work_flow?.nodes?.filter((v: any) => v.id === 'base-node')[0]
|
||||
.properties.user_input_field_list.length > 0
|
||||
?.properties.user_input_field_list.length > 0
|
||||
)
|
||||
const isAPIInput = computed(
|
||||
() =>
|
||||
props.type === 'debug-ai-chat' &&
|
||||
props.applicationDetails.work_flow?.nodes?.filter((v: any) => v.id === 'base-node')[0]
|
||||
.properties.api_input_field_list.length > 0
|
||||
?.properties.api_input_field_list.length > 0
|
||||
)
|
||||
const showUserInputContent = computed(() => {
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
class="problem-button mt-4 mb-4 flex"
|
||||
:class="sendMessage ? 'cursor' : 'disabled'"
|
||||
>
|
||||
<el-icon class="mr-8" style="margin-top: 2px;">
|
||||
<el-icon class="mr-8" style="margin-top: 2px">
|
||||
<EditPen />
|
||||
</el-icon>
|
||||
{{ item.content }}
|
||||
|
|
@ -237,7 +237,7 @@ const split_form_rander_ = (source: string, type: string) => {
|
|||
padding: 12px;
|
||||
box-sizing: border-box;
|
||||
color: var(--el-text-color-regular);
|
||||
word-break: break-all;
|
||||
word-break: break-word;
|
||||
|
||||
&:hover {
|
||||
background: var(--el-color-primary-light-9);
|
||||
|
|
|
|||
|
|
@ -139,7 +139,7 @@ Response requirements:
|
|||
hybridSearch: 'Hybrid Search',
|
||||
hybridSearchTooltip:
|
||||
'Hybrid search is a retrieval method based on both vector and text similarity, suitable for medium data volumes in the knowledge.',
|
||||
similarityThreshold: 'Similarity higher than',
|
||||
similarityThreshold: 'Similarity not lower than',
|
||||
similarityTooltip: 'The higher the similarity, the stronger the correlation.',
|
||||
topReferences: 'Top N Segments',
|
||||
maxCharacters: 'Maximum Characters per Reference',
|
||||
|
|
|
|||
|
|
@ -149,7 +149,7 @@ export default {
|
|||
tooltip: 'When user asks a question, handle matched segments according to the set method.'
|
||||
},
|
||||
similarity: {
|
||||
label: 'Similarity Higher Than',
|
||||
label: 'Similarity not lower than',
|
||||
placeholder: 'Directly return segment content',
|
||||
requiredMessage: 'Please enter similarity value'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -130,7 +130,7 @@ export default {
|
|||
hybridSearch: '混合检索',
|
||||
hybridSearchTooltip:
|
||||
'混合检索是一种基于向量和文本相似度的检索方式,适用于知识库中的中等数据量场景。',
|
||||
similarityThreshold: '相似度高于',
|
||||
similarityThreshold: '相似度不低于',
|
||||
similarityTooltip: '相似度越高相关性越强。',
|
||||
topReferences: '引用分段数 TOP',
|
||||
maxCharacters: '最多引用字符数',
|
||||
|
|
|
|||
|
|
@ -147,7 +147,7 @@ export default {
|
|||
tooltip: '用户提问时,命中文档下的分段时按照设置的方式进行处理。'
|
||||
},
|
||||
similarity: {
|
||||
label: '相似度高于',
|
||||
label: '相似度不低于',
|
||||
placeholder: '直接返回分段内容',
|
||||
requiredMessage: '请输入相似度'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -129,7 +129,7 @@ export default {
|
|||
hybridSearch: '混合檢索',
|
||||
hybridSearchTooltip:
|
||||
'混合檢索是一種基於向量和文本相似度的檢索方式,適用於知識庫中的中等數據量場景。',
|
||||
similarityThreshold: '相似度高於',
|
||||
similarityThreshold: '相似度不低於',
|
||||
similarityTooltip: '相似度越高相關性越強。',
|
||||
topReferences: '引用分段數 TOP',
|
||||
maxCharacters: '最多引用字元數',
|
||||
|
|
|
|||
|
|
@ -146,7 +146,7 @@ export default {
|
|||
tooltip: '用戶提問時,命中文檔下的分段時按照設置的方式進行處理。'
|
||||
},
|
||||
similarity: {
|
||||
label: '相似度高于',
|
||||
label: '相似度不低於',
|
||||
placeholder: '直接返回分段内容',
|
||||
requiredMessage: '请输入相似度'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { MsgError } from '@/utils/message'
|
||||
|
||||
import { nanoid } from 'nanoid'
|
||||
export function toThousands(num: any) {
|
||||
return num?.toString().replace(/\d+/, function (n: any) {
|
||||
return n.replace(/(\d)(?=(?:\d{3})+$)/g, '$1,')
|
||||
|
|
@ -25,7 +25,7 @@ export function filesize(size: number) {
|
|||
随机id
|
||||
*/
|
||||
export const randomId = function () {
|
||||
return Math.floor(Math.random() * 10000) + ''
|
||||
return nanoid()
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -48,7 +48,9 @@ const typeList: any = {
|
|||
export function getImgUrl(name: string) {
|
||||
const list = Object.values(typeList).flat()
|
||||
|
||||
const type = list.includes(fileType(name).toLowerCase()) ? fileType(name).toLowerCase() : 'unknown'
|
||||
const type = list.includes(fileType(name).toLowerCase())
|
||||
? fileType(name).toLowerCase()
|
||||
: 'unknown'
|
||||
return new URL(`../assets/fileType/${type}-icon.svg`, import.meta.url).href
|
||||
}
|
||||
// 是否是白名单后缀
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
<div class="header border-b flex-between p-12-24">
|
||||
<div class="flex align-center">
|
||||
<back-button @click="back"></back-button>
|
||||
<h4>{{ detail?.name }}</h4>
|
||||
<h4 class="ellipsis" style="max-width: 270px" :title="detail?.name">{{ detail?.name }}</h4>
|
||||
<div v-if="showHistory && disablePublic">
|
||||
<el-text type="info" class="ml-16 color-secondary"
|
||||
>{{ $t('views.applicationWorkflow.info.previewVersion') }}
|
||||
|
|
@ -101,7 +101,7 @@
|
|||
/>
|
||||
</div>
|
||||
|
||||
<h4>
|
||||
<h4 class="ellipsis" style="max-width: 270px" :title="detail?.name">
|
||||
{{ detail?.name || $t('views.application.applicationForm.form.appName.label') }}
|
||||
</h4>
|
||||
</div>
|
||||
|
|
@ -279,7 +279,6 @@ async function publicHandle() {
|
|||
return
|
||||
}
|
||||
applicationApi.putPublishApplication(id as String, obj, loading).then(() => {
|
||||
|
||||
application.asyncGetApplicationDetail(id, loading).then((res: any) => {
|
||||
detail.value.name = res.data.name
|
||||
MsgSuccess(t('views.applicationWorkflow.tip.publicSuccess'))
|
||||
|
|
|
|||
|
|
@ -28,7 +28,9 @@
|
|||
/>
|
||||
</div>
|
||||
|
||||
<h4>{{ applicationDetail?.name }}</h4>
|
||||
<h4 class="ellipsis-1" style="width: 50%" :title="applicationDetail?.name">
|
||||
{{ applicationDetail?.name }}
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
|
|
@ -263,7 +265,7 @@ function getChatRecord() {
|
|||
currentChatId.value,
|
||||
paginationConfig,
|
||||
loading,
|
||||
false
|
||||
true
|
||||
)
|
||||
.then((res: any) => {
|
||||
paginationConfig.total = res.data.total
|
||||
|
|
|
|||
|
|
@ -27,7 +27,9 @@
|
|||
/>
|
||||
</div>
|
||||
|
||||
<h4>{{ applicationDetail?.name }}</h4>
|
||||
<h4 class="ellipsis-1" style="width: 66%" :title="applicationDetail?.name">
|
||||
{{ applicationDetail?.name }}
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
|
|
@ -259,7 +261,7 @@ function getChatRecord() {
|
|||
currentChatId.value,
|
||||
paginationConfig,
|
||||
loading,
|
||||
false
|
||||
true
|
||||
)
|
||||
.then((res: any) => {
|
||||
paginationConfig.total = res.data.total
|
||||
|
|
|
|||
|
|
@ -27,7 +27,9 @@
|
|||
:size="32"
|
||||
/>
|
||||
</div>
|
||||
<h4>{{ applicationDetail?.name }}</h4>
|
||||
<h4 class="ellipsis-1" style="width: 66%" :title="applicationDetail?.name">
|
||||
{{ applicationDetail?.name }}
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
|
|
@ -313,7 +315,7 @@ function getChatRecord() {
|
|||
currentChatId.value,
|
||||
paginationConfig.value,
|
||||
loading,
|
||||
false
|
||||
true
|
||||
)
|
||||
.then((res: any) => {
|
||||
paginationConfig.value.total = res.data.total
|
||||
|
|
|
|||
|
|
@ -8,15 +8,16 @@
|
|||
</h4>
|
||||
</template>
|
||||
<div class="hit-test__main p-16" v-loading="loading">
|
||||
<div class="question-title" :style="{ visibility: questionTitle ? 'visible' : 'hidden' }">
|
||||
<div class="avatar">
|
||||
<AppAvatar>
|
||||
<img src="@/assets/user-icon.svg" style="width: 54%" alt="" />
|
||||
</AppAvatar>
|
||||
</div>
|
||||
<div class="content">
|
||||
<h4 class="text break-all">{{ questionTitle }}</h4>
|
||||
</div>
|
||||
<div
|
||||
class="question-title flex align-center"
|
||||
:style="{ visibility: questionTitle ? 'visible' : 'hidden' }"
|
||||
>
|
||||
<AppAvatar>
|
||||
<img src="@/assets/user-icon.svg" style="width: 54%" alt="" />
|
||||
</AppAvatar>
|
||||
<h4 class="break-all ellipsis-1 ml-8" style="width: 66%" :title="questionTitle">
|
||||
{{ questionTitle }}
|
||||
</h4>
|
||||
</div>
|
||||
<el-scrollbar>
|
||||
<div class="hit-test-height">
|
||||
|
|
@ -349,20 +350,6 @@ onMounted(() => {})
|
|||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.hit-test {
|
||||
.question-title {
|
||||
.avatar {
|
||||
float: left;
|
||||
}
|
||||
.content {
|
||||
padding-left: 40px;
|
||||
.text {
|
||||
padding: 6px 0;
|
||||
height: 34px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__operate {
|
||||
.operate-textarea {
|
||||
box-shadow: 0px 6px 24px 0px rgba(31, 35, 41, 0.08);
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@
|
|||
ref="el"
|
||||
v-bind:modelValue="form_data.branch"
|
||||
:disabled="form_data.branch === 2"
|
||||
:filter="'.no-drag'"
|
||||
handle=".handle"
|
||||
:animation="150"
|
||||
ghostClass="ghost"
|
||||
|
|
|
|||
|
|
@ -91,6 +91,7 @@ import { ref, computed, onMounted } from 'vue'
|
|||
import { isLastNode } from '@/workflow/common/data'
|
||||
import applicationApi from '@/api/application'
|
||||
import { app } from '@/main'
|
||||
import {t} from "@/locales";
|
||||
const props = defineProps<{ nodeModel: any }>()
|
||||
|
||||
const nodeCascaderRef = ref()
|
||||
|
|
@ -119,8 +120,16 @@ const chat_data = computed({
|
|||
const FunctionNodeFormRef = ref<FormInstance>()
|
||||
|
||||
const validate = () => {
|
||||
for (const item of chat_data.value.input_field_list) {
|
||||
if (item.source === 'reference' && item.is_required && item.value[0] !== 'global') {
|
||||
if (props.nodeModel.graphModel.nodes.filter((node: any) => node.id === item.value[0]).length === 0 ) {
|
||||
item.value = []
|
||||
return Promise.reject({node: props.nodeModel, errMessage: item.name + t('dynamicsForm.tip.requiredMessage')})
|
||||
}
|
||||
}
|
||||
}
|
||||
return FunctionNodeFormRef.value?.validate().catch((err) => {
|
||||
return Promise.reject({ node: props.nodeModel, errMessage: err })
|
||||
return Promise.reject({node: props.nodeModel, errMessage: err})
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue