diff --git a/README.md b/README.md index 10eff3a6a0e..9556afe3184 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,69 @@ Access MaxKB web interface at `http://your_server_ip:8080` with default admin cr - LLM Framework:[LangChain](https://www.langchain.com/) - Database:[PostgreSQL + pgvector](https://www.postgresql.org/) +## Feature Comparison + +MaxKB is positioned as an Ready-to-use RAG (Retrieval-Augmented Generation) intelligent Q&A application, rather than a middleware platform for building large model applications. The following table is merely a comparison from a functional perspective. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FeatureLangChainDify.AIFlowiseMaxKB
(Built upon LangChain)
Supported LLMsRich VarietyRich VarietyRich VarietyRich Variety
RAG Engine
Agent
Workflow
Observability
SSO/Access control✅ (Pro)
On-premise Deployment
+ ## Star History [![Star History Chart](https://api.star-history.com/svg?repos=1Panel-dev/MaxKB&type=Date)](https://star-history.com/#1Panel-dev/MaxKB&Date) diff --git a/apps/application/flow/step_node/__init__.py b/apps/application/flow/step_node/__init__.py index 5e992079bbb..f3602901aa5 100644 --- a/apps/application/flow/step_node/__init__.py +++ b/apps/application/flow/step_node/__init__.py @@ -24,11 +24,14 @@ from .speech_to_text_step_node import BaseSpeechToTextNode from .start_node import * from .text_to_speech_step_node.impl.base_text_to_speech_node import BaseTextToSpeechNode +from .variable_assign_node import BaseVariableAssignNode -node_list = [BaseStartStepNode, BaseChatNode, BaseSearchDatasetNode, BaseQuestionNode, BaseConditionNode, BaseReplyNode, +node_list = [BaseStartStepNode, BaseChatNode, BaseSearchDatasetNode, BaseQuestionNode, + BaseConditionNode, BaseReplyNode, BaseFunctionNodeNode, BaseFunctionLibNodeNode, BaseRerankerNode, BaseApplicationNode, BaseDocumentExtractNode, - BaseImageUnderstandNode, BaseFormNode, BaseSpeechToTextNode, BaseTextToSpeechNode,BaseImageGenerateNode] + BaseImageUnderstandNode, BaseFormNode, BaseSpeechToTextNode, BaseTextToSpeechNode, + BaseImageGenerateNode, BaseVariableAssignNode] def get_node(node_type): diff --git a/apps/application/flow/step_node/variable_assign_node/__init__.py b/apps/application/flow/step_node/variable_assign_node/__init__.py new file mode 100644 index 00000000000..2d231e6066d --- /dev/null +++ b/apps/application/flow/step_node/variable_assign_node/__init__.py @@ -0,0 +1,3 @@ +# coding=utf-8 + +from .impl import * \ No newline at end of file diff --git a/apps/application/flow/step_node/variable_assign_node/i_variable_assign_node.py b/apps/application/flow/step_node/variable_assign_node/i_variable_assign_node.py new file mode 100644 index 00000000000..e4594183f35 --- /dev/null +++ b/apps/application/flow/step_node/variable_assign_node/i_variable_assign_node.py @@ -0,0 +1,27 @@ +# coding=utf-8 + +from typing import Type + +from django.utils.translation import gettext_lazy as _ +from rest_framework import serializers + +from application.flow.i_step_node import INode, NodeResult +from common.util.field_message import ErrMessage + + +class VariableAssignNodeParamsSerializer(serializers.Serializer): + variable_list = serializers.ListField(required=True, + error_messages=ErrMessage.list(_("Reference Field"))) + + +class IVariableAssignNode(INode): + type = 'variable-assign-node' + + def get_node_params_serializer_class(self) -> Type[serializers.Serializer]: + return VariableAssignNodeParamsSerializer + + def _run(self): + return self.execute(**self.node_params_serializer.data, **self.flow_params_serializer.data) + + def execute(self, variable_list, stream, **kwargs) -> NodeResult: + pass diff --git a/apps/application/flow/step_node/variable_assign_node/impl/__init__.py b/apps/application/flow/step_node/variable_assign_node/impl/__init__.py new file mode 100644 index 00000000000..7585cdd8fe4 --- /dev/null +++ b/apps/application/flow/step_node/variable_assign_node/impl/__init__.py @@ -0,0 +1,9 @@ +# coding=utf-8 +""" + @project: maxkb + @Author:虎 + @file: __init__.py + @date:2024/6/11 17:49 + @desc: +""" +from .base_variable_assign_node import * \ No newline at end of file diff --git a/apps/application/flow/step_node/variable_assign_node/impl/base_variable_assign_node.py b/apps/application/flow/step_node/variable_assign_node/impl/base_variable_assign_node.py new file mode 100644 index 00000000000..1eebcf3b988 --- /dev/null +++ b/apps/application/flow/step_node/variable_assign_node/impl/base_variable_assign_node.py @@ -0,0 +1,59 @@ +# coding=utf-8 +import json +from typing import List + +from application.flow.i_step_node import NodeResult +from application.flow.step_node.variable_assign_node.i_variable_assign_node import IVariableAssignNode + + +class BaseVariableAssignNode(IVariableAssignNode): + def save_context(self, details, workflow_manage): + self.context['variable_list'] = details.get('variable_list') + self.context['result_list'] = details.get('result_list') + + def execute(self, variable_list, stream, **kwargs) -> NodeResult: + # + result_list = [] + for variable in variable_list: + if 'fields' not in variable: + continue + if 'global' == variable['fields'][0]: + result = { + 'name': variable['name'], + 'input_value': self.get_reference_content(variable['fields']), + } + if variable['source'] == 'custom': + if variable['type'] in ['dict', 'array']: + if isinstance(variable['value'], dict) or isinstance(variable['value'], list): + val = variable['value'] + else: + val = json.loads(variable['value']) + self.workflow_manage.context[variable['fields'][1]] = val + result['output_value'] = variable['value'] = val + else: + self.workflow_manage.context[variable['fields'][1]] = variable['value'] + result['output_value'] = variable['value'] + else: + reference = self.get_reference_content(variable['reference']) + self.workflow_manage.context[variable['fields'][1]] = reference + result['output_value'] = reference + result_list.append(result) + + return NodeResult({'variable_list': variable_list, 'result_list': result_list}, {}) + + def get_reference_content(self, fields: List[str]): + return str(self.workflow_manage.get_reference_field( + fields[0], + fields[1:])) + + def get_details(self, index: int, **kwargs): + return { + 'name': self.node.properties.get('stepName'), + "index": index, + 'run_time': self.context.get('run_time'), + 'type': self.node.type, + 'variable_list': self.context.get('variable_list'), + 'result_list': self.context.get('result_list'), + 'status': self.status, + 'err_message': self.err_message + } diff --git a/apps/application/models/application.py b/apps/application/models/application.py index ace8c29d427..cc38ff88ddb 100644 --- a/apps/application/models/application.py +++ b/apps/application/models/application.py @@ -7,6 +7,7 @@ @desc: """ import datetime +import decimal import json import uuid @@ -140,6 +141,8 @@ def default(self, obj): return str(obj) if isinstance(obj, datetime.datetime): return obj.strftime("%Y-%m-%d %H:%M:%S") + if isinstance(obj, decimal.Decimal): + return float(obj) else: return json.JSONEncoder.default(self, obj) diff --git a/apps/application/serializers/chat_serializers.py b/apps/application/serializers/chat_serializers.py index 3d8d7696f17..055ec4a623a 100644 --- a/apps/application/serializers/chat_serializers.py +++ b/apps/application/serializers/chat_serializers.py @@ -66,6 +66,10 @@ def valid_model_params_setting(model_id, model_params_setting): credential.get_model_params_setting_form(model.model_name).valid_form(model_params_setting) +class ReAbstractInstanceSerializers(serializers.Serializer): + abstract = serializers.CharField(required=True, error_messages=ErrMessage.char(_("abstract"))) + + class ChatSerializers(serializers.Serializer): class Operate(serializers.Serializer): chat_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid(_("Conversation ID"))) @@ -78,6 +82,15 @@ def logic_delete(self, with_valid=True): is_deleted=True) return True + def re_abstract(self, instance, with_valid=True): + if with_valid: + self.is_valid(raise_exception=True) + ReAbstractInstanceSerializers(data=instance).is_valid(raise_exception=True) + + QuerySet(Chat).filter(id=self.data.get('chat_id'), application_id=self.data.get('application_id')).update( + abstract=instance.get('abstract')) + return True + def delete(self, with_valid=True): if with_valid: self.is_valid(raise_exception=True) diff --git a/apps/application/swagger_api/application_api.py b/apps/application/swagger_api/application_api.py index 2cabe2150a0..2c9cbd86bf4 100644 --- a/apps/application/swagger_api/application_api.py +++ b/apps/application/swagger_api/application_api.py @@ -234,7 +234,7 @@ def get_request_body_api(): default=[]), 'edges': openapi.Schema(type=openapi.TYPE_ARRAY, items=openapi.Schema(type=openapi.TYPE_OBJECT), title=_('Connection List'), description=_("Connection List"), - default={}), + default=[]), } ) @@ -324,7 +324,8 @@ def get_request_body_api(): return openapi.Schema( type=openapi.TYPE_OBJECT, required=['name', 'desc', 'model_id', 'dialogue_number', 'dataset_setting', 'model_setting', - 'problem_optimization', 'stt_model_enable', 'stt_model_enable', 'tts_type'], + 'problem_optimization', 'stt_model_enable', 'stt_model_enable', 'tts_type', + 'work_flow'], properties={ 'name': openapi.Schema(type=openapi.TYPE_STRING, title=_("Application Name"), description=_("Application Name")), @@ -361,7 +362,8 @@ def get_request_body_api(): 'tts_model_enable': openapi.Schema(type=openapi.TYPE_STRING, title=_("Is text-to-speech enabled"), description=_("Is text-to-speech enabled")), 'tts_type': openapi.Schema(type=openapi.TYPE_STRING, title=_("Text-to-speech type"), - description=_("Text-to-speech type")) + description=_("Text-to-speech type")), + 'work_flow': ApplicationApi.WorkFlow.get_request_body_api(), } ) @@ -432,4 +434,4 @@ def get_request_body_api(): 'text': openapi.Schema(type=openapi.TYPE_STRING, title=_("Text"), description=_("Text")), } - ) \ No newline at end of file + ) diff --git a/apps/application/swagger_api/chat_api.py b/apps/application/swagger_api/chat_api.py index a0cfc8e1b97..32522fd9457 100644 --- a/apps/application/swagger_api/chat_api.py +++ b/apps/application/swagger_api/chat_api.py @@ -23,6 +23,34 @@ def get_request_params_api(): description=_('Application ID')) ] + class Operate(ApiMixin): + @staticmethod + def get_request_params_api(): + return [openapi.Parameter(name='application_id', + in_=openapi.IN_PATH, + type=openapi.TYPE_STRING, + required=True, + description=_('Application ID')), + openapi.Parameter(name='chat_id', + in_=openapi.IN_PATH, + type=openapi.TYPE_STRING, + required=True, + description=_('Conversation ID')), + ] + + class ReAbstract(ApiMixin): + @staticmethod + def get_request_body_api(): + return openapi.Schema( + type=openapi.TYPE_OBJECT, + required=['abstract'], + properties={ + 'abstract': openapi.Schema(type=openapi.TYPE_STRING, title=_("abstract"), + description=_("abstract")) + + } + ) + class OpenAIChatApi(ApiMixin): @staticmethod diff --git a/apps/application/views/chat_views.py b/apps/application/views/chat_views.py index b87309bb298..556f99f26b4 100644 --- a/apps/application/views/chat_views.py +++ b/apps/application/views/chat_views.py @@ -150,7 +150,7 @@ def post(self, request: Request, chat_id: str): operation_id=_("Get the conversation list"), manual_parameters=ChatApi.get_request_params_api(), responses=result.get_api_array_response(ChatApi.get_response_body_api()), - tags=[_("Application/Conversation Log")] + tags=[_("Application/Conversation Log")] ) @has_permissions( ViewPermission([RoleConstants.ADMIN, RoleConstants.USER, RoleConstants.APPLICATION_KEY], @@ -222,6 +222,23 @@ def delete(self, request: Request, application_id: str, chat_id: str): data={'application_id': application_id, 'user_id': request.user.id, 'chat_id': chat_id}).logic_delete()) + @action(methods=['PUT'], detail=False) + @swagger_auto_schema(operation_summary=_("Client modifies dialogue summary"), + operation_id=_("Client modifies dialogue summary"), + request_body=ChatClientHistoryApi.Operate.ReAbstract.get_request_body_api(), + tags=[_("Application/Conversation Log")]) + @has_permissions(ViewPermission( + [RoleConstants.APPLICATION_ACCESS_TOKEN], + [lambda r, keywords: Permission(group=Group.APPLICATION, operate=Operate.USE, + dynamic_tag=keywords.get('application_id'))], + compare=CompareConstants.AND), + compare=CompareConstants.AND) + def put(self, request: Request, application_id: str, chat_id: str): + return result.success( + ChatSerializers.Operate( + data={'application_id': application_id, 'user_id': request.user.id, + 'chat_id': chat_id}).re_abstract(request.data)) + class Page(APIView): authentication_classes = [TokenAuth] diff --git a/apps/common/util/function_code.py b/apps/common/util/function_code.py index fa3dc503801..31797a9f425 100644 --- a/apps/common/util/function_code.py +++ b/apps/common/util/function_code.py @@ -31,7 +31,7 @@ def __init__(self, sandbox=False): self.user = None self._createdir() if self.sandbox: - os.system(f"chown -R {self.user}:{self.user} {self.sandbox_path}") + os.system(f"chown -R {self.user}:root {self.sandbox_path}") def _createdir(self): old_mask = os.umask(0o077) diff --git a/apps/locales/en_US/LC_MESSAGES/django.po b/apps/locales/en_US/LC_MESSAGES/django.po index 04cb138e3dc..eab7f3db8ed 100644 --- a/apps/locales/en_US/LC_MESSAGES/django.po +++ b/apps/locales/en_US/LC_MESSAGES/django.po @@ -6753,3 +6753,5 @@ msgstr "" msgid "Image download failed, check network" msgstr "" +msgid "Client modifies dialogue summary" +msgstr "" diff --git a/apps/locales/zh_CN/LC_MESSAGES/django.po b/apps/locales/zh_CN/LC_MESSAGES/django.po index 77d44475870..9e29bb722a8 100644 --- a/apps/locales/zh_CN/LC_MESSAGES/django.po +++ b/apps/locales/zh_CN/LC_MESSAGES/django.po @@ -6892,4 +6892,5 @@ msgstr "超出许可证使用限制。" msgid "Image download failed, check network" msgstr "图片下载失败,请检查网络" - +msgid "Client modifies dialogue summary" +msgstr "客户端修改对话摘要" diff --git a/apps/locales/zh_Hant/LC_MESSAGES/django.po b/apps/locales/zh_Hant/LC_MESSAGES/django.po index e0386d030f3..d61d8e98c61 100644 --- a/apps/locales/zh_Hant/LC_MESSAGES/django.po +++ b/apps/locales/zh_Hant/LC_MESSAGES/django.po @@ -6902,4 +6902,7 @@ msgid "License usage limit exceeded." msgstr "超出許可證使用限制。" msgid "Image download failed, check network" -msgstr "圖片下載失敗,檢查網絡" \ No newline at end of file +msgstr "圖片下載失敗,檢查網絡" + +msgid "Client modifies dialogue summary" +msgstr "用戶端修改對話摘要" \ No newline at end of file diff --git a/apps/setting/models_provider/impl/ollama_model_provider/credential/reranker.py b/apps/setting/models_provider/impl/ollama_model_provider/credential/reranker.py index 7f7feff1560..c2825aacb42 100644 --- a/apps/setting/models_provider/impl/ollama_model_provider/credential/reranker.py +++ b/apps/setting/models_provider/impl/ollama_model_provider/credential/reranker.py @@ -64,4 +64,3 @@ def build_model(self, model_info: Dict[str, object]): return self api_base = forms.TextInputField('API URL', required=True) - api_key = forms.TextInputField('API Key', required=True) diff --git a/apps/setting/models_provider/impl/ollama_model_provider/model/reranker.py b/apps/setting/models_provider/impl/ollama_model_provider/model/reranker.py index fd004ea014f..f82c9a21adf 100644 --- a/apps/setting/models_provider/impl/ollama_model_provider/model/reranker.py +++ b/apps/setting/models_provider/impl/ollama_model_provider/model/reranker.py @@ -1,82 +1,48 @@ from typing import Sequence, Optional, Any, Dict from langchain_core.callbacks import Callbacks -from langchain_core.documents import BaseDocumentCompressor, Document -import requests - +from langchain_core.documents import Document +from langchain_community.embeddings import OllamaEmbeddings from setting.models_provider.base_model_provider import MaxKBBaseModel +from sklearn.metrics.pairwise import cosine_similarity +from pydantic.v1 import BaseModel, Field -class OllamaReranker(MaxKBBaseModel, BaseDocumentCompressor): - api_base: Optional[str] - """URL of the Ollama server""" - model_name: Optional[str] - """The model name to use for reranking""" - api_key: Optional[str] +class OllamaReranker(MaxKBBaseModel, OllamaEmbeddings, BaseModel): + top_n: Optional[int] = Field(3, description="Number of top documents to return") @staticmethod - def new_instance(model_name, model_credential: Dict[str, object], **model_kwargs): - return OllamaReranker(api_base=model_credential.get('api_base'), model_name=model_name, - api_key=model_credential.get('api_key'), top_n=model_kwargs.get('top_n', 3)) - - top_n: Optional[int] = 3 - - def __init__( - self, api_base: Optional[str] = None, model_name: Optional[str] = None, top_n=3, - api_key: Optional[str] = None - ): - super().__init__() - - if api_base is None: - raise ValueError("Please provide server URL") - - if model_name is None: - raise ValueError("Please provide the model name") - - self.api_base = api_base - self.model_name = model_name - self.api_key = api_key - self.top_n = top_n + def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): + optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs) + return OllamaReranker( + model=model_name, + base_url=model_credential.get('api_base'), + **optional_params + ) def compress_documents(self, documents: Sequence[Document], query: str, callbacks: Optional[Callbacks] = None) -> \ Sequence[Document]: - """ - Given a query and a set of documents, rerank them using Ollama API. - """ - if not documents or len(documents) == 0: - return [] - - # Prepare the data to send to Ollama API - headers = { - 'Authorization': f'Bearer {self.api_key}' # Use API key for authentication if required - } - - # Format the documents to be sent in a format understood by Ollama's API - documents_text = [document.page_content for document in documents] - - # Make a POST request to Ollama's rerank API endpoint - payload = { - 'model': self.model_name, # Specify the model - 'query': query, - 'documents': documents_text, - 'top_n': self.top_n - } - - try: - response = requests.post(f'{self.api_base}/v1/rerank', headers=headers, json=payload) - response.raise_for_status() - res = response.json() - - # Ensure the response contains expected results - if 'results' not in res: - raise ValueError("The API response did not contain rerank results.") + """Rank documents based on their similarity to the query. + + Args: + query: The query text. + documents: The list of document texts to rank. + + Returns: + List of documents sorted by relevance to the query. + """ + # 获取查询和文档的嵌入 + query_embedding = self.embed_query(query) + documents = [doc.page_content for doc in documents] + document_embeddings = self.embed_documents(documents) + # 计算相似度 + similarities = cosine_similarity([query_embedding], document_embeddings)[0] + ranked_docs = [(doc,_) for _, doc in sorted(zip(similarities, documents), reverse=True)][:self.top_n] + return [ + Document( + page_content=doc, # 第一个值是文档内容 + metadata={'relevance_score': score} # 第二个值是相似度分数 + ) + for doc, score in ranked_docs + ] - # Convert the API response into a list of Document objects with relevance scores - ranked_documents = [ - Document(page_content=d['text'], metadata={'relevance_score': d['relevance_score']}) - for d in res['results'] - ] - return ranked_documents - except requests.exceptions.RequestException as e: - print(f"Error during API request: {e}") - return [] # Return an empty list if the request failed diff --git a/apps/setting/models_provider/impl/vllm_model_provider/model/image.py b/apps/setting/models_provider/impl/vllm_model_provider/model/image.py index f3b69a382e1..4d5dda29dd7 100644 --- a/apps/setting/models_provider/impl/vllm_model_provider/model/image.py +++ b/apps/setting/models_provider/impl/vllm_model_provider/model/image.py @@ -1,5 +1,8 @@ -from typing import Dict +from typing import Dict, List +from langchain_core.messages import get_buffer_string, BaseMessage + +from common.config.tokenizer_manage_config import TokenizerManage from setting.models_provider.base_model_provider import MaxKBBaseModel from setting.models_provider.impl.base_chat_open_ai import BaseChatOpenAI @@ -18,3 +21,18 @@ def new_instance(model_type, model_name, model_credential: Dict[str, object], ** stream_usage=True, **optional_params, ) + + def is_cache_model(self): + return False + + def get_num_tokens_from_messages(self, messages: List[BaseMessage]) -> int: + if self.usage_metadata is None or self.usage_metadata == {}: + 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) + + def get_num_tokens(self, text: str) -> int: + if self.usage_metadata is None or self.usage_metadata == {}: + tokenizer = TokenizerManage.get_tokenizer() + return len(tokenizer.encode(text)) + return self.get_last_generation_info().get('output_tokens', 0) diff --git a/apps/setting/models_provider/impl/vllm_model_provider/model/llm.py b/apps/setting/models_provider/impl/vllm_model_provider/model/llm.py index 6f3ed0620d0..7d2a63acd08 100644 --- a/apps/setting/models_provider/impl/vllm_model_provider/model/llm.py +++ b/apps/setting/models_provider/impl/vllm_model_provider/model/llm.py @@ -1,8 +1,11 @@ # coding=utf-8 -from typing import Dict +from typing import Dict, List from urllib.parse import urlparse, ParseResult +from langchain_core.messages import BaseMessage, get_buffer_string + +from common.config.tokenizer_manage_config import TokenizerManage from setting.models_provider.base_model_provider import MaxKBBaseModel from setting.models_provider.impl.base_chat_open_ai import BaseChatOpenAI @@ -33,3 +36,15 @@ def new_instance(model_type, model_name, model_credential: Dict[str, object], ** stream_usage=True, ) return vllm_chat_open_ai + + def get_num_tokens_from_messages(self, messages: List[BaseMessage]) -> int: + if self.usage_metadata is None or self.usage_metadata == {}: + 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) + + def get_num_tokens(self, text: str) -> int: + if self.usage_metadata is None or self.usage_metadata == {}: + tokenizer = TokenizerManage.get_tokenizer() + return len(tokenizer.encode(text)) + return self.get_last_generation_info().get('output_tokens', 0) diff --git a/pyproject.toml b/pyproject.toml index 9e16fe0500e..a5d175b855b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,7 +48,7 @@ gunicorn = "^22.0.0" python-daemon = "3.0.1" boto3 = "^1.34.160" tencentcloud-sdk-python = "^3.0.1209" -xinference-client = "^0.14.1.post1" +xinference-client = "^1.3.0" psutil = "^6.0.0" celery = { extras = ["sqlalchemy"], version = "^5.4.0" } django-celery-beat = "^2.6.0" @@ -59,6 +59,7 @@ pydub = "^0.25.1" cffi = "^1.17.1" pysilk = "^0.0.1" django-db-connection-pool = "^1.2.5" +opencv-python-headless = "^4.11.0.86" [build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" diff --git a/ui/package.json b/ui/package.json index df369cee555..960dd86829d 100644 --- a/ui/package.json +++ b/ui/package.json @@ -18,6 +18,7 @@ "@ctrl/tinycolor": "^4.1.0", "@logicflow/core": "^1.2.27", "@logicflow/extension": "^1.2.27", + "@types/sortablejs": "^1.15.8", "@vavt/cm-extension": "^1.6.0", "@vueuse/core": "^10.9.0", "@wecom/jssdk": "^2.1.0", @@ -42,6 +43,7 @@ "pinyin-pro": "^3.18.2", "recorder-core": "^1.3.24040900", "screenfull": "^6.0.2", + "sortablejs": "^1.15.6", "use-element-plus-theme": "^0.0.5", "vue": "^3.3.4", "vue-clipboard3": "^2.0.0", diff --git a/ui/src/api/log.ts b/ui/src/api/log.ts index 20973a37104..edcd4d93e33 100644 --- a/ui/src/api/log.ts +++ b/ui/src/api/log.ts @@ -220,6 +220,29 @@ const delChatClientLog: ( return del(`${prefix}/${application_id}/chat/client/${chat_id}`, undefined, {}, loading) } +/** + * 修改历史日志abstract + * @param 参数 + * application_id, chat_id, + * data { + "abstract": "string", + } + */ + +const putChatClientLog: ( + application_id: string, + chat_id: string, + data: any, + loading?: Ref +) => Promise> = (application_id, chat_id, data, loading) => { + return put( + `${prefix}/${application_id}/chat/client/${chat_id}`, + data, + undefined, + loading + ) +} + export default { getChatLog, delChatLog, @@ -231,5 +254,6 @@ export default { exportChatLog, getChatLogClient, delChatClientLog, - postChatRecordLog + postChatRecordLog, + putChatClientLog } diff --git a/ui/src/assets/icon_assigner.svg b/ui/src/assets/icon_assigner.svg new file mode 100644 index 00000000000..269f075c381 --- /dev/null +++ b/ui/src/assets/icon_assigner.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/ui/src/components/ai-chat/ExecutionDetailDialog.vue b/ui/src/components/ai-chat/ExecutionDetailDialog.vue index 52c20844193..4f97c00850b 100644 --- a/ui/src/components/ai-chat/ExecutionDetailDialog.vue +++ b/ui/src/components/ai-chat/ExecutionDetailDialog.vue @@ -1,6 +1,6 @@ + + +